Repository navigation
Player Animations
This feature allows the player to see a visual representation in an aesthetically pleasing manner using the assets from https://szadiart.itch.io/2d-soulslike-character?download which are free to use.
The sprite sheets and atlas's within the LevelGameArea class
private static final String[] entityAtlases = {
"images/ghost.atlas", "images/ghostKing.atlas", "images/gold_coin/gold_coin.atlas",
"images/knight.atlas", "images/LeftKnight.atlas"
};
The assets are then put into the PlayerRenderComponent inside of the Player entity done inside Player Factory
PlayerRenderComponent animator =
new PlayerRenderComponent(
ServiceLocator.getResourceService()
.getAsset("images/knight.atlas", TextureAtlas.class),
ServiceLocator.getResourceService()
.getAsset("images/LeftKnight.atlas", TextureAtlas.class)
);
From this the animations are added to the PlayerRenderComponent, with Loop if the animation is intended to loop or normal if it should stop after going through the animation once.
animator.addAnimation("Idle", 0.1f, Animation.PlayMode.LOOP);
animator.addAnimation("Jump", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("Attacks", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("crouchidle", 0.1f, Animation.PlayMode.LOOP);
animator.addAnimation("Roll", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("Slide", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("Run", 0.1f, Animation.PlayMode.LOOP);
animator.addAnimation("LeftIdle", 0.1f, Animation.PlayMode.LOOP);
animator.addAnimation("LeftJump", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("LeftAttacks", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("Leftcrouchidle", 0.1f, Animation.PlayMode.LOOP);
animator.addAnimation("LeftRoll", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("LeftSlide", 0.1f, Animation.PlayMode.NORMAL);
animator.addAnimation("LeftRun", 0.1f, Animation.PlayMode.LOOP);
player.addComponent(animator).addComponent(new PlayerAnimationController());
The initial idle animation is caused by the following code inside of PlayerFactory:
animator.startAnimation("Idle");
Inside of PlayerAnimation Controller listeners are added for every movement
animator = this.entity.getComponent(PlayerRenderComponent.class);
entity.getEvents().addListener("idle", this::animateIdle);
entity.getEvents().addListener("run", this::animateRun);
entity.getEvents().addListener("attacking", this::animateAttack);
entity.getEvents().addListener("sliding", this::animateSlide);
entity.getEvents().addListener("crouchidle", this::animateCrouch);
entity.getEvents().addListener("jumping", this::animateJump);
entity.getEvents().addListener("rolling", this::animateRoll);
The animate functions contain similar code to the following with Left and Right distinguished to make sure that the direction of the character is clear
void animateIdle(String direction) {
if ("Right".equals(direction)) {
animator.startAnimation("Idle");
} else {
animator.startAnimation("LeftIdle");
}
}
These listeners are called inside of KeyboardPlayerInputComponent whenever the corresponding key is pressed inside of KeyDown.
case Keys.A:
direction = "Left";
walkDirection.add(Vector2Utils.LEFT);
if (crouch) {
entity.getEvents().trigger("crouchidle", direction);
} else {
entity.getEvents().trigger("run", direction);
}
triggerWalkEvent();
return true;
Then if the animation is a looped animation such as run or crouch the following code or similar is used to stop it inside of the keyUp function:
case Keys.A:
walkDirection.sub(Vector2Utils.LEFT);
if (walkDirection.isZero()) {
entity.getEvents().trigger("idle", direction);
}
triggerWalkEvent();
return true;
However if the animation is not looped then the PlayerActions class stops it using an animationtimer function which identifies the direction from a getter function.
private void animationtimer(String direction) {
PlayerRenderComponent animator = entity.getComponent(PlayerRenderComponent.class);
if (animator.isFinished()
&! animator.getCurrentAnimation().equals("crouchidle")
&! animator.getCurrentAnimation().equals("Leftcrouchidle")
) {
entity.getEvents().trigger("idle", direction);
}
}
This function is called inside the update function
@Override
public void update() {
playMovementSound();
if (!dead && (moving || platformerComponent.getJumpingBool())) {
updateSpeed();
}
timerforslide();
String direction = entity.getComponent(KeyboardPlayerInputComponent.class).getDirection();
animationtimer(direction);
}
The Entity is given an initial scale by:
static Texture size = new Texture("images/knight_default.png");
player.setScale(0.75f, (float) size.getHeight() / size.getWidth());
However the proportions of each individual frame is different and thus were kept consistent by PlayerRenderComponent which overides the inherited draw function from the AnimationRenderComponent to ensure the player animations kept consistent proportions.
@Override
protected void draw(SpriteBatch batch) {
if (currentAnimation == null) {
return;
}
TextureRegion region = currentAnimation.getKeyFrame(animationPlayTime);
Vector2 pos = entity.getPosition();
float pixel = 1f / 42f;
float width = pixel * region.getRegionWidth();
float height = pixel * region.getRegionHeight();
batch.draw(region, pos.x, pos.y, width, height);
animationPlayTime += timeSource.getDeltaTime();
}
PlayerRenderComponentTest was created to test the player render component using the same tests as the AnimationRenderComponentTests however accounting for the 3 atlas's required for PlayerRenderComponent and the difference in the draw function to create the sprite.