Player Sprite Animation - UQcsse3200/2026-studio-3 GitHub Wiki
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.
| 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 | ![]() |
| Hurt | 6 | 0.08 s | ~0.48 s | NORMAL |
updateHealth with lower health than before |
![]() |
| Attack | 14 | 0.06 s | ~0.84 s | NORMAL |
playerAttack, once per card that targets enemies |
![]() |
- 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.
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)
| 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 |
| 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).
| 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, Nearestto keep the pixel art sharp. -
defaultis required:AnimationRenderComponent.scaleEntity()uses it to size the entity.
- Name the frames
<name>_0.png,<name>_1.png, β¦ (indexes start at 0) and add them to the frame folder. - Re-pack with LibGDX TexturePacker (already available through
gdx-tools), using thispack.json:{ filterMin: Nearest, filterMag: Nearest, stripWhitespaceX: false, stripWhitespaceY: false, paddingX: 2, paddingY: 2 } - Register it in
PlayerFactorywithanimator.addAnimation("<name>", FRAME_DURATION, PlayMode). - Start it from
PlayerAnimationControlleron the relevant event. If it plays once, add it to the return-to-idle check inupdate(). - If the log shows
Animation <name> not found in texture atlas, the region name doesn't match.
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.
- 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
updateHealthhook.
Player sprite frames by Maria. Atlas prepared and integrated by Jay (@Jeyllyrez).


