From 64f825465de9fa30c4dfe2707067efdb96110db8 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Thu, 23 Oct 2025 21:59:37 -0700 Subject: Init --- .../coffee/liz/ecs/animation/AnimationSystem.kt | 77 ++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt (limited to 'composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt') diff --git a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt new file mode 100644 index 0000000..7ae405e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt @@ -0,0 +1,77 @@ +package coffee.liz.ecs.animation + +import coffee.liz.ecs.System +import coffee.liz.ecs.World + +/** + * System that updates animation playback state for all entities with an Animator component. + * + * This system: + * - Updates elapsed time for playing animations + * - Advances frame indices based on frame duration + * - Handles loop modes (ONCE, LOOP, PING_PONG) + * - Stops animations when they complete (for ONCE mode) + */ +class AnimationSystem : System { + override fun update(world: World, deltaTime: Float) { + // Query all entities that have both Animator and SpriteSheet + world.query(Animator::class, SpriteSheet::class).forEach { entity -> + val animator = entity.get(Animator::class) ?: return@forEach + + // Skip if animation is not playing + if (!animator.playing) return@forEach + + // Get the current animation clip + val clip = animator.clips[animator.currentClip] ?: return@forEach + + // Accumulate elapsed time + animator.elapsed += deltaTime + + // Advance frames while we have enough elapsed time + while (animator.elapsed >= clip.frameDuration) { + animator.elapsed -= clip.frameDuration + advanceFrame(animator, clip) + } + } + } + + /** + * Advances the animation to the next frame based on loop mode. + */ + private fun advanceFrame(animator: Animator, clip: AnimationClip) { + animator.frameIndex += animator.direction + + when (clip.loopMode) { + LoopMode.ONCE -> { + // Play once and stop on last frame + if (animator.frameIndex >= clip.frameNames.size) { + animator.frameIndex = clip.frameNames.size - 1 + animator.playing = false + } + } + + LoopMode.LOOP -> { + // Loop back to beginning + if (animator.frameIndex >= clip.frameNames.size) { + animator.frameIndex = 0 + } else if (animator.frameIndex < 0) { + // Handle negative wrap (shouldn't happen in LOOP but be safe) + animator.frameIndex = clip.frameNames.size - 1 + } + } + + LoopMode.PING_PONG -> { + // Bounce back and forth + if (animator.frameIndex >= clip.frameNames.size) { + // Hit the end, reverse direction + animator.frameIndex = (clip.frameNames.size - 2).coerceAtLeast(0) + animator.direction = -1 + } else if (animator.frameIndex < 0) { + // Hit the beginning, reverse direction + animator.frameIndex = 1.coerceAtMost(clip.frameNames.size - 1) + animator.direction = 1 + } + } + } + } +} -- cgit v1.2.3-70-g09d2