package coffee.liz.ecs.animation import coffee.liz.ecs.TickedSystem import coffee.liz.ecs.World import kotlin.time.Duration /** * Updates animation playback state for all [Animator] components in [World]. */ class AnimationSystem(animationTickRate: Duration) : TickedSystem(animationTickRate) { override fun update(world: World, ticks: Int) { world.query(Animator::class).forEach { entity -> val animator = entity.get(Animator::class) if (!animator.playing) return@forEach // Get the current animation clip val clip = animator.clips[animator.currentClip] ?: return@forEach // Accumulate elapsed time animator.elapsedTicks += ticks // Advance frames while we have enough elapsed time while (animator.elapsedTicks >= clip.frameTicks) { animator.elapsedTicks -= clip.frameTicks 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.step when (clip.loopMode) { LoopMode.ONCE -> { if (animator.frameIndex >= clip.frameNames.size) { animator.frameIndex = clip.frameNames.size - 1 animator.playing = false } } LoopMode.LOOP -> { if (animator.frameIndex >= clip.frameNames.size) { animator.frameIndex = 0 } else if (animator.frameIndex < 0) { animator.frameIndex = clip.frameNames.size - 1 } } LoopMode.PING_PONG -> { // Go to (endFrame - 1) or (beginFrame + 1) ping ponging if (animator.frameIndex >= clip.frameNames.size) { animator.frameIndex = (clip.frameNames.size - 2).coerceAtLeast(0) animator.direction = AnimationDirection.BACKWARD } else if (animator.frameIndex < 0) { animator.frameIndex = 1.coerceAtMost(clip.frameNames.size - 1) animator.direction = AnimationDirection.FORWARD } } } } }