Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@

### Added

- **Coin bounce animation**: Coins now bounce upward when collected, providing satisfying
visual feedback with a parabolic arc animation (0.4s, 2 tiles high)

### Fixed

- **Java plugin dependency**: Removed Java plugin as a required dependency to improve
compatibility

### Changed

- **Coordinate calculations**: Reduced code duplication in rendering coordinate
calculations

## [0.2.2] - 2026-04-06

### Added

- **Debug render mode**: Added an optional in-editor debug overlay for game rendering
- **FPS counter**: Show current renderer FPS inside debug mode

Expand Down
12 changes: 10 additions & 2 deletions src/main/kotlin/dev/stillya/vpet/game/WorldUpdate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import dev.stillya.vpet.game.ecs.Physics
import dev.stillya.vpet.game.ecs.SpatialGrid
import dev.stillya.vpet.game.ecs.World
import dev.stillya.vpet.game.ecs.components.Collectible
import dev.stillya.vpet.game.ecs.components.Collecting
import dev.stillya.vpet.game.ecs.components.PhaseState
import dev.stillya.vpet.game.ecs.components.PhysicsState
import dev.stillya.vpet.game.ecs.components.SpriteState
import dev.stillya.vpet.game.ecs.components.Transform
import dev.stillya.vpet.game.ecs.components.Velocity
import dev.stillya.vpet.game.ecs.systems.AnimationSystem
import dev.stillya.vpet.game.ecs.systems.CollectionSystem
import dev.stillya.vpet.game.ecs.systems.CollisionSystem
import dev.stillya.vpet.game.input.InputState
import dev.stillya.vpet.game.physics.PhysicsBody
Expand Down Expand Up @@ -68,9 +70,15 @@ object WorldUpdate {
var scoreGain = 0
for (id in collected) {
val collectible = reg.get<Collectible>(id)
if (collectible != null) scoreGain += collectible.value
reg.markForRemoval(id)
val entityTransform = reg.get<Transform>(id)
if (collectible != null && entityTransform != null) {
scoreGain += collectible.value
reg.add(id, Collecting(startY = entityTransform.y))
reg.remove<Collectible>(id)
}
}

CollectionSystem.updateCollecting(reg, dt)
reg.flushRemovals()

AnimationSystem.updateAnimations(reg, dt)
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/dev/stillya/vpet/game/ecs/EntityRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ class EntityRegistry {
fun <T : Any> has(id: EntityID, type: KClass<T>): Boolean =
components[id]?.containsKey(type) == true

inline fun <reified T : Any> remove(id: EntityID) {
remove(id, T::class)
}

fun <T : Any> remove(id: EntityID, type: KClass<T>) {
components[id]?.remove(type)
}

fun allWith(vararg types: KClass<*>): List<EntityID> =
components.entries
.filter { (_, comps) -> types.all { it in comps } }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dev.stillya.vpet.game.ecs.components

data class Collecting(
val startY: Float,
val elapsed: Float = 0f
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dev.stillya.vpet.game.ecs.systems

import dev.stillya.vpet.game.ecs.EntityRegistry
import dev.stillya.vpet.game.ecs.components.Collecting
import dev.stillya.vpet.game.ecs.components.Transform
import kotlin.math.pow

object CollectionSystem {
private const val BOUNCE_DURATION = 0.4f
private const val BOUNCE_HEIGHT = 2.0f

fun updateCollecting(registry: EntityRegistry, dt: Float) {
val collectingEntities = registry.allWith(Collecting::class, Transform::class)

for (id in collectingEntities) {
val collecting = registry.get<Collecting>(id) ?: continue
val transform = registry.get<Transform>(id) ?: continue

val newElapsed = collecting.elapsed + dt

if (newElapsed >= BOUNCE_DURATION) {
registry.markForRemoval(id)
} else {
val progress = newElapsed / BOUNCE_DURATION
val bounceOffset = BOUNCE_HEIGHT * (1 - (2 * progress - 1).pow(2))

registry.add(id, transform.copy(y = collecting.startY - bounceOffset))
registry.add(id, collecting.copy(elapsed = newElapsed))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ class RenderSystem(
if (frames.isEmpty()) continue

val coinLine = t.y.toTileInt()
if (coinLine < 0) continue

val lineFrac = t.y - coinLine
val baseY = editor.logicalPositionToXY(LogicalPosition(coinLine, 0)).y
val pixelY = baseY + (lineFrac * lineHeight).toInt()
Expand Down
5 changes: 4 additions & 1 deletion src/test/kotlin/dev/stillya/vpet/game/WorldUpdateTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dev.stillya.vpet.game.ecs.Physics
import dev.stillya.vpet.game.ecs.World
import dev.stillya.vpet.game.ecs.components.AnimationComponent
import dev.stillya.vpet.game.ecs.components.Collectible
import dev.stillya.vpet.game.ecs.components.Collecting
import dev.stillya.vpet.game.ecs.components.PhaseState
import dev.stillya.vpet.game.ecs.components.PhysicsState
import dev.stillya.vpet.game.ecs.components.SpriteState
Expand Down Expand Up @@ -131,7 +132,9 @@ class WorldUpdateTest {
val (frame, _) = WorldUpdate.tick(world, InputState(), 0.016f, character, tileMap, 0..10)

assertEquals(initialScore + 1, frame.world.score)
assertTrue("Collected bug should be removed", !frame.world.registry.exists(bug))
assertTrue("Collected bug should still exist during bounce", frame.world.registry.exists(bug))
assertTrue("Collected bug should have Collecting component", frame.world.registry.has<Collecting>(bug))
assertTrue("Collected bug should not have Collectible component", !frame.world.registry.has<Collectible>(bug))
}

@Test
Expand Down
Loading