java game engine fxgl 04 animation

Posted by jil on Sat, 25 Dec 2021 20:56:04 +0100

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.

Topics: Java Game Development