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

时间:2018-09-29 14:32:20

标签: java libgdx game-development entity-component-system gdx-ai

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

我大部分时间都在使用ECS,但是由于无法区分玩家或至少是攻击者的状态,我一直在渲染玩家的动画时遇到困难。

玩家状态类

让我告诉你,所以我有以下“移动”状态:

setlocal /?

玩家动画枚举

但是对于播放器动画,我至少需要以下状态:

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

您可以在这个打包的纹理上看到更多的东西: Player Animations Atlas

PlayerAgent类别:

public enum PlayerAnimations {

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

}

动画代码

AnimationSystem中负责播放器的代码:

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

}

我当时正在考虑让状态处于“接地->空闲,行走”状态之内。 但是我仍然不知道如何使玩家进入AttackStates。

1 个答案:

答案 0 :(得分:0)

经过一番挖掘,至少到现在为止,我找到了解决方案,所以对于任何有相同问题的人,这都是我的解决方法。

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

希望这可以帮助一些人,再次抱歉我的英语不好。如果您有任何问题,请随时问我。