refactor: remove blinking expiration animations from rotten and cursed apples
This commit is contained in:
1 parent
adda5db2f4
commit
8559159fc0
4 files changed
+7
-17
No files matched your search
@@ -95,9 +95,10 @@ The game operates in one of two scenes ([`Scene`](include/game_state.h#L9-L12)):
|
|||||||
|
|
||||||
### 3.3 Game Elements & Apples
|
### 3.3 Game Elements & Apples
|
||||||
- **Snake Body:** Represented as `std::deque<std::array<int, 3>>` where each entry is `{x, y, direction}`. Alternating segments use regular and small sprite tiles. Head and tail sprites flip dynamically according to direction.
|
- **Snake Body:** Represented as `std::deque<std::array<int, 3>>` where each entry is `{x, y, direction}`. Alternating segments use regular and small sprite tiles. Head and tail sprites flip dynamically according to direction.
|
||||||
|
- **Immediate Head Rotation Feedback:** When the player presses a D-Pad direction while moving, `handle_input_frame()` validates that the input is not a reverse into the neck. If valid, `state.snakeDirection` is immediately updated and `draw_game()` is called on the main thread before the next movement tick. In `draw_snake_head()`, the head sprite tile pointer (`gfx_head_v` vs `gfx_head_h`) and hardware flip flags (`hflip`, `vflip`) are selected directly from `state.snakeDirection` and written to shadow OAM (`oamSub`). On the subsequent 60 FPS VBlank (~16.6 ms), `handle_vblank()` commits shadow OAM to hardware registers via `oamUpdate(&oamSub)`, providing instantaneous visual feedback that the snake has turned to face the new heading before the slower Timer 0 tick (5.0–17.0 Hz) advances grid coordinates via `update_snake()`.
|
||||||
- **Normal Apple (Red):** Spawns if count < 1. Awards +10 score, increases length by 1, plays pickup sound. Flashes/blinks every 4 ticks.
|
- **Normal Apple (Red):** Spawns if count < 1. Awards +10 score, increases length by 1, plays pickup sound. Flashes/blinks every 4 ticks.
|
||||||
- **Rotten Apple (Brown/Green):** Spawns in Hard Mode (periodic chance up to 20 on screen). Decreases length by 1, plays explosion SFX. If length drops below 2, triggers Game Over. Despawns after `BAD_APPLE_LIFETIME_TICKS` (80 ticks); flashes during its last `EVIL_ITEM_BLINK_TICKS` (20 ticks, accelerating to rapid strobe during the final 8 ticks) to signal expiration.
|
- **Rotten Apple (Brown/Green):** Spawns in Hard Mode (periodic chance up to 20 on screen). Decreases length by 1, plays explosion SFX. If length drops below 2, triggers Game Over. Despawns after `BAD_APPLE_LIFETIME_TICKS` (80 ticks).
|
||||||
- **Cursed Apple (Electric Neon Purple & Radioactive Green):** Spawns occasionally. Inverts player steering controls (Up ↔ Down, Left ↔ Right) for `CURSE_DURATION_TICKS` (40 ticks). Rendered using a dedicated 8x8 sprite with a glowing core rune and toxic green horn, using high-contrast dedicated palette entries (`CURSE_PAL_OUTLINE`, `CURSE_PAL_FILL`, `CURSE_PAL_GLOW`, `CURSE_PAL_CORE`, `CURSE_PAL_LEAF`). Its palette dynamically breathes and pulses with radioactive energy every frame at 60 FPS in [`handle_vblank()`](source/main.cpp#L134-L153), and its sprite sways/twitches with mystical instability in [`draw_apples_cursed()`](source/game_render.cpp#L139-L151). Despawns after `CURSED_APPLE_LIFETIME_TICKS` (90 ticks) with the same warning blink, clearing the way for new cursed apples to spawn if previously avoided.
|
- **Cursed Apple (Electric Neon Purple & Radioactive Green):** Spawns occasionally. Inverts player steering controls (Up ↔ Down, Left ↔ Right) for `CURSE_DURATION_TICKS` (40 ticks). Rendered using a dedicated 8x8 sprite with a glowing core rune and toxic green horn, using high-contrast dedicated palette entries (`CURSE_PAL_OUTLINE`, `CURSE_PAL_FILL`, `CURSE_PAL_GLOW`, `CURSE_PAL_CORE`, `CURSE_PAL_LEAF`). Its palette dynamically breathes and pulses with radioactive energy every frame at 60 FPS in [`handle_vblank()`](source/main.cpp#L134-L153), and its sprite sways/twitches with mystical instability in [`draw_apples_cursed()`](source/game_render.cpp#L139-L151). Despawns after `CURSED_APPLE_LIFETIME_TICKS` (90 ticks), clearing the way for new cursed apples to spawn if previously avoided.
|
||||||
- **Reverse-Direction Denial:** Pressing the D-Pad in the exact opposite direction (i.e. into the snake's own neck) is silently denied instead of causing an immediate Game Over. The game computes the would-be next head position (including edge-wrapping) and compares it to the neck segment; if they match, the direction change is rejected and a subtle "denied" thud SFX plays.
|
- **Reverse-Direction Denial:** Pressing the D-Pad in the exact opposite direction (i.e. into the snake's own neck) is silently denied instead of causing an immediate Game Over. The game computes the would-be next head position (including edge-wrapping) and compares it to the neck segment; if they match, the direction change is rejected and a subtle "denied" thud SFX plays.
|
||||||
|
|
||||||
### 3.4 Game Modes & Progressive Speed Mechanics
|
### 3.4 Game Modes & Progressive Speed Mechanics
|
||||||
@@ -193,6 +194,7 @@ The repository includes preconfigured tasks in [`.vscode/tasks.json`](.vscode/ta
|
|||||||
- [`gfx/tilemap.png`](gfx/tilemap.png) + [`gfx/tilemap.grit`](gfx/tilemap.grit) are processed by `grit` into `${CMAKE_CURRENT_BINARY_DIR}/tilemap.s` and `tilemap.h`.
|
- [`gfx/tilemap.png`](gfx/tilemap.png) + [`gfx/tilemap.grit`](gfx/tilemap.grit) are processed by `grit` into `${CMAKE_CURRENT_BINARY_DIR}/tilemap.s` and `tilemap.h`.
|
||||||
- Sprites are referenced by index: tile 1 (segment), 2 (small segment), 3 (head horizontal), 4 (head vertical), 5/6 (apple flash frames), 7/8/9/10 (tail variations), 11 (rotten apple). All 12 sprite tiles are preloaded once into VRAM at startup; directional and blinking variants are rendered via pointer selection and hardware flipping flags without mid-frame DMA transfers.
|
- Sprites are referenced by index: tile 1 (segment), 2 (small segment), 3 (head horizontal), 4 (head vertical), 5/6 (apple flash frames), 7/8/9/10 (tail variations), 11 (rotten apple). All 12 sprite tiles are preloaded once into VRAM at startup; directional and blinking variants are rendered via pointer selection and hardware flipping flags without mid-frame DMA transfers.
|
||||||
- Active OAM sprites are clamped at hardware limit (128) and trailing unused sprites are hidden every frame via `oamClear(&oamSub, oamId, 128 - oamId)`.
|
- Active OAM sprites are clamped at hardware limit (128) and trailing unused sprites are hidden every frame via `oamClear(&oamSub, oamId, 128 - oamId)`.
|
||||||
|
- **Decoupled Steering & Render Flow:** Steering inputs update `state.snakeDirection` and invoke `draw_game()` immediately on the main thread, updating the snake head's sprite pointer and flipping flags in shadow OAM (`oamSub`). Display updates are synchronized via VBlank (`handle_vblank()`), which flushes `oamSub` to hardware registers with `oamUpdate(&oamSub)` at 60 FPS (~16.6 ms) independently of the slower Timer 0 movement ticks (5.0–17.0 Hz).
|
||||||
- Console text HUD rendering in `draw_stats()` is throttled via dirty-flag caching (`HudCache`), eliminating redundant `iprintf` calls on VBlank.
|
- Console text HUD rendering in `draw_stats()` is throttled via dirty-flag caching (`HudCache`), eliminating redundant `iprintf` calls on VBlank.
|
||||||
- **Audio Pipeline:**
|
- **Audio Pipeline:**
|
||||||
- Audio files in [`sounds/`](sounds/) are compiled by `mmutil` into `soundbank.bin` and header `soundbank.h`.
|
- Audio files in [`sounds/`](sounds/) are compiled by `mmutil` into `soundbank.bin` and header `soundbank.h`.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
- **Classic Gameplay with a Twist:**
|
- **Classic Gameplay with a Twist:**
|
||||||
- Classic grid-based snake action wrapped on a 32×24 playfield with edge-wrapping.
|
- Classic grid-based snake action wrapped on a 32×24 playfield with edge-wrapping.
|
||||||
- **Normal Apples:** Boost length and score (+10), with flashing animation and pickup sounds.
|
- **Normal Apples:** Boost length and score (+10), with flashing animation and pickup sounds.
|
||||||
- **Rotten Apples:** Shrink snake length by 1; shrinking below length 2 results in Game Over! Despawn after 80 ticks with a flashing warning animation.
|
- **Rotten Apples:** Shrink snake length by 1; shrinking below length 2 results in Game Over! Despawn after 80 ticks.
|
||||||
- **Cursed Apples:** Invert steering controls (Up ↔ Down, Left ↔ Right) for 40 ticks, requiring quick reflexes and adapted steering. Despawn after 90 ticks if avoided, allowing new curses to spawn later.
|
- **Cursed Apples:** Invert steering controls (Up ↔ Down, Left ↔ Right) for 40 ticks, requiring quick reflexes and adapted steering. Despawn after 90 ticks if avoided, allowing new curses to spawn later.
|
||||||
- **Dual-Screen Layout:**
|
- **Dual-Screen Layout:**
|
||||||
- **Top Screen:** Real-time scoreboard, status alerts (*"CONTROLS INVERTED!"*), pause overlay, and optional debug stats.
|
- **Top Screen:** Real-time scoreboard, status alerts (*"CONTROLS INVERTED!"*), pause overlay, and optional debug stats.
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ const int CURSE_DURATION_TICKS = 40;
|
|||||||
// Evil item lifetimes (in game ticks, so density stays balanced across speed scaling)
|
// Evil item lifetimes (in game ticks, so density stays balanced across speed scaling)
|
||||||
const int BAD_APPLE_LIFETIME_TICKS = 80; // Rotten apple lasts 80 ticks (~8 spawn cycles)
|
const int BAD_APPLE_LIFETIME_TICKS = 80; // Rotten apple lasts 80 ticks (~8 spawn cycles)
|
||||||
const int CURSED_APPLE_LIFETIME_TICKS = 90; // Cursed apple lasts 90 ticks before despawning
|
const int CURSED_APPLE_LIFETIME_TICKS = 90; // Cursed apple lasts 90 ticks before despawning
|
||||||
const int EVIL_ITEM_BLINK_TICKS = 20; // Last 20 ticks: flashes to warn player of imminent despawn
|
|
||||||
const int EVIL_ITEM_FAST_BLINK_TICKS = 8; // Last 8 ticks: rapid strobe before disappearing
|
|
||||||
|
|
||||||
// Cursed apple palette indices in SPRITE_PALETTE_SUB
|
// Cursed apple palette indices in SPRITE_PALETTE_SUB
|
||||||
const uint16_t CURSE_PAL_OUTLINE = 16;
|
const uint16_t CURSE_PAL_OUTLINE = 16;
|
||||||
|
|||||||
+2
-12
@@ -129,14 +129,9 @@ void draw_apples_rotten(const GameState &state, const RenderAssets &assets,
|
|||||||
if (oamId >= 128) {
|
if (oamId >= 128) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool blink = (apple.ticksRemaining <= EVIL_ITEM_BLINK_TICKS);
|
|
||||||
const bool is_hidden =
|
|
||||||
blink && ((apple.ticksRemaining <= EVIL_ITEM_FAST_BLINK_TICKS)
|
|
||||||
? ((state.ticks % 2) == 0)
|
|
||||||
: (((state.ticks / 2) % 2) == 0));
|
|
||||||
oamSet(&oamSub, oamId, apple.x * 8, apple.y * 8, 0, 0, SpriteSize_8x8,
|
oamSet(&oamSub, oamId, apple.x * 8, apple.y * 8, 0, 0, SpriteSize_8x8,
|
||||||
SpriteColorFormat_256Color, assets.gfx_apple_rotten, -1, false,
|
SpriteColorFormat_256Color, assets.gfx_apple_rotten, -1, false,
|
||||||
is_hidden, false, false, false);
|
false, false, false, false);
|
||||||
oamId++;
|
oamId++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,14 +143,9 @@ void draw_apples_cursed(const GameState &state, const RenderAssets &assets,
|
|||||||
if (oamId >= 128) {
|
if (oamId >= 128) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool blink = (apple.ticksRemaining <= EVIL_ITEM_BLINK_TICKS);
|
|
||||||
const bool is_hidden =
|
|
||||||
blink && ((apple.ticksRemaining <= EVIL_ITEM_FAST_BLINK_TICKS)
|
|
||||||
? ((state.ticks % 2) == 0)
|
|
||||||
: (((state.ticks / 2) % 2) == 0));
|
|
||||||
oamSet(&oamSub, oamId, apple.x * 8, apple.y * 8, 0, 0, SpriteSize_8x8,
|
oamSet(&oamSub, oamId, apple.x * 8, apple.y * 8, 0, 0, SpriteSize_8x8,
|
||||||
SpriteColorFormat_256Color, assets.gfx_apple_cursed, -1, false,
|
SpriteColorFormat_256Color, assets.gfx_apple_cursed, -1, false,
|
||||||
is_hidden, hflip, false, false);
|
false, hflip, false, false);
|
||||||
oamId++;
|
oamId++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user