-
-
Notifications
You must be signed in to change notification settings - Fork 563
Adding Sprite Animations
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.
Works with
- Add a player whose view is an animated sprite loaded from a sprite sheet.
I will use this simple sprite sheet that is adapted from the Phaser game engine.
Just drop the file under assets/textures
.
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.
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.
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);
}
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.