FXGL 04. Animation (lovely squid mother)
In the previous article, we simply created a png format image display entity. Here we want to change the display mode of this entity and let a vivid squid mother appear in our game world!
1. Presentation process of entity
We are not unfamiliar with such an entity creation process. If we need to use a material as the display content, we only need to add it to the view method in creation.
Entity entity = FXGL.entityBuilder().with(new MoveComponent()).build();
Let's explore the essence of view method. (I don't know much about Kotlin)
But I still found the following source code
fun view(node: Node) = this.also { entity.viewComponent.addChild(node) } fun view(textureName: String) = this.also { view(FXGL.texture(textureName)) }
Node class is the top-level abstract class of most javafx shape graphics. The familiar shape, box and other classes inherit from this class. It is obvious that viewcomponent is the actual presentation control class of an entity.
If you want to modify the animation content of the entity when the program is running, it is easy to think that you need to modify the viewcomponent in the entity
2. Download material from material website
I recommend it here Love to network , free and abundant resources.
You can download all kinds of materials you need.
3. Entity display before modification
I downloaded a walking picture of squid mother here
First, for the entity static factory, I remove the view part
public static Entity createEntity(EntityType type){ switch (type) { case PLANE -> { Entity entity = FXGL.entityBuilder().with(new MoveComponent()).build(); entity.setType(EntityType.PLANE); return entity; } default -> { return null; } } }
Then modify the moving component and add animation content to it
package com.dam.wonder.component; import com.almasb.fxgl.core.math.Vec2; import com.almasb.fxgl.entity.component.Component; import com.almasb.fxgl.texture.AnimatedTexture; import com.almasb.fxgl.texture.AnimationChannel; import javafx.scene.image.Image; import javafx.util.Duration; import lombok.extern.slf4j.Slf4j; @Slf4j public class MoveComponent extends Component { private double speedX = 0d; private double speedY = 0d; /** * Called after the component is added to entity. */ @Override public void onAdded() { entity.getViewComponent().addChild(texture); } private double maxSpeed = 4d; private double aTime = 1d; private boolean speedXAdd; private boolean speedYAdd; //Here, an oriented attribute is added to judge the oriented problem of the entity at various speeds private int face = 1; //Animation channels correspond to animation in four directions private final AnimationChannel up; private final AnimationChannel down; private final AnimationChannel right; private final AnimationChannel left; //Animation material private final AnimatedTexture texture; @Override public void onUpdate(double tpf) { // log.info("in the current state, X speed is = [{}], Y speed is = [{}] x acceleration state is = [{}] Y acceleration state is = [{}]], speedX,speedY,speedXAdd,speedYAdd); int tempFace = face; if (speedX != 0d) { Vec2 dir = Vec2.fromAngle(entity.getRotation() - 360) .mulLocal(speedX); entity.translate(dir); if (speedX>0) { tempFace = 1; }else { tempFace = 2; } } if (speedY != 0d) { Vec2 dir = Vec2.fromAngle(entity.getRotation() - 90) .mulLocal(speedY); entity.translate(dir); if (speedY > 0) { tempFace = 3; }else { tempFace = 4; } } if (!speedXAdd) { slowDownSpeed(true); } if (!speedYAdd) { slowDownSpeed(false); } if (tempFace != face) { if (tempFace == 1) { this.texture.loopAnimationChannel(left); }else if (tempFace == 2){ this.texture.loopAnimationChannel(right); }else if (tempFace == 3) { this.texture.loopAnimationChannel(up); }else { this.texture.loopAnimationChannel(down); } face = tempFace; } } public MoveComponent () { //Add the method of loading animation in the construction method Image image = new Image("assets/textures/player.png"); //Here, through the planning of the picture, the number of animations in each line, the width and height of each frame, the span time of the whole animation, and several animations down = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 0, 3); right = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 4, 7); left = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 8, 11); up = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 12, 15); texture = new AnimatedTexture(up); //Play animation texture.loop(); } public void up() { changeSpeed(true,false); } public void left() { changeSpeed(false,true); } public void right() { changeSpeed(true,true); } public void down(){ changeSpeed(false,false); } public void stop() { speedX = 0d; speedY = 0d; } public void stopX() { speedXAdd = false; } public void stopY() { speedYAdd = false; } /** * Change the moving speed actively * @param upOrDown * @param xOrY */ private void changeSpeed(boolean upOrDown,boolean xOrY) { if (xOrY) { speedXAdd = true; if (upOrDown) { if (speedX < maxSpeed) { speedX = speedX + (float)maxSpeed/(10*aTime) + 0.01; } }else { if (speedX > -maxSpeed) { speedX = speedX - (float)maxSpeed/(10f*aTime) - 0.01; } } }else { speedYAdd = true; if (upOrDown) { if (speedY < maxSpeed) { speedY = speedY + (float)maxSpeed/(10f*aTime) + 0.01; } }else { if (speedY > -maxSpeed) { speedY = speedY - (float)maxSpeed/(10f*aTime) - 0.01; } } } // log.info("the current state of the entity is position = [{}], speed Y = [{}], speed x = [{}]", entity. Getposition(), this speedY,this. speedX); } /** * Speed reduction passive deceleration * @param xOrY */ private void slowDownSpeed(boolean xOrY) { if (xOrY) { if (speedX > 0.5) { speedX = speedX - (float)speedX/10 -0.01;} else if (speedX< -0.5){ speedX = speedX - (float)speedX/10 +0.01; }else { speedX = 0d; } }else { if (speedY > 0.5) { speedY = speedY - (float)speedY/10 -0.01;} else if (speedY< -0.5){ speedY = speedY - (float)speedY/10 +0.01; }else { speedY = 0d; } } } public double getSpeedX() { return speedX; } public void setSpeedX(double speedX) { this.speedX = speedX; } public double getSpeedY() { return speedY; } public void setSpeedY(double speedY) { this.speedY = speedY; } public double getMaxSpeed() { return maxSpeed; } public void setMaxSpeed(double maxSpeed) { this.maxSpeed = maxSpeed; } public double getaTime() { return aTime; } public void setaTime(double aTime) { this.aTime = aTime; } }
Simply start the project, a squid mother jumped onto the paper!
Of course, I'm not satisfied with this step, because the squid mother still keeps moving animation when the speed is zero. Make a little change.
It's easy to think of simply adding four new channels.
private final AnimationChannel up; private final AnimationChannel upHold; private final AnimationChannel down; private final AnimationChannel downHold; private final AnimationChannel right; private final AnimationChannel rightHold; private final AnimationChannel left; private final AnimationChannel leftHold; private final AnimatedTexture texture;
Simply initialize them
down = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 0, 3); downHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 1, 1); right = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 4, 7); rightHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 5, 5); leftHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 9, 9); left = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 8, 11); up = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 12, 15); upHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 13, 13);
Then simply judge that when the speed is zero, it is modified to a static channel
@Override public void onUpdate(double tpf) { // log.info("in the current state, X speed is = [{}], Y speed is = [{}] x acceleration state is = [{}] Y acceleration state is = [{}]], speedX,speedY,speedXAdd,speedYAdd); int tempFace = face; if (speedX != 0d) { Vec2 dir = Vec2.fromAngle(entity.getRotation() - 360) .mulLocal(speedX); entity.translate(dir); if (speedX>0) { tempFace = 1; }else if (speedX<0){ tempFace = 2; } }else { //Transformation oriented if (tempFace == 1) { tempFace = 5; }else if (tempFace == 2) { tempFace = 6; } } if (speedY != 0d) { Vec2 dir = Vec2.fromAngle(entity.getRotation() - 90) .mulLocal(speedY); entity.translate(dir); if (speedY > 0) { tempFace = 3; }else { tempFace = 4; } }else { if (tempFace == 3) { tempFace = 7; }else if (tempFace == 4) { tempFace = 8; } } if (!speedXAdd) { slowDownSpeed(true); } if (!speedYAdd) { slowDownSpeed(false); } if (tempFace != face) { if (tempFace == 1) { this.texture.loopAnimationChannel(left); }else if (tempFace == 2){ this.texture.loopAnimationChannel(right); }else if (tempFace == 3) { this.texture.loopAnimationChannel(up); }else if (tempFace == 4){ this.texture.loopAnimationChannel(down); }else if (tempFace == 5) { this.texture.loopAnimationChannel(leftHold); }else if (tempFace == 6) { this.texture.loopAnimationChannel(rightHold); }else if (tempFace == 7){ this.texture.loopAnimationChannel(upHold); }else if (tempFace == 8) { this.texture.loopAnimationChannel(downHold); } face = tempFace; } }
It's very easy. With a pop, the whole animation process is finished.
Of course, in addition to the method of dynamic material animation channel, there is another way to use more animation builders
(animationBuilder)
This method can customize many animations, such as various deformation animations, particle effects, etc. it can also set callbacks to control animation. It is detailed in the official wiki, so I won't repeat it here.