Reputation: 21
I have a big school project and I'm developing a Pixel Platformer game using libGDX.
I'm using ECS for the most part, but I've been having trouble rendering my player's animations because I can not distinguish the state of the player or at least the attack ones.
Let me just show you, so I have this "move" states:
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;
}
}
But I'll need at least this states for player animation:
public enum PlayerAnimations {
Idle, Walking, Jumping, DoubleJumping, Falling, Attack, JumpAttack, FallingAttack, FallAttack, Hit, Die;
}
And many more has u can see on this packed texture: Player Animations Atlas
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;
}
}
Code in the AnimationSystem that takes care of the player:
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;
}
I was thinking of having states inside a state like Grounded -> Idle, Walking. But I still don't know how I can make the player AttackStates.
Upvotes: 1
Views: 724
Reputation: 21
after some digging i found a solution, at least for now, so for anyone that has the same problem here is how i solved it.
I started by creating a new attack state called 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;
}
}
So after this I needed to add a state machine for it in the PlayerAgent
class and some other variables.
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;
}
}
Finally I needed to set up the animations for the player so i created a enum called PlayerAnimations
.
public enum PlayerAnimations {
Idle,
Walking,
Jumping, DoubleJumping,
Falling,
Attack_1, Attack_2, Attack_3,
AirAttack_1, AirAttack_2,
FallingAttack, FallAttack,
Hit,
Die
}
And then render them using the 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);
Hope this can help some one and, once again, sorry for my poor english. If you have any question feel free to ask me.
Upvotes: 0