Refactoring: Project structure, sprite stairs & fog masks rendering fix
This commit is contained in:
+243
@@ -0,0 +1,243 @@
|
||||
#include <gb/gb.h>
|
||||
#include <gbdk/font.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <rand.h>
|
||||
|
||||
#include "engine.h"
|
||||
#include "globals.h"
|
||||
#include "maze.h"
|
||||
#include "sound.h"
|
||||
#include "render.h"
|
||||
#include "player_logic.h"
|
||||
#include "enemy_logic.h"
|
||||
|
||||
// Generated by png2asset
|
||||
#include "tiles.h"
|
||||
#include "player.h"
|
||||
#include "enemy.h"
|
||||
#include "gameover.h"
|
||||
#include "victory.h"
|
||||
#include "stamina.h"
|
||||
#include "title_bg.h"
|
||||
#ifdef USE_SPRITE_STAIRS
|
||||
#include "stairs_sprite.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Inizializza la schermata del titolo
|
||||
*/
|
||||
void title_init(void) {
|
||||
// Nasconde tutti gli sprite (sia personaggio che HUD)
|
||||
for (uint8_t i = 0; i < 40; i++) move_sprite(i, 0, 0);
|
||||
|
||||
// Carica l'immagine di sfondo del titolo
|
||||
set_bkg_data(0, title_bg_TILE_COUNT, title_bg_tiles);
|
||||
set_bkg_tiles(0, 0, title_bg_WIDTH / 8, title_bg_HEIGHT / 8, title_bg_map);
|
||||
|
||||
// Configura la palette standard B/N
|
||||
BGP_REG = 0xE4;
|
||||
|
||||
// Centra lo scroll della telecamera in alto a sinistra (0,0)
|
||||
move_bkg(0, 0);
|
||||
|
||||
// Inizializza l'Audio Hardware del Game Boy
|
||||
NR52_REG = 0x80; // Accende il chip audio
|
||||
NR50_REG = 0x77; // Volume master al massimo
|
||||
NR51_REG = 0xFF; // Abilita l'output stereo su tutti i canali
|
||||
|
||||
// Avvia la musica del titolo riavviando il sequencer
|
||||
sound_reset_music_state();
|
||||
remove_VBL(play_music_tick);
|
||||
add_VBL(play_music_tick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop di aggiornamento della schermata del titolo (non fa nulla, la pressione di START è gestita in main.c)
|
||||
*/
|
||||
void title_update(uint8_t keys, uint8_t prev_keys) {
|
||||
// Vuoto. La musica continua in background via Interrupt (VBL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Inizializza l'engine per la fase di Gameplay.
|
||||
* Qui viene orchestrata la "nascita" di un nuovo livello.
|
||||
*/
|
||||
void engine_init(void) {
|
||||
SPRITES_8x16; // Imposta la modalità Sprite Alti (16 pixel invece di 8)
|
||||
|
||||
// Hard reset Audio
|
||||
NR52_REG = 0x80;
|
||||
NR50_REG = 0x77;
|
||||
NR51_REG = 0xFF;
|
||||
|
||||
// Carica il font testuale di sistema (IBM)
|
||||
font_init();
|
||||
font_t ibm_font = font_load(font_ibm);
|
||||
font_set(ibm_font);
|
||||
|
||||
// Avvia il sequencer per la musica In-Game "Eerie pulse"
|
||||
sound_reset_music_state();
|
||||
remove_VBL(play_music_tick);
|
||||
add_VBL(play_music_tick);
|
||||
|
||||
// Usa il registro divisore hardware (DIV_REG) per seedare l'RNG. Garantisce labirinti diversi ogni volta.
|
||||
initrand(DIV_REG);
|
||||
|
||||
// DELEGA LA GENERAZIONE: Chiede al modulo maze.c di creare l'array della mappa
|
||||
generate_maze();
|
||||
|
||||
// Imposta le palette (DMG compatibile)
|
||||
OBP0_REG = 0xE4; // Palette Normale (Nero, Grigio Scuro, Grigio Chiaro, Bianco)
|
||||
OBP1_REG = 0x1B; // Palette Invertita (per il fantasma: bianco, grigio scuro, nero)
|
||||
BGP_REG = 0xE4; // Palette Background
|
||||
|
||||
// Carica in memoria VRAM la grafica di Tiles e Sprite
|
||||
set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles);
|
||||
set_sprite_data(0, player_TILE_COUNT, player_tiles);
|
||||
set_sprite_data(player_TILE_COUNT, enemy_TILE_COUNT, enemy_tiles);
|
||||
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, victory_TILE_COUNT, victory_tiles);
|
||||
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT + victory_TILE_COUNT, stamina_TILE_COUNT * 2, stamina_tiles);
|
||||
#ifdef USE_SPRITE_STAIRS
|
||||
set_sprite_data(player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT + victory_TILE_COUNT + stamina_TILE_COUNT * 2, stairs_sprite_TILE_COUNT, stairs_sprite_tiles);
|
||||
#endif
|
||||
|
||||
// SPAWN DEL GIOCATORE
|
||||
player_lx = 1;
|
||||
player_ly = 1;
|
||||
// Fallback di sicurezza: Cerca la prima casella libera se 1,1 è occupato (teoricamente impossibile col DFS)
|
||||
if (maze[player_ly][player_lx] == 0) {
|
||||
uint8_t found = 0;
|
||||
for (uint8_t y = 1; y < MAP_SIZE - 1; y++) {
|
||||
for (uint8_t x = 1; x < MAP_SIZE - 1; x++) {
|
||||
if (maze[y][x] == 1) {
|
||||
player_lx = x;
|
||||
player_ly = y;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SPRITE_STAIRS
|
||||
// HARDCODE STAIRS FOR TESTING
|
||||
stairs_lx = player_lx + 1;
|
||||
stairs_ly = player_ly;
|
||||
maze[stairs_ly][stairs_lx] = 2;
|
||||
#endif
|
||||
|
||||
// SPAWN DEL NEMICO
|
||||
// Cerca randomicamente un punto lontano almeno 3 celle dal giocatore
|
||||
while (1) {
|
||||
uint8_t rx = rand() % MAP_SIZE;
|
||||
uint8_t ry = rand() % MAP_SIZE;
|
||||
if (maze[ry][rx] == 1) {
|
||||
int8_t dx = (int8_t)rx - (int8_t)player_lx;
|
||||
int8_t dy = (int8_t)ry - (int8_t)player_ly;
|
||||
if (dx < 0) dx = -dx;
|
||||
if (dy < 0) dy = -dy;
|
||||
int8_t dist = (dx > dy) ? dx : dy;
|
||||
if (dist >= 3) {
|
||||
enemy_lx = rx;
|
||||
enemy_ly = ry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset Variabili di Gameplay
|
||||
enemy_is_moving = 0;
|
||||
enemy_cooldown = 60;
|
||||
game_over = 0;
|
||||
stamina = 100;
|
||||
stamina_recharge_timer = 0;
|
||||
is_jumping = 0;
|
||||
is_moving = 0;
|
||||
|
||||
// Richiama render.c per forzare un disegno iniziale della scena
|
||||
draw_map(player_lx, player_ly);
|
||||
update_camera();
|
||||
update_player_sprite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop Principale di Aggiornamento del Gioco (chiamato ad ogni Frame ~60 FPS).
|
||||
* Questo metodo funge da "Direttore d'Orchestra", delegando i compiti ai moduli specializzati.
|
||||
*/
|
||||
void engine_update(uint8_t keys, uint8_t prev_keys) {
|
||||
|
||||
// --- GESTIONE STATO DI FINE GIOCO (Sconfitta o Vittoria) ---
|
||||
if (game_over) {
|
||||
if (game_over_timer > 0) {
|
||||
game_over_timer--;
|
||||
// Quando scade il timer drammatico...
|
||||
if (game_over_timer == 0) {
|
||||
// ...Svuota lo schermo
|
||||
memset(map_buffer, 0, sizeof(map_buffer));
|
||||
set_bkg_tiles(0, 0, 32, 32, map_buffer);
|
||||
|
||||
// Imposta i timer audio per far iniziare la musica finale dal modulo sound.c
|
||||
// La musica verrà suonata dall'interrupt play_music_tick in background.
|
||||
if (game_over == 1) {
|
||||
// Start gameover sequence state
|
||||
// gameover_music_timer/step are handled in play_music_tick inside sound.c automatically
|
||||
// We just trigger a starting dramatic beat.
|
||||
NR21_REG = 0x80;
|
||||
NR22_REG = 0xF3;
|
||||
NR23_REG = 0x2C;
|
||||
NR24_REG = 0x84;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Dopo il timer, disegna i metasprite di VITTORIA o GAME OVER fissi in mezzo allo schermo
|
||||
if (game_over == 1) {
|
||||
move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 88, 120);
|
||||
} else if (game_over == 2) {
|
||||
move_metasprite(victory_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT, 8, 88, 120);
|
||||
}
|
||||
|
||||
// Il giocatore rimane visibile al centro, il nemico scompare se vinto o appare se sconfitto.
|
||||
update_player_sprite();
|
||||
|
||||
int16_t enemy_px = (enemy_lx - enemy_ly) * 16 + 96;
|
||||
int16_t enemy_py = (enemy_lx + enemy_ly) * 8 + 16;
|
||||
int16_t enemy_screen_x = ((enemy_px - scroll_x) & 255) + 24;
|
||||
int16_t enemy_screen_y = ((enemy_py - scroll_y) & 255) + 16;
|
||||
|
||||
int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx;
|
||||
int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly;
|
||||
if (edx < 0) edx = -edx;
|
||||
if (edy < 0) edy = -edy;
|
||||
uint8_t ep_dist = (edx > edy) ? edx : edy;
|
||||
|
||||
if (game_over == 1 && ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
|
||||
move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, enemy_screen_x, enemy_screen_y);
|
||||
} else {
|
||||
move_metasprite(enemy_metasprites[0], player_TILE_COUNT, 4, 0, 0);
|
||||
}
|
||||
|
||||
// Attende la pressione di START per riavviare
|
||||
if ((keys & J_START) && !(prev_keys & J_START)) {
|
||||
// Nasconde la grafica testuale e ricarica tutto l'engine
|
||||
move_metasprite(gameover_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT, 8, 0, 0);
|
||||
move_metasprite(victory_metasprites[0], player_TILE_COUNT + enemy_TILE_COUNT + gameover_TILE_COUNT, 8, 0, 0);
|
||||
engine_init();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// --- GESTIONE DEL LOOP DI GIOCO STANDARD ---
|
||||
|
||||
// 1. DELEGA L'AI DEL NEMICO (se il fantasma si deve muovere, lo calcola qui)
|
||||
update_enemy_logic();
|
||||
|
||||
// 2. DELEGA LA LOGICA DEL GIOCATORE (movimento, tasti, animazioni)
|
||||
update_player_movement(keys, prev_keys);
|
||||
|
||||
// Note: Il rendering effettivo della mappa e della telecamera avviene già all'interno di
|
||||
// update_player_movement durante l'interpolazione del passo (per fluidità sub-tile).
|
||||
}
|
||||
Reference in New Issue
Block a user