Skip to content

Adding Sprite Animations

Almas Baimagambetov edited this page Jan 5, 2018 · 5 revisions

This tutorial is standalone, but it is expected that you've completed previous tutorials, hence we won't talk about the things we already discussed.

Tutorial Brief

Works with fxgl

  1. Add a player whose view is an animated sprite loaded from a sprite sheet.

Preparation

I will use this simple sprite sheet that is adapted from the Phaser game engine. Just drop the file under assets/textures.

Basics

Let's quickly get our game app going:

public class BasicGameApp4 extends GameApplication {

    @Override
    protected void initSettings(GameSettings settings) {
        settings.setWidth(800);
        settings.setHeight(600);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Run the app and you should have a basic 800x600 window.

Add player

Let's add a player at 200, 200 without any view but with a DudeControl, which we will create below.

private Entity player;

@Override
protected void initGame() {
    player = Entities.builder()
            .at(200, 200)
            .with(new DudeControl())
            .buildAndAttach();
}

You can read about Control in FXGL ECS wiki but in short, a control defines / adds a behavior to an entity.

Add DudeControl

Here's the full source of DudeControl, which we will discuss line by line.

class DudeControl extends Control {

    private int speed = 0;

    private AnimatedTexture texture;
    private AnimationChannel animIdle, animWalk;

    public DudeControl() {
        animIdle = new AnimationChannel("newdude.png", 4, 32, 42, Duration.seconds(1), 1, 1);
        animWalk = new AnimationChannel("newdude.png", 4, 32, 42, Duration.seconds(1), 0, 3);

        texture = new AnimatedTexture(animIdle);
    }

    @Override
    public void onAdded(Entity entity) {
        entity.setView(texture);
    }

    @Override
    public void onUpdate(Entity entity, double tpf) {
        entity.translateX(speed * tpf);

        if (speed == 0) {
            texture.setAnimationChannel(animIdle);
        } else {
            texture.setAnimationChannel(animWalk);

            speed = (int) (speed * 0.9);

            if (FXGLMath.abs(speed) < 1) {
                speed = 0;
            }
        }
    }

    public void moveRight() {
        speed = 150;

        getEntity().setScaleX(1);
    }

    public void moveLeft() {
        speed = -150;

        getEntity().setScaleX(-1);
    }
}

First, class DudeControl extends Control, any control extends Control.

private int speed = 0;

We will use this variable to check if our dude is moving. The following is the core of this tutorial. First, we declare an animated texture, which is the same as a normal texture, except it can have animation channels. An animation channels defines a single animation, such as "walk", "stand", "attack", "jump", etc. In this example there are two channels: idle and walk, shown in the snippet below. In the DudeControl constructor we set the channels and the parameters are as follows: sprite sheet name, number of frames per row, single frame width, single frame height, duration of the animation channel, start frame and end frame.

private AnimatedTexture texture;
private AnimationChannel animIdle, animWalk;

public DudeControl() {
    animIdle = new AnimationChannel("newdude.png", 4, 32, 42, Duration.seconds(1), 1, 1);
    animWalk = new AnimationChannel("newdude.png", 4, 32, 42, Duration.seconds(1), 0, 3);

    texture = new AnimatedTexture(animIdle);
}

@Override
public void onAdded(Entity entity) {
    entity.setView(texture);
}

Note that entity.setView(texture); is called in onAdded() since in the constructor we are creating the control, but we don't know which entity it'll be attached. In onAdded() we know exactly what entity the control is attached to.

Let's consider the update method:

@Override
public void onUpdate(Entity entity, double tpf) {
    entity.translateX(speed * tpf);

    if (speed == 0) {
        texture.setAnimationChannel(animIdle);
    } else {
        texture.setAnimationChannel(animWalk);

        speed = (int) (speed * 0.9);

        if (FXGLMath.abs(speed) < 1) {
            speed = 0;
        }
    }
}

We translate (move) the entity by given speed, then we check what animation to use. If we are not moving (speed == 0) then set animation to idle, else set animation to walk. Reduce the speed by 10% and if speed is less than 1, set it to 0.

Move left and right methods allow us to make the entity face "the other way" if needed:

public void moveRight() {
    speed = 150;

    getEntity().setScaleX(1);
}

public void moveLeft() {
    speed = -150;

    getEntity().setScaleX(-1);
}

Adding Input

The following is straightforward: we bind "A" and "D" to call DudeControl's left and right movement methods.

@Override
protected void initInput() {
    getInput().addAction(new UserAction("Right") {
        @Override
        protected void onAction() {
            player.getControl(DudeControl.class).moveRight();
        }
    }, KeyCode.D);

    getInput().addAction(new UserAction("Left") {
        @Override
        protected void onAction() {
            player.getControl(DudeControl.class).moveLeft();
        }
    }, KeyCode.A);
}

The entire file is available below:

import com.almasb.fxgl.app.GameApplication;
import com.almasb.fxgl.core.math.FXGLMath;
import com.almasb.fxgl.entity.Control;
import com.almasb.fxgl.entity.Entities;
import com.almasb.fxgl.entity.Entity;
import com.almasb.fxgl.input.UserAction;
import com.almasb.fxgl.settings.GameSettings;
import com.almasb.fxgl.texture.AnimatedTexture;
import com.almasb.fxgl.texture.AnimationChannel;
import javafx.scene.input.KeyCode;
import javafx.util.Duration;

/**
 *
 *
 * @author Almas Baimagambetov (AlmasB) ([email protected])
 */
public class BasicGameApp4 extends GameApplication {

    @Override
    protected void initSettings(GameSettings settings) {
        settings.setWidth(800);
        settings.setHeight(600);
    }

    private Entity player;

    @Override
    protected void initInput() {
        getInput().addAction(new UserAction("Right") {
            @Override
            protected void onAction() {
                player.getControl(DudeControl.class).moveRight();
            }
        }, KeyCode.D);

        getInput().addAction(new UserAction("Left") {
            @Override
            protected void onAction() {
                player.getControl(DudeControl.class).moveLeft();
            }
        }, KeyCode.A);
    }

    @Override
    protected void initGame() {
        player = Entities.builder()
                .at(200, 200)
                .with(new DudeControl())
                .buildAndAttach();
    }

    class DudeControl extends Control {

        private int speed = 0;

        private AnimatedTexture texture;
        private AnimationChannel animIdle, animWalk;

        public DudeControl() {
            animIdle = new AnimationChannel("newdude.png", 4, 32, 42, Duration.seconds(1), 1, 1);
            animWalk = new AnimationChannel("newdude.png", 4, 32, 42, Duration.seconds(1), 0, 3);

            texture = new AnimatedTexture(animIdle);
        }

        @Override
        public void onAdded(Entity entity) {
            entity.setView(texture);
        }

        @Override
        public void onUpdate(Entity entity, double tpf) {
            entity.translateX(speed * tpf);

            if (speed == 0) {
                texture.setAnimationChannel(animIdle);
            } else {
                texture.setAnimationChannel(animWalk);

                speed = (int) (speed * 0.9);

                if (FXGLMath.abs(speed) < 1) {
                    speed = 0;
                }
            }
        }

        public void moveRight() {
            speed = 150;

            getEntity().setScaleX(1);
        }

        public void moveLeft() {
            speed = -150;

            getEntity().setScaleX(-1);
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

You should now be able to use a simple sprite sheet animation in your games!

Want some more? Explore Core Features, or experiment with pre-made games.

Clone this wiki locally