summaryrefslogtreecommitdiff
path: root/composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation
diff options
context:
space:
mode:
Diffstat (limited to 'composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation')
-rw-r--r--composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationComponents.kt67
-rw-r--r--composeApp/src/commonMain/kotlin/coffee/liz/ecs/animation/AnimationSystem.kt41
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
}
}
}