diff options
Diffstat (limited to 'composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation')
| -rw-r--r-- | composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationComponents.kt | 67 | ||||
| -rw-r--r-- | composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt | 41 |
2 files changed, 43 insertions, 65 deletions
diff --git a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationComponents.kt b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationComponents.kt index 4289eec..cb3708f 100644 --- a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationComponents.kt +++ b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationComponents.kt @@ -1,85 +1,77 @@ package coffee.liz.ecs.animation import coffee.liz.ecs.Component +import coffee.liz.ecs.Rect import kotlin.jvm.JvmInline /** * Loop modes for animation playback. */ enum class LoopMode { - /** Play once and stop on last frame */ + /** Play once **/ ONCE, - /** Repeat from beginning */ + /** Repeat **/ LOOP, - /** Play forward, then backward, repeat */ + /** Play forward, then backward, repeat **/ PING_PONG } /** - * Type-safe wrapper for animation names. + * Name of an animation clip. */ @JvmInline value class AnimationName(val value: String) /** - * Type-safe wrapper for frame names. + * Name of a frame. */ @JvmInline value class FrameName(val value: String) -/** - * Represents a rectangle region in a sprite sheet. - */ -data class Rect( - val x: Int, - val y: Int, - val width: Int, - val height: Int -) /** - * Defines a single animation clip with frames and playback settings. + * Animation Clip details. */ data class AnimationClip( val frameNames: List<FrameName>, - val frameDuration: Float, // seconds per frame + val frameTicks: Int, val loopMode: LoopMode = LoopMode.LOOP ) /** * Component containing sprite sheet data - the image and frame definitions. - * This is immutable data that can be shared across entities. */ data class SpriteSheet( val imagePath: String, val frames: Map<FrameName, Rect> ) : Component +enum class AnimationDirection(val step: Int) { + /** Play in forward direction **/ + FORWARD(step = 1), + /** Play in reverse direction **/ + BACKWARD(step = -1); +} /** - * Component for animation playback state. - * Contains animation clips and current playback state. + * Current state of an animation. */ data class Animator( val clips: Map<AnimationName, AnimationClip>, var currentClip: AnimationName, var frameIndex: Int = 0, - var elapsed: Float = 0f, + var elapsedTicks: Int = 0, var playing: Boolean = true, - var direction: Int = 1 // 1 for forward, -1 for backward (used in PING_PONG) + var direction: AnimationDirection = AnimationDirection.FORWARD ) : Component { - /** * Play a specific animation clip by name. - * Resets playback state when switching clips. */ fun play(clipName: AnimationName, restart: Boolean = true) { - if (currentClip != clipName || restart) { - currentClip = clipName - frameIndex = 0 - elapsed = 0f - direction = 1 - playing = true - } + clipName.takeIf { currentClip != it || restart } + .run { + currentClip = clipName + reset() + } } /** @@ -103,12 +95,11 @@ data class Animator( val clip = clips[currentClip] ?: return null return clip.frameNames.getOrNull(frameIndex) } -} -/** - * Component for entity position in 2D space. - */ -data class Position( - var x: Float, - var y: Float -) : Component + private fun reset() { + frameIndex = 0 + elapsedTicks = 0 + direction = AnimationDirection.FORWARD + playing = true + } +} diff --git a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt index 7ae405e..7968250 100644 --- a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt +++ b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt @@ -1,35 +1,27 @@ package coffee.liz.ecs.animation -import coffee.liz.ecs.System +import coffee.liz.ecs.TickedSystem import coffee.liz.ecs.World +import kotlin.time.Duration /** - * 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) + * Updates animation playback state for all [Animator] components in [World]. */ -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 +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.elapsed += deltaTime + animator.elapsedTicks += ticks // Advance frames while we have enough elapsed time - while (animator.elapsed >= clip.frameDuration) { - animator.elapsed -= clip.frameDuration + while (animator.elapsedTicks >= clip.frameTicks) { + animator.elapsedTicks -= clip.frameTicks advanceFrame(animator, clip) } } @@ -39,11 +31,10 @@ class AnimationSystem : System { * Advances the animation to the next frame based on loop mode. */ private fun advanceFrame(animator: Animator, clip: AnimationClip) { - animator.frameIndex += animator.direction + animator.frameIndex += animator.direction.step 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 @@ -51,25 +42,21 @@ class AnimationSystem : System { } 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 + // Go to (endFrame - 1) or (beginFrame + 1) ping ponging if (animator.frameIndex >= clip.frameNames.size) { - // Hit the end, reverse direction animator.frameIndex = (clip.frameNames.size - 2).coerceAtLeast(0) - animator.direction = -1 + animator.direction = AnimationDirection.BACKWARD } else if (animator.frameIndex < 0) { - // Hit the beginning, reverse direction animator.frameIndex = 1.coerceAtMost(clip.frameNames.size - 1) - animator.direction = 1 + animator.direction = AnimationDirection.FORWARD } } } |
