Player Sprite Animation - UQcsse3200/2026-studio-3 GitHub Wiki

Player Sprite Animation

The battle player is an animated sprite that reacts to combat: it idles, plays an attack animation when the player plays a card that targets enemies, and recoils when it takes damage. This page covers the assets, how the animation is driven, and how to extend it.

Card effect visuals over the player (Block, Heal, Cleanse…) are a separate system β€” see Combat Animation Foundation.

Animations

Animation Frames Frame time Length Play mode Trigger Demo
Idle 7 0.12 s ~0.84 s LOOP On battle start, and whenever a one-shot animation finishes idle
Hurt 6 0.08 s ~0.48 s NORMAL updateHealth with lower health than before hurt
Attack 14 0.06 s ~0.84 s NORMAL playerAttack, once per card that targets enemies attack
  • Hurt never plays on healing β€” the controller compares against the last known health.
  • Attack also plays for debuff-only cards such as Expose, since they target enemies too.

How it works

stateDiagram-v2
    [*] --> Idle : create()
    Idle --> Hurt : updateHealth (health dropped)
    Idle --> Attack : playerAttack
    Hurt --> Idle : animation finished
    Attack --> Idle : animation finished
    Hurt --> Attack : playerAttack
    Attack --> Hurt : updateHealth (health dropped)
Loading
Class Responsibility
ForestGameArea Loads images/player/player.atlas with the other battle atlases
PlayerFactory.createPlayer(int, RunState) Builds the AnimationRenderComponent, registers idle / hurt / attack, adds PlayerAnimationController, and scales the player by PLAYER_RENDER_SCALE (2Γ—). Battle only β€” the map player keeps its static image
PlayerAnimationController Starts idle; listens for updateHealth and playerAttack; returns to idle in update() once hurt/attack has finished
BattleAnimationCoordinator.onEnemyEffects Triggers playerAttack on the player when a card's enemy effects resolve
CombatStatsComponent Triggers updateHealth(health, maxHealth) whenever health changes

Events

Event Sent by Values Effect
updateHealth CombatStatsComponent new health, max health Plays hurt if new health < last known health
playerAttack BattleAnimationCoordinator none Plays attack

playerAttack is deliberately not called attack β€” the player already has a legacy attack event from keyboard/touch input (PlayerActions).

Assets

File Contents
images/player/player.png 2048Γ—1024 sheet, 82 KB
images/player/player.atlas Regions idle (Γ—7), hurt (Γ—6), attack (Γ—14), default
  • Frames are 180Γ—200. The source art was 900Γ—1000, drawn with 10 px "pixels", so it was shrunk by exactly 1/5 with nearest-neighbour scaling β€” no quality loss.
  • Every frame keeps the same canvas size so the character doesn't jitter; the hurt recoil and attack jump are drawn into the frames.
  • Filter is Nearest, Nearest to keep the pixel art sharp.
  • default is required: AnimationRenderComponent.scaleEntity() uses it to size the entity.

Adding or changing an animation

  1. Name the frames <name>_0.png, <name>_1.png, … (indexes start at 0) and add them to the frame folder.
  2. Re-pack with LibGDX TexturePacker (already available through gdx-tools), using this pack.json: { filterMin: Nearest, filterMag: Nearest, stripWhitespaceX: false, stripWhitespaceY: false, paddingX: 2, paddingY: 2 }
  3. Register it in PlayerFactory with animator.addAnimation("<name>", FRAME_DURATION, PlayMode).
  4. Start it from PlayerAnimationController on the relevant event. If it plays once, add it to the return-to-idle check in update().
  5. If the log shows Animation <name> not found in texture atlas, the region name doesn't match.

Testing

PlayerAnimationControllerTest (mocked animator, real CombatStatsComponent starting at 30 HP):

Test Checks
shouldStartIdleOnCreate idle starts on create
shouldPlayHurtWhenDamaged health drop plays hurt
healthNotDecreaseShouldNotTriggerHurtAnimation heal does not play hurt
shouldPlayAttackAnimationWhenAttacking playerAttack plays attack
shouldReturnToIdleAfterHurtFinishes finished hurt returns to idle

Manual checks: Strike β†’ attack; enemy hit β†’ recoil; heal β†’ no recoil; map screen still shows the static player.

Known limitations / future work

  • No death animation (no frames yet) β€” the player keeps its last frame.
  • Frames 15–33 (blink/rest) are prepared but unused β€” candidate for a defend animation.
  • Hurt plays when health drops, which may be slightly before the enemy's attack animation lands.
  • Floating damage/heal numbers would reuse the same updateHealth hook.

Credits

Player sprite frames by Maria. Atlas prepared and integrated by Jay (@Jeyllyrez).

⚠️ **GitHub.com Fallback** ⚠️