Implement run mechanic and level progression with top-left HUD indicator
Run (B + direction): - player_logic.c: while holding B + direction with stamina >= 10, each tile takes 8 frames instead of 16 (move_progress += 2, LERP >>4 unchanged) and costs 10 stamina per tile. DAS_REPEAT_RUN=2 chains tiles fluidly. - Falls back silently to normal walk when stamina < 10. Level progression: - New global 'level' (starts at 1). title->game resets to 1; reaching the hatch (victory/Going Deeper) + START increments before engine_init (new maze); defeat + START resets to 1. - Top-left HUD 'L<n>' via 3 sprites (OAM 23-25) from new level.png asset (glyphs L, 0-9), generated by scripts/generate_level.py and converted with png2asset -keep_duplicate_tiles for predictable tile ordering. - VRAM base aligned to an EVEN index (8x16 hardware ignores tile LSB) and placed after the stamina load claim to avoid the shared BG tile block. - update_level_display() called every frame; hides during game over/title. Docs: README, doc/gameplay.md and the technical report updated.
This commit is contained in:
@@ -9,14 +9,15 @@ SCRIPTS_DIR = scripts
|
|||||||
BUILD_DIR = build
|
BUILD_DIR = build
|
||||||
|
|
||||||
# C source files
|
# C source files
|
||||||
SRCS = $(SRC_DIR)/main.c $(SRC_DIR)/engine.c $(SRC_DIR)/globals.c $(SRC_DIR)/maze.c $(SRC_DIR)/sound.c $(SRC_DIR)/render.c $(SRC_DIR)/player_logic.c $(SRC_DIR)/enemy_logic.c $(SRC_DIR)/tiles.c $(SRC_DIR)/player.c $(SRC_DIR)/enemy.c $(SRC_DIR)/gameover.c $(SRC_DIR)/next_level.c $(SRC_DIR)/stamina.c $(SRC_DIR)/title_bg.c
|
SRCS = $(SRC_DIR)/main.c $(SRC_DIR)/engine.c $(SRC_DIR)/globals.c $(SRC_DIR)/maze.c $(SRC_DIR)/sound.c $(SRC_DIR)/render.c $(SRC_DIR)/player_logic.c $(SRC_DIR)/enemy_logic.c $(SRC_DIR)/tiles.c $(SRC_DIR)/player.c $(SRC_DIR)/enemy.c $(SRC_DIR)/gameover.c $(SRC_DIR)/next_level.c $(SRC_DIR)/stamina.c $(SRC_DIR)/level.c $(SRC_DIR)/title_bg.c
|
||||||
|
|
||||||
all: $(BUILD_DIR)/hello_iso.gb $(BUILD_DIR)/test_gameover.gb
|
all: $(BUILD_DIR)/hello_iso.gb $(BUILD_DIR)/test_gameover.gb
|
||||||
|
|
||||||
# Generate image assets
|
# Generate image assets
|
||||||
generate_images: $(SCRIPTS_DIR)/generate_assets.py $(SCRIPTS_DIR)/generate_enemy.py
|
generate_images: $(SCRIPTS_DIR)/generate_assets.py $(SCRIPTS_DIR)/generate_enemy.py $(SCRIPTS_DIR)/generate_level.py
|
||||||
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_assets.py
|
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_assets.py
|
||||||
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_enemy.py
|
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_enemy.py
|
||||||
|
cd $(ASSETS_DIR) && python3 ../$(SCRIPTS_DIR)/generate_level.py
|
||||||
|
|
||||||
# Convert image assets to C source files
|
# Convert image assets to C source files
|
||||||
generate_c_assets: generate_images
|
generate_c_assets: generate_images
|
||||||
@@ -26,6 +27,7 @@ generate_c_assets: generate_images
|
|||||||
$(PNG2ASSET) $(ASSETS_DIR)/gameover.png -c $(SRC_DIR)/gameover.c -bpp 2 -noflip -keep_palette_order
|
$(PNG2ASSET) $(ASSETS_DIR)/gameover.png -c $(SRC_DIR)/gameover.c -bpp 2 -noflip -keep_palette_order
|
||||||
$(PNG2ASSET) $(ASSETS_DIR)/next_level.png -c $(SRC_DIR)/next_level.c -map -bpp 2 -noflip -max_palettes 1
|
$(PNG2ASSET) $(ASSETS_DIR)/next_level.png -c $(SRC_DIR)/next_level.c -map -bpp 2 -noflip -max_palettes 1
|
||||||
$(PNG2ASSET) $(ASSETS_DIR)/stamina.png -c $(SRC_DIR)/stamina.c -bpp 2 -noflip -keep_palette_order
|
$(PNG2ASSET) $(ASSETS_DIR)/stamina.png -c $(SRC_DIR)/stamina.c -bpp 2 -noflip -keep_palette_order
|
||||||
|
$(PNG2ASSET) $(ASSETS_DIR)/level.png -c $(SRC_DIR)/level.c -sw 8 -sh 16 -bpp 2 -noflip -keep_palette_order -keep_duplicate_tiles
|
||||||
$(PNG2ASSET) $(ASSETS_DIR)/title_bg.png -c $(SRC_DIR)/title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
|
$(PNG2ASSET) $(ASSETS_DIR)/title_bg.png -c $(SRC_DIR)/title_bg.c -map -bpp 2 -noflip -keep_palette_order -max_palettes 1
|
||||||
|
|
||||||
# Main ROM target
|
# Main ROM target
|
||||||
@@ -46,4 +48,5 @@ clean:
|
|||||||
rm -f $(SRC_DIR)/gameover.c $(SRC_DIR)/gameover.h
|
rm -f $(SRC_DIR)/gameover.c $(SRC_DIR)/gameover.h
|
||||||
rm -f $(SRC_DIR)/next_level.c $(SRC_DIR)/next_level.h
|
rm -f $(SRC_DIR)/next_level.c $(SRC_DIR)/next_level.h
|
||||||
rm -f $(SRC_DIR)/stamina.c $(SRC_DIR)/stamina.h
|
rm -f $(SRC_DIR)/stamina.c $(SRC_DIR)/stamina.h
|
||||||
|
rm -f $(SRC_DIR)/level.c $(SRC_DIR)/level.h
|
||||||
rm -f $(SRC_DIR)/title_bg.c $(SRC_DIR)/title_bg.h
|
rm -f $(SRC_DIR)/title_bg.c $(SRC_DIR)/title_bg.h
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ Un survival-horror procedurale in prospettiva isometrica per Game Boy (DMG/CGB),
|
|||||||
- **Movimento interpolato (LERP)**: spostamenti fluidi del personaggio e della telecamera su 16 tick a punto fisso (no float).
|
- **Movimento interpolato (LERP)**: spostamenti fluidi del personaggio e della telecamera su 16 tick a punto fisso (no float).
|
||||||
- **Delayed Auto-Shift (DAS)**: controlli alla Tetris — delay iniziale di 12 frame e ripetizione ogni 6 frame per il movimento continuo tenendo premuto il D-Pad.
|
- **Delayed Auto-Shift (DAS)**: controlli alla Tetris — delay iniziale di 12 frame e ripetizione ogni 6 frame per il movimento continuo tenendo premuto il D-Pad.
|
||||||
- **Salto evasivo con Stamina**: A+direzione scavalca il blocco adiacente atterrando 2 tile più in là (la cella intermedia deve essere un muro). Costa 60 punti stamina; la barra si ricarica di 1 punto al secondo.
|
- **Salto evasivo con Stamina**: A+direzione scavalca il blocco adiacente atterrando 2 tile più in là (la cella intermedia deve essere un muro). Costa 60 punti stamina; la barra si ricarica di 1 punto al secondo.
|
||||||
|
- **Corsa con Stamina**: B+direzione fa correre il protagonista: il passo dura 8 frame invece di 16 e consuma 10 stamina per tile, con DAS più rapido per incatenare i tile fluidamente. Se la stamina scende sotto 10 ripiega automaticamente sulla camminata normale.
|
||||||
|
- **Progressione livelli**: si parte dal livello 1; raggiungere la botola fa sprofondare nel livello successivo (livello +1). L'indicatore `L<n>` in alto a sinistra (sprite HUD, come la barra stamina in alto a destra) mostra il livello corrente e si aggiorna ad ogni livello superato. La sconfitta riporta al livello 1.
|
||||||
- **AI del fantasma**: pathfinding greedy con distanza al quadrato (niente sqrt, niente A*), cooldown di 1 secondo tra i passi, hitbox pixel-perfect (12×6 px) per una morte "giusta".
|
- **AI del fantasma**: pathfinding greedy con distanza al quadrato (niente sqrt, niente A*), cooldown di 1 secondo tra i passi, hitbox pixel-perfect (12×6 px) per una morte "giusta".
|
||||||
- **Audio procedurale**: colonna sonora sintetizzata manipolando direttamente i registri APU via VBL interrupt (nessun campione).
|
- **Audio procedurale**: colonna sonora sintetizzata manipolando direttamente i registri APU via VBL interrupt (nessun campione).
|
||||||
- **Schermate a tutto schermo**: copertina 2-bit nativa per il titolo, immagine "Going Deeper" per la vittoria, metasprite "GAME OVER" per la sconfitta.
|
- **Schermate a tutto schermo**: copertina 2-bit nativa per il titolo, immagine "Going Deeper" per la vittoria, metasprite "GAME OVER" per la sconfitta.
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 957 B |
@@ -61,11 +61,17 @@ Camera centrata: `scroll_x = px - 64`, `scroll_y = py - 72` (160×144 display
|
|||||||
- **Fase 3 — botola**: sceglie una cella calpestabile **a sufficiente distanza** dalla partenza `(1,1)` (distanza di Chebyshev ≥ `MIN_GOAL_DIST` = 3) tra **tutte** le celle del labirinto, non più vincolata al bordo sud. Fallback estremo sulla cella calpestabile più lontana in assoluto da `(1,1)` se nessuna cella fosse a sufficienza distante (teoricamente impossibile in un perfect maze 7x7).
|
- **Fase 3 — botola**: sceglie una cella calpestabile **a sufficiente distanza** dalla partenza `(1,1)` (distanza di Chebyshev ≥ `MIN_GOAL_DIST` = 3) tra **tutte** le celle del labirinto, non più vincolata al bordo sud. Fallback estremo sulla cella calpestabile più lontana in assoluto da `(1,1)` se nessuna cella fosse a sufficienza distante (teoricamente impossibile in un perfect maze 7x7).
|
||||||
|
|
||||||
### 3.3 Movimento & DAS (`player_logic.c`)
|
### 3.3 Movimento & DAS (`player_logic.c`)
|
||||||
- **State machine rigida**: durante `is_moving` (16 frame di LERP) ogni input è ignorato → movimento strettamente grid-based stile Zelda/Pokémon.
|
- **State machine rigida**: durante `is_moving` (16 frame di LERP, o 8 in corsa) ogni input è ignorato → movimento strettamente grid-based stile Zelda/Pokémon.
|
||||||
- **LERP** a punto fisso: `px = start_px + ((target_px - start_px) * move_progress) >> 4` (divisione per 16 via shift, no float).
|
- **LERP** a punto fisso: `px = start_px + ((target_px - start_px) * move_progress) >> 4`. In corsa `move_progress` incrementa di 2/frame → il passo dura 8 frame invece di 16.
|
||||||
- **DAS (Delayed Auto Shift)** alla Tetris: `DAS_DELAY=12` frame di attesa iniziale, poi `DAS_REPEAT=6` frame tra ripetizioni. `keys_pressed = keys & ~prev_keys` rileva il fronte di salita.
|
- **DAS (Delayed Auto Shift)** alla Tetris: `DAS_DELAY=12` frame di attesa iniziale, poi `DAS_REPEAT=6` frame tra ripetizioni (camminata); `DAS_REPEAT_RUN=2` mentre si corre per incatenare i tile.
|
||||||
- **Salto evasivo**: A+direzione → atterraggio 2 tile più in là. Condizioni: cella intermedia DEVE essere muro (`maze==0`), cella di arrivo pavimento/vittoria, stamina ≥ 60. Consuma 60 stamina. L'arco parabolico è solo visivo: `y_offset = (move_progress * (16 - move_progress)) >> 2` (apice 16px a frame 8). La logica resta 2D.
|
- **Salto evasivo**: A+direzione → atterraggio 2 tile più in là. Condizioni: cella intermedia DEVE essere muro, cella di arrivo pavimento/vittoria, stamina ≥ 60. Consuma 60 stamina. Arco parabolico solo visivo: `y_offset = (move_progress * (16 - move_progress)) >> 2`.
|
||||||
- **Stamina**: ricarica 1 punto/s (60 frame). Barra UI a 5 segmenti sprite (ID 18–22) in alto a destra, conversione `stamina*40/100` → pixel.
|
- **Corsa**: B+direzione (stamina ≥ 10) → passo di 8 frame, costo 10 stamina/tile, DAS rapido. Se stamina < 10 ripiega su camminata normale senza costo.
|
||||||
|
- **Stamina**: ricarica 1 punto/s. Barra UI a 5 sprite (ID 18–22) in alto a destra.
|
||||||
|
|
||||||
|
### 3.3b Progressione livelli (`globals.c` + `engine.c` + `render.c`)
|
||||||
|
- Variabile globale `level` (parte da 1). Il titolo→gioco la azzera a 1 (`main.c`); raggiungere la botola (vittoria, `game_over==2`) e premere START fa `level++` prima di `engine_init()` (nuovo labirinto); la sconfitta (`game_over==1`) + START fa `level = 1`.
|
||||||
|
- Indicatore HUD `L<n>` in alto a sinistra via 3 sprite (OAM ID 23–25), disegnati dall'asset `level.png` (11 glifi 8x16: 'L', '0'–'9') generato da `scripts/generate_level.py` e convertito con `png2asset -keep_duplicate_tiles` (senza deduplica, così il glifo di ordine i occupa i tile 2i/2i+1 in ordine prevedibile).
|
||||||
|
- **VRAM**: i glifi sono caricati a `LEVEL_SPRITE_BASE = allineamento-pari-di (tiles_TILE_COUNT + stamina_TILE_COUNT*2)` (~204) — base PARI perché in modalità 8x16 l'hardware ignora il LSB dell'indice tile; e disgiunta dal blocco tile del background e dalla stamina (stesso workaround del commit 93deb35). `update_level_display()` viene chiamata ogni frame in `engine_update` e si nasconde da sola quando `game_over` è attivo.
|
||||||
|
|
||||||
### 3.4 Fog of War (`render.c` + `enemy_logic.c`)
|
### 3.4 Fog of War (`render.c` + `enemy_logic.c`)
|
||||||
Distanza di **Chebyshev** `max(|dx|,|dy|)` invece di euclidea (no sqrt, no lookup): quadrato di visibilità 5×5.
|
Distanza di **Chebyshev** `max(|dx|,|dy|)` invece di euclidea (no sqrt, no lookup): quadrato di visibilità 5×5.
|
||||||
|
|||||||
@@ -27,3 +27,16 @@ Condizioni per il salto:
|
|||||||
|
|
||||||
La stamina si ricarica di 1 punto ogni secondo. Eseguire un salto costa 60 punti, disabilitando ulteriori balzi per un minuto intero. Questo lo rende una manovra evasiva salva-vita estrema e non uno strumento da spam abusivo.
|
La stamina si ricarica di 1 punto ogni secondo. Eseguire un salto costa 60 punti, disabilitando ulteriori balzi per un minuto intero. Questo lo rende una manovra evasiva salva-vita estrema e non uno strumento da spam abusivo.
|
||||||
Visivamente, l'altezza del salto non altera la logica (sempre 2D Grid), ma solo il rendering in `update_player_sprite()`, in cui sottraiamo al posizionamento verticale la formula parabolica `x * (16 - x) / 4`.
|
Visivamente, l'altezza del salto non altera la logica (sempre 2D Grid), ma solo il rendering in `update_player_sprite()`, in cui sottraiamo al posizionamento verticale la formula parabolica `x * (16 - x) / 4`.
|
||||||
|
|
||||||
|
## La Corsa e il Costo in Stamina
|
||||||
|
|
||||||
|
Tenendo premuto il tasto `B` assieme a una direzione direzionale, il giocatore si mette a correre:
|
||||||
|
la transizione tra tile dura **8 frame** invece di 16 (l'incremento di `move_progress` raddoppia, mentre la formula LERP a punto fisso `>> 4` resta invariata e raggiunge il target in metà tempo). Ogni tile corso consuma **10 punti Stamina**.
|
||||||
|
Il DAS diventa più rapido (`DAS_REPEAT_RUN = 2`) così da incatenare i tile fluidamente quando si tiene premuto B+direzione.
|
||||||
|
Se la Stamina scende sotto 10, B+direzione **ripiega silenziosamente su camminata normale** (16 frame, 0 costo); la corsa si riattiva da sola appena la stamina torna ≥ 10. La ricarica resta di 1 punto al secondo, quindi la corsa è uno strumento a scatto: da pieno (100) si possono percorrere fino a 10 tile, dopodiché serve tempo per ricaricarla.
|
||||||
|
|
||||||
|
## Progressione dei Livelli
|
||||||
|
|
||||||
|
Il gioco parte dal **livello 1**. Raggiungere la botola (casella traguardo) significa "sprofondare più giù" (*Going Deeper*): alla pressione di START il livello viene **incrementato** e viene generato un nuovo labirinto. La sconfitta (cattura da parte del fantasma) riporta invece al **livello 1**.
|
||||||
|
|
||||||
|
L'indicatore `L<n>` è mostrato in **alto a sinistra** tramite tre sprite hardware (OAM ID 23–25), disegnati dall'asset `level.png` (11 glifi 8x16: 'L', '0'–'9'). Le decine vengono mostrate solo dal livello 10 in poi (sotto, lo sprite decine è spostato fuori schermo). Come la barra stamina (in alto a destra), l'indicatore è a coordinate-schermo fisse e indipendente dallo scroll isometrico, e viene nascosto durante il game over e sul titolo. La base VRAM dei glifi è allineata a un indice **pari** perché in modalità sprite 8x16 l'hardware ignora il bit meno significativo dell'indice tile.
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Standard Game Boy palette (same as generate_assets.py)
|
||||||
|
# 0: White/transparent-for-sprites, 1: Light Gray, 2: Dark Gray, 3: Black
|
||||||
|
PALETTE = [
|
||||||
|
224, 248, 207, # 0
|
||||||
|
134, 192, 108, # 1
|
||||||
|
48, 104, 80, # 2
|
||||||
|
7, 24, 33 # 3
|
||||||
|
] + [0] * (256 * 3 - 12)
|
||||||
|
|
||||||
|
# 5x7 font, '1' = light pixel (on), drawn with light gray (1) on transparent (0).
|
||||||
|
GLYPHS = {
|
||||||
|
' ': [
|
||||||
|
"00000",
|
||||||
|
"00000",
|
||||||
|
"00000",
|
||||||
|
"00000",
|
||||||
|
"00000",
|
||||||
|
"00000",
|
||||||
|
"00000",
|
||||||
|
],
|
||||||
|
'L': [
|
||||||
|
"10000",
|
||||||
|
"10000",
|
||||||
|
"10000",
|
||||||
|
"10000",
|
||||||
|
"10000",
|
||||||
|
"10000",
|
||||||
|
"11111",
|
||||||
|
],
|
||||||
|
'0': [
|
||||||
|
"01110",
|
||||||
|
"10001",
|
||||||
|
"10011",
|
||||||
|
"10101",
|
||||||
|
"11001",
|
||||||
|
"10001",
|
||||||
|
"01110",
|
||||||
|
],
|
||||||
|
'1': [
|
||||||
|
"00100",
|
||||||
|
"01100",
|
||||||
|
"00100",
|
||||||
|
"00100",
|
||||||
|
"00100",
|
||||||
|
"00100",
|
||||||
|
"01110",
|
||||||
|
],
|
||||||
|
'2': [
|
||||||
|
"01110",
|
||||||
|
"10001",
|
||||||
|
"00001",
|
||||||
|
"00010",
|
||||||
|
"00100",
|
||||||
|
"01000",
|
||||||
|
"11111",
|
||||||
|
],
|
||||||
|
'3': [
|
||||||
|
"11110",
|
||||||
|
"00001",
|
||||||
|
"00001",
|
||||||
|
"01110",
|
||||||
|
"00001",
|
||||||
|
"00001",
|
||||||
|
"11110",
|
||||||
|
],
|
||||||
|
'4': [
|
||||||
|
"00010",
|
||||||
|
"00110",
|
||||||
|
"01010",
|
||||||
|
"10010",
|
||||||
|
"11111",
|
||||||
|
"00010",
|
||||||
|
"00010",
|
||||||
|
],
|
||||||
|
'5': [
|
||||||
|
"11111",
|
||||||
|
"10000",
|
||||||
|
"11110",
|
||||||
|
"00001",
|
||||||
|
"00001",
|
||||||
|
"10001",
|
||||||
|
"01110",
|
||||||
|
],
|
||||||
|
'6': [
|
||||||
|
"00110",
|
||||||
|
"01000",
|
||||||
|
"10000",
|
||||||
|
"11110",
|
||||||
|
"10001",
|
||||||
|
"10001",
|
||||||
|
"01110",
|
||||||
|
],
|
||||||
|
'7': [
|
||||||
|
"11111",
|
||||||
|
"00001",
|
||||||
|
"00010",
|
||||||
|
"00100",
|
||||||
|
"01000",
|
||||||
|
"10000",
|
||||||
|
"10000",
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
"01110",
|
||||||
|
"10001",
|
||||||
|
"10001",
|
||||||
|
"01110",
|
||||||
|
"10001",
|
||||||
|
"10001",
|
||||||
|
"01110",
|
||||||
|
],
|
||||||
|
'9': [
|
||||||
|
"01110",
|
||||||
|
"10001",
|
||||||
|
"10001",
|
||||||
|
"01111",
|
||||||
|
"00001",
|
||||||
|
"00010",
|
||||||
|
"01100",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Glyph order in the sprite sheet (top -> bottom). 11 glyphs, each 8x16 = 2 tiles.
|
||||||
|
# Index i (0-based) occupies tiles (2i, 2i+1) in level_tiles thanks to -keep_duplicate_tiles.
|
||||||
|
# 0: 'L', 1..10: '0'..'9'. (No blank glyph: hidden digits are moved offscreen instead.)
|
||||||
|
ORDER = ['L', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 8x16 per glyph, stacked vertically -> 8 wide x (16*11)=176 tall
|
||||||
|
img = Image.new("P", (8, 176), 0) # transparent background
|
||||||
|
img.putpalette(PALETTE)
|
||||||
|
|
||||||
|
for gi, name in enumerate(ORDER):
|
||||||
|
glyph = GLYPHS[name]
|
||||||
|
y_base = gi * 16
|
||||||
|
# Place the 5x7 glyph in the top rows (0..6), columns 1..5.
|
||||||
|
for row in range(7):
|
||||||
|
line = glyph[row]
|
||||||
|
for col in range(5):
|
||||||
|
if line[col] == '1':
|
||||||
|
img.putpixel((1 + col, y_base + row), 1) # light gray
|
||||||
|
# Rows 7..15 stay transparent (0)
|
||||||
|
|
||||||
|
img.save("level.png")
|
||||||
|
print("Generated level.png (11 glyphs: L, 0-9)")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "gameover.h"
|
#include "gameover.h"
|
||||||
#include "next_level.h"
|
#include "next_level.h"
|
||||||
#include "stamina.h"
|
#include "stamina.h"
|
||||||
|
#include "level.h"
|
||||||
#include "title_bg.h"
|
#include "title_bg.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,6 +97,8 @@ void engine_init(void) {
|
|||||||
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT, gameover_TILE_COUNT, gameover_tiles);
|
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT, gameover_TILE_COUNT, gameover_tiles);
|
||||||
// Load stamina_tiles AFTER the background tiles to prevent VRAM overlap in the shared block (indices > 127)
|
// Load stamina_tiles AFTER the background tiles to prevent VRAM overlap in the shared block (indices > 127)
|
||||||
set_sprite_data(tiles_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles);
|
set_sprite_data(tiles_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles);
|
||||||
|
// Load the level HUD glyph tiles right after stamina (blank, 'L', '0'..'9').
|
||||||
|
set_sprite_data(LEVEL_SPRITE_BASE, level_TILE_COUNT, level_tiles);
|
||||||
|
|
||||||
// SPAWN DEL GIOCATORE
|
// SPAWN DEL GIOCATORE
|
||||||
player_lx = 1;
|
player_lx = 1;
|
||||||
@@ -143,11 +146,13 @@ void engine_init(void) {
|
|||||||
stamina_recharge_timer = 0;
|
stamina_recharge_timer = 0;
|
||||||
is_jumping = 0;
|
is_jumping = 0;
|
||||||
is_moving = 0;
|
is_moving = 0;
|
||||||
|
is_running = 0;
|
||||||
|
|
||||||
// Richiama render.c per forzare un disegno iniziale della scena
|
// Richiama render.c per forzare un disegno iniziale della scena
|
||||||
draw_map(player_lx, player_ly);
|
draw_map(player_lx, player_ly);
|
||||||
update_camera();
|
update_camera();
|
||||||
update_player_sprite();
|
update_player_sprite();
|
||||||
|
update_level_display(); // Mostra l'indicatore livello (top-left)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,6 +161,10 @@ void engine_init(void) {
|
|||||||
*/
|
*/
|
||||||
void engine_update(uint8_t keys, uint8_t prev_keys) {
|
void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||||
|
|
||||||
|
// Indicatore livello: aggiornato ogni frame. Si nasconde da solo quando
|
||||||
|
// `game_over` e' attivo (sconfitta/vittoria) grazie al check interno.
|
||||||
|
update_level_display();
|
||||||
|
|
||||||
// --- GESTIONE STATO DI FINE GIOCO (Sconfitta o Vittoria) ---
|
// --- GESTIONE STATO DI FINE GIOCO (Sconfitta o Vittoria) ---
|
||||||
if (game_over) {
|
if (game_over) {
|
||||||
if (game_over_timer > 0) {
|
if (game_over_timer > 0) {
|
||||||
@@ -217,6 +226,12 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
|
|||||||
// Attende la pressione di START per riavviare
|
// Attende la pressione di START per riavviare
|
||||||
if ((keys & J_START) && !(prev_keys & J_START)) {
|
if ((keys & J_START) && !(prev_keys & J_START)) {
|
||||||
// Nasconde la grafica testuale e ricarica tutto l'engine
|
// Nasconde la grafica testuale e ricarica tutto l'engine
|
||||||
|
// Vittoria (Going Deeper) -> livello successivo; Sconfitta -> nuova partita dal livello 1.
|
||||||
|
if (game_over == 2) {
|
||||||
|
level++;
|
||||||
|
} else {
|
||||||
|
level = 1;
|
||||||
|
}
|
||||||
move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 0, 0);
|
move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 0, 0);
|
||||||
SHOW_SPRITES;
|
SHOW_SPRITES;
|
||||||
engine_init();
|
engine_init();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ uint8_t stairs_ly = 0;
|
|||||||
|
|
||||||
uint8_t is_moving = 0;
|
uint8_t is_moving = 0;
|
||||||
uint8_t is_jumping = 0;
|
uint8_t is_jumping = 0;
|
||||||
|
uint8_t is_running = 0;
|
||||||
uint8_t move_progress = 0;
|
uint8_t move_progress = 0;
|
||||||
int8_t start_lx, start_ly;
|
int8_t start_lx, start_ly;
|
||||||
int8_t target_lx, target_ly;
|
int8_t target_lx, target_ly;
|
||||||
@@ -27,6 +28,8 @@ int16_t target_px, target_py;
|
|||||||
uint8_t stamina = 100;
|
uint8_t stamina = 100;
|
||||||
uint8_t stamina_recharge_timer = 0;
|
uint8_t stamina_recharge_timer = 0;
|
||||||
|
|
||||||
|
uint8_t level = 1;
|
||||||
|
|
||||||
uint8_t enemy_lx = 0;
|
uint8_t enemy_lx = 0;
|
||||||
uint8_t enemy_ly = 0;
|
uint8_t enemy_ly = 0;
|
||||||
uint8_t enemy_is_moving = 0;
|
uint8_t enemy_is_moving = 0;
|
||||||
|
|||||||
+6
-1
@@ -47,6 +47,7 @@ extern uint8_t player_dir; // 0=DR, 1=DL, 2=UL, 3=UR
|
|||||||
// Shared variables for interpolated movement (walking/jumping)
|
// Shared variables for interpolated movement (walking/jumping)
|
||||||
extern uint8_t is_moving;
|
extern uint8_t is_moving;
|
||||||
extern uint8_t is_jumping;
|
extern uint8_t is_jumping;
|
||||||
|
extern uint8_t is_running; // B+direction: step in 8 frames instead of 16, costs 10 stamina/tile
|
||||||
extern uint8_t move_progress; // 0 to 16, tracks the sub-tile animation progress
|
extern uint8_t move_progress; // 0 to 16, tracks the sub-tile animation progress
|
||||||
extern int8_t start_lx, start_ly;
|
extern int8_t start_lx, start_ly;
|
||||||
extern int8_t target_lx, target_ly;
|
extern int8_t target_lx, target_ly;
|
||||||
@@ -54,10 +55,14 @@ extern int16_t start_px, start_py;
|
|||||||
extern int16_t target_px, target_py;
|
extern int16_t target_px, target_py;
|
||||||
|
|
||||||
// --- Stamina System ---
|
// --- Stamina System ---
|
||||||
// Stamina powers the jump mechanic. 100 = full. Recharges over time.
|
// Stamina powers the jump/run mechanic. 100 = full. Recharges over time.
|
||||||
extern uint8_t stamina;
|
extern uint8_t stamina;
|
||||||
extern uint8_t stamina_recharge_timer;
|
extern uint8_t stamina_recharge_timer;
|
||||||
|
|
||||||
|
// --- Level Progression ---
|
||||||
|
// Current level (starts at 1, increments each time the hatch is reached).
|
||||||
|
extern uint8_t level;
|
||||||
|
|
||||||
// --- Enemy State ---
|
// --- Enemy State ---
|
||||||
extern uint8_t enemy_lx;
|
extern uint8_t enemy_lx;
|
||||||
extern uint8_t enemy_ly;
|
extern uint8_t enemy_ly;
|
||||||
|
|||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
//AUTOGENERATED FILE FROM png2asset
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <gbdk/platform.h>
|
||||||
|
#include <gbdk/metasprites.h>
|
||||||
|
|
||||||
|
BANKREF(level)
|
||||||
|
|
||||||
|
const palette_color_t level_palettes[32] = {
|
||||||
|
RGB8(224,248,207), RGB8(134,192,108), RGB8( 48,104, 80), RGB8( 7, 24, 33)
|
||||||
|
,
|
||||||
|
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
|
||||||
|
,
|
||||||
|
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
|
||||||
|
,
|
||||||
|
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
|
||||||
|
,
|
||||||
|
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
|
||||||
|
,
|
||||||
|
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
|
||||||
|
,
|
||||||
|
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
|
||||||
|
,
|
||||||
|
RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8_t level_tiles[352] = {
|
||||||
|
0x40,0x00,0x40,0x00,
|
||||||
|
0x40,0x00,0x40,0x00,
|
||||||
|
0x40,0x00,0x40,0x00,
|
||||||
|
0x7c,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x38,0x00,0x44,0x00,
|
||||||
|
0x4c,0x00,0x54,0x00,
|
||||||
|
0x64,0x00,0x44,0x00,
|
||||||
|
0x38,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x10,0x00,0x30,0x00,
|
||||||
|
0x10,0x00,0x10,0x00,
|
||||||
|
0x10,0x00,0x10,0x00,
|
||||||
|
0x38,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x38,0x00,0x44,0x00,
|
||||||
|
0x04,0x00,0x08,0x00,
|
||||||
|
0x10,0x00,0x20,0x00,
|
||||||
|
0x7c,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x78,0x00,0x04,0x00,
|
||||||
|
0x04,0x00,0x38,0x00,
|
||||||
|
0x04,0x00,0x04,0x00,
|
||||||
|
0x78,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x08,0x00,0x18,0x00,
|
||||||
|
0x28,0x00,0x48,0x00,
|
||||||
|
0x7c,0x00,0x08,0x00,
|
||||||
|
0x08,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x7c,0x00,0x40,0x00,
|
||||||
|
0x78,0x00,0x04,0x00,
|
||||||
|
0x04,0x00,0x44,0x00,
|
||||||
|
0x38,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x18,0x00,0x20,0x00,
|
||||||
|
0x40,0x00,0x78,0x00,
|
||||||
|
0x44,0x00,0x44,0x00,
|
||||||
|
0x38,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x7c,0x00,0x04,0x00,
|
||||||
|
0x08,0x00,0x10,0x00,
|
||||||
|
0x20,0x00,0x40,0x00,
|
||||||
|
0x40,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x38,0x00,0x44,0x00,
|
||||||
|
0x44,0x00,0x38,0x00,
|
||||||
|
0x44,0x00,0x44,0x00,
|
||||||
|
0x38,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
0x38,0x00,0x44,0x00,
|
||||||
|
0x44,0x00,0x3c,0x00,
|
||||||
|
0x04,0x00,0x08,0x00,
|
||||||
|
0x30,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,
|
||||||
|
0x00,0x00,0x00,0x00
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite0[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 0, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite1[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 2, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite2[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 4, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite3[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 6, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite4[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 8, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite5[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 10, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite6[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 12, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite7[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 14, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite8[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 16, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite9[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 18, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t level_metasprite10[] = {
|
||||||
|
METASPR_ITEM(-8, -4, 20, S_PAL(0)),
|
||||||
|
METASPR_TERM
|
||||||
|
};
|
||||||
|
|
||||||
|
const metasprite_t* const level_metasprites[11] = {
|
||||||
|
level_metasprite0, level_metasprite1, level_metasprite2, level_metasprite3, level_metasprite4, level_metasprite5, level_metasprite6, level_metasprite7, level_metasprite8, level_metasprite9, level_metasprite10
|
||||||
|
};
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
//AUTOGENERATED FILE FROM png2asset
|
||||||
|
#ifndef METASPRITE_level_H
|
||||||
|
#define METASPRITE_level_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <gbdk/platform.h>
|
||||||
|
#include <gbdk/metasprites.h>
|
||||||
|
|
||||||
|
#define level_TILE_ORIGIN 0
|
||||||
|
#define level_TILE_W 8
|
||||||
|
#define level_TILE_H 16
|
||||||
|
#define level_WIDTH 8
|
||||||
|
#define level_HEIGHT 16
|
||||||
|
#define level_TILE_COUNT 22
|
||||||
|
#define level_PALETTE_COUNT 8
|
||||||
|
#define level_COLORS_PER_PALETTE 4
|
||||||
|
#define level_TOTAL_COLORS 32
|
||||||
|
#define level_PIVOT_X 4
|
||||||
|
#define level_PIVOT_Y 8
|
||||||
|
#define level_PIVOT_W 8
|
||||||
|
#define level_PIVOT_H 16
|
||||||
|
extern const metasprite_t* const level_metasprites[11];
|
||||||
|
|
||||||
|
BANKREF_EXTERN(level)
|
||||||
|
|
||||||
|
extern const palette_color_t level_palettes[32];
|
||||||
|
extern const uint8_t level_tiles[352];
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <gb/gb.h>
|
#include <gb/gb.h>
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
uint8_t app_state = 0; // 0 = title, 1 = game
|
uint8_t app_state = 0; // 0 = title, 1 = game
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ void main(void) {
|
|||||||
title_update(keys, prev_keys);
|
title_update(keys, prev_keys);
|
||||||
if ((keys & J_START) && !(prev_keys & J_START)) {
|
if ((keys & J_START) && !(prev_keys & J_START)) {
|
||||||
app_state = 1;
|
app_state = 1;
|
||||||
|
level = 1; // Nuova partita dal livello 1
|
||||||
engine_init();
|
engine_init();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+26
-4
@@ -9,6 +9,9 @@ static uint8_t das_timer = 0;
|
|||||||
static uint8_t das_active = 0;
|
static uint8_t das_active = 0;
|
||||||
#define DAS_DELAY 12
|
#define DAS_DELAY 12
|
||||||
#define DAS_REPEAT 6
|
#define DAS_REPEAT 6
|
||||||
|
#define DAS_REPEAT_RUN 2 // DAS piu' rapido mentre si corre (il passo di corsa dura 8 frame)
|
||||||
|
#define RUN_STAMINA_COST 10
|
||||||
|
#define RUN_MIN_STAMINA 10
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gestisce la logica di movimento del giocatore, i salti e le collisioni.
|
* Gestisce la logica di movimento del giocatore, i salti e le collisioni.
|
||||||
@@ -23,6 +26,10 @@ static uint8_t das_active = 0;
|
|||||||
* 3. Meccanica del Salto: Premendo A+Direzione, il giocatore può saltare *esattamente* 2 celle
|
* 3. Meccanica del Salto: Premendo A+Direzione, il giocatore può saltare *esattamente* 2 celle
|
||||||
* più in là. Il codice verifica se la cella intermedia (+1) è un ostacolo (0) e se
|
* più in là. Il codice verifica se la cella intermedia (+1) è un ostacolo (0) e se
|
||||||
* la cella di atterraggio (+2) è calpestabile. Consuma 60 di stamina per bilanciare il vantaggio tattico.
|
* la cella di atterraggio (+2) è calpestabile. Consuma 60 di stamina per bilanciare il vantaggio tattico.
|
||||||
|
* 4. Meccanica della Corsa: Premendo B+Direzione (con stamina >= 10) il giocatore si mette a correre:
|
||||||
|
* il passo dura 8 frame invece di 16 (move_progress incrementato di 2) e consuma 10 stamina per tile.
|
||||||
|
* Il DAS diventa piu' rapido (DAS_REPEAT_RUN) per incatenare i tile fluidamente. Se la stamina
|
||||||
|
* non basta, ripiega silenziosamente su camminata normale senza consumo.
|
||||||
*/
|
*/
|
||||||
void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
||||||
// --- Gestione della Stamina ---
|
// --- Gestione della Stamina ---
|
||||||
@@ -39,7 +46,9 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
|||||||
// --- State Machine: Transizione Animazione ---
|
// --- State Machine: Transizione Animazione ---
|
||||||
// Se siamo nel mezzo di uno spostamento tra tile, calcoliamo i pixel interpolati
|
// Se siamo nel mezzo di uno spostamento tra tile, calcoliamo i pixel interpolati
|
||||||
if (is_moving) {
|
if (is_moving) {
|
||||||
move_progress++;
|
// Corsa: il passo dura 8 frame (incremento 2) invece di 16 (incremento 1).
|
||||||
|
// La formula LERP resta a punto fisso (>>4 == /16): move_progress raggiunge 16 in meta' tempo.
|
||||||
|
move_progress += is_running ? 2 : 1;
|
||||||
|
|
||||||
// Interpolazione lineare (LERP) da start a target
|
// Interpolazione lineare (LERP) da start a target
|
||||||
int16_t px = start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4); // >> 4 equivale a / 16
|
int16_t px = start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4); // >> 4 equivale a / 16
|
||||||
@@ -62,6 +71,7 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
|||||||
if (move_progress == 16) {
|
if (move_progress == 16) {
|
||||||
is_moving = 0;
|
is_moving = 0;
|
||||||
is_jumping = 0;
|
is_jumping = 0;
|
||||||
|
is_running = 0;
|
||||||
player_lx = target_lx;
|
player_lx = target_lx;
|
||||||
player_ly = target_ly;
|
player_ly = target_ly;
|
||||||
|
|
||||||
@@ -91,9 +101,11 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
|||||||
|
|
||||||
// --- Lettura Controlli (Polling) con DAS ---
|
// --- Lettura Controlli (Polling) con DAS ---
|
||||||
uint8_t keys_pressed = keys & ~prev_keys; // Tasti premuti ESATTAMENTE in questo frame
|
uint8_t keys_pressed = keys & ~prev_keys; // Tasti premuti ESATTAMENTE in questo frame
|
||||||
|
// La corsa (B tenuto + stamina) usa un DAS piu' corto per incatenare i tile fluidamente.
|
||||||
|
uint8_t wants_run = (keys & J_B) && (stamina >= RUN_MIN_STAMINA);
|
||||||
|
|
||||||
if (keys_pressed) {
|
if (keys_pressed) {
|
||||||
das_timer = DAS_DELAY;
|
das_timer = wants_run ? DAS_REPEAT_RUN : DAS_DELAY;
|
||||||
das_active = 1;
|
das_active = 1;
|
||||||
} else if (keys && das_active) {
|
} else if (keys && das_active) {
|
||||||
// Se si tiene premuto un tasto...
|
// Se si tiene premuto un tasto...
|
||||||
@@ -102,7 +114,7 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
|||||||
}
|
}
|
||||||
// Se il timer arriva a 0, emuliamo una pressione ripetuta
|
// Se il timer arriva a 0, emuliamo una pressione ripetuta
|
||||||
if (das_timer == 0 && !is_moving) {
|
if (das_timer == 0 && !is_moving) {
|
||||||
das_timer = DAS_REPEAT;
|
das_timer = wants_run ? DAS_REPEAT_RUN : DAS_REPEAT;
|
||||||
keys_pressed = keys;
|
keys_pressed = keys;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -168,7 +180,7 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
|||||||
// Se le condizioni di salto falliscono, aggiorna semplicemente il frame per far girare il personaggio
|
// Se le condizioni di salto falliscono, aggiorna semplicemente il frame per far girare il personaggio
|
||||||
update_player_sprite();
|
update_player_sprite();
|
||||||
}
|
}
|
||||||
// 2. Movimento standard (Camminata)
|
// 2. Movimento standard (Camminata o Corsa)
|
||||||
else {
|
else {
|
||||||
int8_t new_lx = player_lx + move_lx;
|
int8_t new_lx = player_lx + move_lx;
|
||||||
int8_t new_ly = player_ly + move_ly;
|
int8_t new_ly = player_ly + move_ly;
|
||||||
@@ -178,6 +190,16 @@ void update_player_movement(uint8_t keys, uint8_t prev_keys) {
|
|||||||
if (maze[new_ly][new_lx] == 1 || maze[new_ly][new_lx] == 2) {
|
if (maze[new_ly][new_lx] == 1 || maze[new_ly][new_lx] == 2) {
|
||||||
is_moving = 1;
|
is_moving = 1;
|
||||||
move_progress = 0;
|
move_progress = 0;
|
||||||
|
// Corsa: B + direzione con stamina sufficiente. Il passo dura 8 frame
|
||||||
|
// (vedi l'incremento di move_progress sopra) e costa 10 stamina per tile.
|
||||||
|
// Se la stamina non basta, ripiega silenziosamente su camminata normale.
|
||||||
|
if ((keys & J_B) && stamina >= RUN_MIN_STAMINA) {
|
||||||
|
is_running = 1;
|
||||||
|
stamina -= RUN_STAMINA_COST; // 10 stamina per tile (guardia sopra: stamina >= 10)
|
||||||
|
update_stamina_display();
|
||||||
|
} else {
|
||||||
|
is_running = 0;
|
||||||
|
}
|
||||||
start_lx = player_lx;
|
start_lx = player_lx;
|
||||||
start_ly = player_ly;
|
start_ly = player_ly;
|
||||||
target_lx = new_lx;
|
target_lx = new_lx;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "enemy.h"
|
#include "enemy.h"
|
||||||
#include "gameover.h"
|
#include "gameover.h"
|
||||||
#include "stamina.h"
|
#include "stamina.h"
|
||||||
|
#include "level.h"
|
||||||
#include "maze.h"
|
#include "maze.h"
|
||||||
|
|
||||||
uint8_t get_tile_state(int8_t cx, int8_t cy, int8_t lx, int8_t ly) {
|
uint8_t get_tile_state(int8_t cx, int8_t cy, int8_t lx, int8_t ly) {
|
||||||
@@ -77,6 +78,54 @@ void update_stamina_display(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggiorna l'indicatore del livello (HUD in alto a sinistra).
|
||||||
|
*
|
||||||
|
* Sviluppo & Scelte Architetturali:
|
||||||
|
* 1. Come la barra stamina, usiamo sprite hardware (ID 23..25) perche' il background
|
||||||
|
* e' scrollato isometricamente e non e' adatto a un HUD fisso. Gli sprite sono
|
||||||
|
* coordinate-schermo fisse, indipendenti dallo scroll.
|
||||||
|
* 2. L'asset `level.png` contiene 12 glifi 8x16 (blank, 'L', '0'..'9'), caricati in
|
||||||
|
* VRAM sprite subito dopo i tile della stamina (base = tiles_TILE_COUNT +
|
||||||
|
* stamina_TILE_COUNT) per evitare sovrapposizioni con i tile del background
|
||||||
|
* (stesso workaround del commit 93deb35 per la VRAM condivisa).
|
||||||
|
* 3. Si mostrano 3 sprite: 'L', decine (blank se livello < 10), unita'.
|
||||||
|
* Viene nascosto durante il game over (sconfitta/vittoria) e sul titolo.
|
||||||
|
*/
|
||||||
|
#define LEVEL_HUD_SPRITE 23 // primo OAM ID del livello (dopo stamina 18..22)
|
||||||
|
|
||||||
|
void update_level_display(void) {
|
||||||
|
// Nascondi durante il game over (sconfitta o vittoria/Going Deeper).
|
||||||
|
if (game_over) {
|
||||||
|
for (uint8_t i = 0; i < 3; i++) {
|
||||||
|
move_sprite(LEVEL_HUD_SPRITE + i, 0, 0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t base = LEVEL_SPRITE_BASE;
|
||||||
|
uint8_t tens = (level / 10) % 10;
|
||||||
|
uint8_t ones = level % 10;
|
||||||
|
|
||||||
|
// Mappatura glifi in level.png (11 glifi, ognuno 8x16 = 2 tile, in ordine grazie
|
||||||
|
// a -keep_duplicate_tiles). Indice di ordine i -> tile (base + 2*i):
|
||||||
|
// 0 = 'L', 1..10 = '0'..'9'. Quindi la cifra d -> indice (1+d) -> tile base + (1+d)*2.
|
||||||
|
set_sprite_tile(LEVEL_HUD_SPRITE + 0, base + 0 * 2); // 'L' (ordine 0)
|
||||||
|
set_sprite_tile(LEVEL_HUD_SPRITE + 2, base + (1 + ones) * 2); // unita' (cifra ones)
|
||||||
|
|
||||||
|
// Posizionamento in alto a sinistra (offset hardware +8 x, +16 y).
|
||||||
|
move_sprite(LEVEL_HUD_SPRITE + 0, 8, 16); // 'L' a screen x=0
|
||||||
|
move_sprite(LEVEL_HUD_SPRITE + 2, 24, 16); // unita' a screen x=16
|
||||||
|
|
||||||
|
// Decine: mostrate solo se livello >= 10 (altrimenti sprite nascosto offscreen).
|
||||||
|
if (level >= 10) {
|
||||||
|
set_sprite_tile(LEVEL_HUD_SPRITE + 1, base + (1 + tens) * 2); // decine (cifra tens)
|
||||||
|
move_sprite(LEVEL_HUD_SPRITE + 1, 16, 16); // screen x=8
|
||||||
|
} else {
|
||||||
|
move_sprite(LEVEL_HUD_SPRITE + 1, 0, 0); // nascondi decine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disegna la porzione visibile del labirinto e applica il sistema "Fog of War".
|
* Disegna la porzione visibile del labirinto e applica il sistema "Fog of War".
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ void update_camera(void);
|
|||||||
// Update stamina bar graphics
|
// Update stamina bar graphics
|
||||||
void update_stamina_display(void);
|
void update_stamina_display(void);
|
||||||
|
|
||||||
|
// Update the level indicator (top-left HUD)
|
||||||
|
void update_level_display(void);
|
||||||
|
|
||||||
|
// Base VRAM sprite tile index for the level HUD glyphs. Placed after the full stamina
|
||||||
|
// load claim and aligned UP to an EVEN tile index: in 8x16 sprite mode the hardware
|
||||||
|
// ignores the tile-index LSB, so the base must be even for base + 2*i to land on the
|
||||||
|
// glyph's top tile. Disjoint from BG tile block and stamina (same rationale as 93deb35).
|
||||||
|
#define LEVEL_SPRITE_BASE ((uint8_t)(((tiles_TILE_COUNT + (uint8_t)(stamina_TILE_COUNT * 2)) + 1) & (uint8_t)0xFEu))
|
||||||
|
|
||||||
// Update the player sprite (animation frames, jumping height offset)
|
// Update the player sprite (animation frames, jumping height offset)
|
||||||
void update_player_sprite(void);
|
void update_player_sprite(void);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user