java - 在 Ashley ECS 中使用有限状态机制作动画播放器

标签 java libgdx game-development entity-component-system

我有一个大型学校项目,正在使用 libGDX 开发一款 Pixel Platformer 游戏。

我大部分时间都使用 ECS,但我在渲染玩家动画时遇到了麻烦,因为我无法区分玩家的状态或至少是攻击状态。

玩家状态等级

让我向您展示一下,我有这个“移动”状态:

public enum PlayerState implements State<PlayerAgent> {

Idle(){
    @Override
    public void update(PlayerAgent agent) {
        agent.moveOnGround();
        if(!agent.isTouchingGround) {
            if (agent.body.getLinearVelocity().y < -0.05)
                agent.stateMachine.changeState(Falling);
            else
                agent.stateMachine.changeState(Jumping);
        }else{
            if (agent.body.getLinearVelocity().x != 0)
                agent.stateMachine.changeState(Walking);
        }
    }
},

Walking() {
    @Override
    public void update(PlayerAgent agent) {
        agent.moveOnGround();
        if(!agent.isTouchingGround) {
            if (agent.body.getLinearVelocity().y < -0.05)
                agent.stateMachine.changeState(Falling);
            else
                agent.stateMachine.changeState(Jumping);
        }else{
            if (agent.body.getLinearVelocity().x == 0)
                agent.stateMachine.changeState(Idle);
        }
    }
},

Jumping() {
    @Override
    public void enter(PlayerAgent agent) {
    }

    @Override
    public void update(PlayerAgent agent) {
        agent.moveOnAir();
        if (agent.body.getLinearVelocity().y < 0)
            agent.stateMachine.changeState(Falling);
        /* else if (agent.jumpOnAir())
            agent.stateMachine.changeState(DoubleJumping);*/
    }
},

DoubleJumping () {
    @Override
    public void update(PlayerAgent agent) {
        agent.moveOnAir();
        if (agent.body.getLinearVelocity().y < 0)
            agent.stateMachine.changeState(Falling);
    }
},

Falling() {

    @Override
    public void update(PlayerAgent agent) {
        agent.moveOnAir();
        if (agent.isTouchingGround) {
            agent.stateMachine.changeState(Idle);
        } else {
            if (agent.stateMachine.getPreviousState() != DoubleJumping) {
                if (agent.jumpOnAir())
                    agent.stateMachine.changeState(DoubleJumping);
            }
        }
    }
};

@Override
public void enter(PlayerAgent agent) {
    // System.out.println(this.toString());
}

@Override
public void update(PlayerAgent agent) {

}

@Override
public void exit(PlayerAgent agent) {
    agent.timer = 0.0f;
}

@Override
public boolean onMessage(PlayerAgent agent, Telegram telegram) {
    return false;
}
}

玩家动画枚举

但是我至少需要这个状态来实现播放器动画:

public enum PlayerAnimations {

 Idle, Walking, Jumping, DoubleJumping, Falling, Attack, JumpAttack, FallingAttack, FallAttack, Hit, Die;

}

在这个打包纹理上您还可以看到更多内容: Player Animations Atlas

PlayerAgent 类:

public class PlayerAgent implements Updateable {

    private Entity player;

    protected Body body;
    private TransformComponent transform;
    SensorCollisionComponent sensors;

    protected static StateMachine<PlayerAgent, PlayerState>  stateMachine;

    public boolean isTouchingGround = true;
    public boolean isTouchingWallLeft = false;
    public boolean isTouchingWallRight = false;

    public static float timer = 0.0f;

    public PlayerAgent(Entity player) {
        this.player = player;
        body = player.getComponent(B2dBodyComponent.class).body;
        transform = player.getComponent(TransformComponent.class);
        sensors = player.getComponent(SensorCollisionComponent.class);

        stateMachine = new DefaultStateMachine<PlayerAgent, PlayerState>(this, PlayerState.Idle);
    }

    @Override
    public void update(float deltaTime) {
        isTouchingGround = (sensors.numFoot > 0);
        isTouchingWallLeft = (sensors.numLeftWall > 0);
        isTouchingWallRight = (sensors.numRightWall > 0);

        stateMachine.update();

        if (!KeyboardController.left && !KeyboardController.right)
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, 0, 0.2f), body.getLinearVelocity().y);

    }


    public static PlayerState getCurrentState(){
        return stateMachine.getCurrentState();
    }
    public static PlayerState getLastState(){
        return stateMachine.getPreviousState();
    }
    public static boolean isInState(PlayerState state){
        return stateMachine.isInState(state);
    }

    public boolean moveOnGround() {
        if (KeyboardController.left) {
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, -3f, 0.1f), body.getLinearVelocity().y);
            transform.flipX = true;
        }
        if (KeyboardController.right){
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, 3f, 0.1f), body.getLinearVelocity().y);
            transform.flipX = false;
        }
        if (KeyboardController.up) {
            body.applyLinearImpulse(0, 2.7f, body.getWorldCenter().x, body.getWorldCenter().y, true);
            KeyboardController.up = false;
        }
            return (KeyboardController.left || KeyboardController.right);
    }

    public boolean moveOnAir(){
        if (KeyboardController.left){
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, -1.5f, 0.1f), body.getLinearVelocity().y);
            transform.flipX = true;
        }if (KeyboardController.right){
            transform.flipX = false;
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, 1.5f, 0.1f), body.getLinearVelocity().y);
        }
            return (KeyboardController.left || KeyboardController.right);
    }

    public boolean jumpOnAir(){
        if (KeyboardController.up) {
            body.applyLinearImpulse(0, 2.5f, body.getWorldCenter().x, body.getWorldCenter().y, true);
            KeyboardController.up = false;
            return true;
        }
        return false;
    }

}

动画代码

动画系统中负责播放器的代码:

PlayerComponent pc = pm.get(entity);
                if (pc.attacking) {

                    switch (PlayerAgent.getCurrentState()) {
                        case Idle:
                        case Walking:
                            if(PlayerAgent.getLastState() == PlayerState.Falling) {
                                key = PlayerAnimations.FallAttack.ordinal();
                                if(ani.animations.get(key).isAnimationFinished(PlayerAgent.timer)) pc.attacking = false;
                            }else
                                key = PlayerAnimations.Attack.ordinal();
                            break;
                        case Jumping:
                        case DoubleJumping:
                            key = PlayerAnimations.JumpAttack.ordinal();
                            break;
                        case Falling:
                            key = PlayerAnimations.FallingAttack.ordinal();
                            break;
                        default:
                            key = PlayerAnimations.Idle.ordinal();
                            break;
                    }
                } else {
                    switch (PlayerAgent.getCurrentState()) {
                        case Idle:
                            key = PlayerAnimations.Idle.ordinal();
                            break;
                        case Walking:
                            key = PlayerAnimations.Walking.ordinal();
                            break;
                        case Jumping:
                            key = PlayerAnimations.Jumping.ordinal();
                            break;
                        case DoubleJumping:
                            key = PlayerAnimations.DoubleJumping.ordinal();
                            break;
                        case Falling:
                            key = PlayerAnimations.Falling.ordinal();
                            break;
                        default:
                            key = PlayerAnimations.Idle.ordinal();
                            break;
                    }

                }
                PlayerAgent.timer += deltaTime;
            }

我正在考虑在状态中设置状态,例如“接地”->“空闲”、“行走”。 但我仍然不知道如何使玩家 AttackStates。

最佳答案

经过一番挖掘,我找到了一个解决方案,至少现在是这样,所以对于任何遇到同样问题的人来说,我是如何解决它的。

PlayerAttackState.java

我首先创建了一个名为 PlayerAttackState 的新攻击状态。

public enum PlayerAttackState implements State<PlayerAgent> {


        NONE(){
            @Override
            public void enter(PlayerAgent agent) {
                agent.attacking = false;
                agent.lastAttack = System.currentTimeMillis();
            }

            @Override
            public void update(PlayerAgent agent) {
                if (KeyboardController.attack && !agent.attacking) {
                    agent.attacking = true;
                    KeyboardController.attack = false;
                    agent.attackStateMachine.changeState(Attacking);
                    //agent.playerComp.attack();
                }
            }
        },

        Attacking(){
            @Override
            public void enter(PlayerAgent agent) {

                agent.timer  = 0f;
                agent.attackCombo++;
                if (agent.attackCombo > 3 || System.currentTimeMillis() - agent.lastAttack > 100) agent.attackCombo = 1;
                if (agent.stateMachine.isInState(PlayerState.Jumping) || agent.stateMachine.isInState(PlayerState.DoubleJumping))
                    agent.attackStateMachine.changeState(AirAttack);

            }

            @Override
            public void update(PlayerAgent agent) {
                super.update(agent);
                if (agent.stateMachine.isInState(PlayerState.Jumping) || agent.stateMachine.isInState(PlayerState.DoubleJumping))
                    agent.attackStateMachine.changeState(AirAttack);

            }
        },

        AirAttack(){
            @Override
            public void enter(PlayerAgent agent) {
                if (agent.attackCombo >= 2) {
                    agent.attackCombo = 1;
                    agent.attackStateMachine.changeState(FallingAttack);
                }
            }

            @Override
            public void update(PlayerAgent agent) {
                super.update(agent);
                if (agent.attackCombo == 3) {
                    agent.attackCombo = 1;
                    agent.attackStateMachine.changeState(FallingAttack);
                }
            }
        },

        FallingAttack(){
            @Override
            public void enter(PlayerAgent agent) {

            }

            @Override
            public void update(PlayerAgent agent) {
                if (agent.stateMachine.isInState(PlayerState.Idle) || agent.stateMachine.isInState(PlayerState.Walking))
                    agent.attackStateMachine.changeState(FallAttack);
                else if (agent.stateMachine.isInState(PlayerState.DoubleJumping))
                    agent.attackStateMachine.changeState(AirAttack);
            }
        },

        FallAttack(){
            @Override
            public void update(PlayerAgent agent) {
                super.update(agent);
            }
        },

    ;

        @Override
        public void enter(PlayerAgent agent) {

        }

        @Override
        public void update(PlayerAgent agent) {
            if (agent.animation.isAnimationFinished(agent.timer))
                agent.attackStateMachine.changeState(NONE);
        }

        @Override
        public void exit(PlayerAgent agent) {
        }

        @Override
        public boolean onMessage(PlayerAgent agent, Telegram telegram) {
            return false;
        }
    }

PlayerAgent.java

因此,在此之后,我需要在 PlayerAgent 类中为其添加一个状态机和一些其他变量。

public class PlayerAgent implements Updateable {

    private Entity player;

    protected Body body;
    private TransformComponent transform;
    public SensorCollisionComponent sensors;
    public PlayerComponent playerComp;
    public AttackComponent attackComp;

    protected static StateMachine<PlayerAgent, PlayerState>  stateMachine;
    protected static StateMachine<PlayerAgent, PlayerAttackState> attackStateMachine;

    public boolean isTouchingGround = true;
    public boolean isTouchingWallLeft = false;
    public boolean isTouchingWallRight = false;
    public static boolean attacking = false;

    public static Animation animation = null;

    public static float timer = 0.0f;
    public static int attackCombo = 0;
    public long lastAttack = 0l;


    public PlayerAgent(Entity player) {
        this.player = player;
        body = player.getComponent(B2dBodyComponent.class).body;
        transform = player.getComponent(TransformComponent.class);
        sensors = player.getComponent(SensorCollisionComponent.class);
        playerComp = player.getComponent(PlayerComponent.class);
        attackComp = player.getComponent(AttackComponent.class);


        stateMachine = new DefaultStateMachine<PlayerAgent, PlayerState>(this, PlayerState.Idle);
        attackStateMachine = new DefaultStateMachine<PlayerAgent, PlayerAttackState>(this, PlayerAttackState.NONE);
    }

    @Override
    public void update(float deltaTime) {

        isTouchingGround = (sensors.numFoot > 0);
        isTouchingWallLeft = (sensors.numLeftWall > 0);
        isTouchingWallRight = (sensors.numRightWall > 0);

        stateMachine.update();
        attackStateMachine.update();

        if (!KeyboardController.left && !KeyboardController.right)
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, 0, 0.2f), body.getLinearVelocity().y);

    }


    public static PlayerState getCurrentState(){
        return stateMachine.getCurrentState();
    }
    public static PlayerAttackState getAttackState(){return attackStateMachine.getCurrentState();}
    public static PlayerState getLastState(){
        return stateMachine.getPreviousState();
    }
    public static boolean isInState(PlayerState state){
        return stateMachine.isInState(state);
    }

    public boolean moveOnGround() {
        if (KeyboardController.left) {
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, -3f, 0.1f), body.getLinearVelocity().y);
            transform.flipX = true;
        }
        if (KeyboardController.right){
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, 3f, 0.1f), body.getLinearVelocity().y);
            transform.flipX = false;
        }
        if (KeyboardController.up) {
            body.applyLinearImpulse(0, 2.7f, body.getWorldCenter().x, body.getWorldCenter().y, true);
            KeyboardController.up = false;
        }
            return (KeyboardController.left || KeyboardController.right);
    }

    public boolean moveOnAir(){
        if (KeyboardController.left){
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, -1.5f, 0.1f), body.getLinearVelocity().y);
            transform.flipX = true;
        }if (KeyboardController.right){
            transform.flipX = false;
            body.setLinearVelocity(MathUtils.lerp(body.getLinearVelocity().x, 1.5f, 0.1f), body.getLinearVelocity().y);
        }
            return (KeyboardController.left || KeyboardController.right);
    }

    public boolean jumpOnAir(){
        if (KeyboardController.up) {
            body.applyLinearImpulse(0, 2.5f, body.getWorldCenter().x, body.getWorldCenter().y, true);
            KeyboardController.up = false;
            return true;
        }
        return false;
    }

}

PlayerAnimations.java

最后,我需要为播放器设置动画,因此我创建了一个名为 PlayerAnimations 的枚举。

public enum PlayerAnimations {

    Idle,
    Walking,
    Jumping, DoubleJumping,
    Falling,
    Attack_1, Attack_2, Attack_3,
    AirAttack_1, AirAttack_2,
    FallingAttack, FallAttack,
    Hit,
    Die

}

AnimationSystem.java

然后使用 AniamtionSystem 渲染它们。

     switch (PlayerAgent.getAttackState()) {
                case NONE:
                    switch (PlayerAgent.getCurrentState()) {
                        case Idle:
                            key = PlayerAnimations.Idle.ordinal();
                            break;
                        case Walking:
                            key = PlayerAnimations.Walking.ordinal();
                            break;
                        case Jumping:
                            key = PlayerAnimations.Jumping.ordinal();
                            break;
                        case DoubleJumping:
                            key = PlayerAnimations.DoubleJumping.ordinal();
                            break;
                        case Falling:
                            key = PlayerAnimations.Falling.ordinal();
                            break;
                        default:
                            key = PlayerAnimations.Idle.ordinal();
                            break;
                    }
                    break;
                case Attacking:
                    switch (PlayerAgent.attackCombo) {
                        case 1:
                            key = PlayerAnimations.Attack_1.ordinal();
                            break;
                        case 2:
                            key = PlayerAnimations.Attack_2.ordinal();
                            break;
                        case 3:
                            key = PlayerAnimations.Attack_3.ordinal();
                            break;
                        default:
                            key = PlayerAnimations.Attack_1.ordinal();
                            System.out.println("Attack Combo Error");
                            break;
                    }
                    break;
                case AirAttack:
                    switch (PlayerAgent.attackCombo) {
                        case 1:
                            key = PlayerAnimations.AirAttack_1.ordinal();
                            break;
                        case 2:
                            key = PlayerAnimations.AirAttack_2.ordinal();
                            break;
                        default:
                            key = PlayerAnimations.AirAttack_1.ordinal();
                            System.out.println("Air Attack Combo Error");
                            break;
                    }
                    break;
                case FallingAttack:
                    key = PlayerAnimations.FallingAttack.ordinal();
                    break;
                case FallAttack:
                    key = PlayerAnimations.FallAttack.ordinal();
                    break;
                default:
                    key = PlayerAnimations.Idle.ordinal();
                    break;
            }
            timer = PlayerAgent.timer;
            PlayerAgent.timer += deltaTime;
            PlayerAgent.animation = ani.animations.get(key);
        }

        tex.region = ani.animations.get(key).getKeyFrame(timer);

希望这可以帮助一些人,再次为我糟糕的英语感到抱歉。如果您有任何疑问,请随时问我。

关于java - 在 Ashley ECS 中使用有限状态机制作动画播放器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52569349/

相关文章:

unity-game-engine - 编辑器循环延迟。是我的游戏有问题吗?

linux - 漂亮的 shell 脚本(菜单、图像、抗锯齿、1080p、颜色、淡入淡出效果等)?

java - 从双表生成 UUID

java - LibGdx 从表中删除 Actor ,然后重新排序

java - 将 Actions.scaleTo() 添加到 LibGDX 中的标签

java - Libgdx 用图像填充屏幕

java - 中断 libgdx 碰撞时的 Action

java - 如何为任务计划程序执行控制台 Java 应用程序?

java - 扫描仪在使用 next() 或 nextFoo() 后跳过 nextLine()?

java - Java 中长时间运行的计划执行器