FXGL 04.动画 (可爱的乌贼娘)

我们在之前的文章中简单创建了一个png格式的图片展示的实体,这里我们要更换换这个实体的展示模式,让一个活灵活现的乌贼娘出现在我们的游戏世界中!

1.实体的展示过程

这样的一个实体创建过程我们并不陌生,如果需要将某个素材作为展示内容,我们只需要在创建中的view方法中加入其中。

Entity entity = FXGL.entityBuilder().with(new MoveComponent()).build();

那我们来探究下view方法的本质。(我并不太懂Kotlin)
但是还是找到了以下源码

fun view(node: Node) = this.also {
        entity.viewComponent.addChild(node)
    }

    fun view(textureName: String) = this.also {
        view(FXGL.texture(textureName))
    }

Node 类是大多数javafx的形状图形的顶级抽象类,我们熟悉的shape,box等类都继承自该类。明显可以理解viewcomponent是实体的实际展示控制类。
那如果想要在程序运行时修改实体的动画内容,很容易就能想到需要做就是修改entity中的viewcomponent

2.从素材网站中下载素材

这里我推荐使用爱给网,免费且资源众多。

java播放动画图集 java动画引擎_java

在其中可以下载需要的各种素材。

3.修改之前的实体展示

我这里下载了一张乌贼娘的行走图

java播放动画图集 java动画引擎_ide_02


首先对于实体静态工厂,我将其中的view部分移除

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;
            }

    }
}

然后修改移动组件,为其增加动画内容

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;
    //这里增加一个面向属性,用来判断实体在各种速度下的面向问题
    private int face = 1;
    //动画频道 对应四个方向的动画
    private final AnimationChannel up;
    private final AnimationChannel down;
    private final AnimationChannel right;
    private final AnimationChannel left;
   //动画素材
    private final AnimatedTexture texture;
    @Override
    public void onUpdate(double tpf) {
//        log.info("当前状态下  x速度为=[{}], Y速度为=[{}]  x加速状态为=[{}] Y加速状态为 =[{}]",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 () {
    //在构造方法中增加加载动画的方法
        Image image = new Image("assets/textures/player.png");
        //这里通过对图片的规划,每一行的动画个数,每个帧的宽度高度,整个动画的跨度时间,跨度几个动画
        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);
        //播放动画
        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;
    }

    /**
     * 改变移动速度 主动改变
     * @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("实体当前状态为 位置=[{}],速度Y=[{}],速度X =[{}]",entity.getPosition(),this.speedY,this.speedX);
    }

    /**
     * 速度减少 被动减速
     * @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;
    }
}

简简单单的启动项目,一个乌贼娘跃然纸上啦!

java播放动画图集 java动画引擎_游戏_03

当然,只到这一步我是不满足的,因为这个乌贼娘在速度为零的时候依然保持着移动动画。进行一点点小小的修改。
很容易想到,简单的添加四个全新的信道。

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;

简单的初始化他们

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);

再简单的判断速度为零时修改为静止信道

@Override
    public void onUpdate(double tpf) {
//        log.info("当前状态下  x速度为=[{}], Y速度为=[{}]  x加速状态为=[{}] Y加速状态为 =[{}]",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 {
                //转变面向
                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;
           }
    }

非常容易啊,啪的一下,这个完整的动画过程就做完了。

当然除了动态素材动画信道的方法,还有一个使用的更多的动画建造器方式
(animationBuilder)
这个方法能自定义很多动画,例如各种变形动画,粒子特效等,也能设置回调用于控制动画。在官方wiki中有详细介绍,这里就不多赘述了。