fix: Synchronize rat reproduction pause with global tile movement grid to prevent desync

This commit is contained in:
2026-06-17 22:26:22 +02:00
parent ac7f2c3fcc
commit b4d4b365db
6 changed files with 239 additions and 133 deletions
+4 -4
View File
@@ -23,8 +23,8 @@ void init_cursor(void) {
// (Gli indici 0-3 sono usati dai tile del topo) // (Gli indici 0-3 sono usati dai tile del topo)
set_sprite_data(4, 1, CursorSpriteData); set_sprite_data(4, 1, CursorSpriteData);
// Usa lo sprite hardware numero 2 (0 e 1 sono il topo) // Usa lo sprite hardware numero 39 (lasciando 0-38 liberi per i ratti)
set_sprite_tile(2, 4); set_sprite_tile(39, 4);
} }
void update_cursor(void) { void update_cursor(void) {
@@ -49,9 +49,9 @@ void update_cursor(void) {
// Effetto lampeggiante: nascondi il cursore per metà del ciclo (ogni 16 frame) // Effetto lampeggiante: nascondi il cursore per metà del ciclo (ogni 16 frame)
blink++; blink++;
if (blink & 0x10) { if (blink & 0x10) {
move_sprite(2, 0, 0); // Nascondi spostandolo fuori schermo move_sprite(39, 0, 0); // Nascondi spostandolo fuori schermo
} else { } else {
// Posiziona il cursore calcolando offset (12, 20) per centrarlo sulla griglia 8x8 // Posiziona il cursore calcolando offset (12, 20) per centrarlo sulla griglia 8x8
move_sprite(2, cursor_x * 8 + 12, cursor_y * 8 + 20); move_sprite(39, cursor_x * 8 + 12, cursor_y * 8 + 20);
} }
} }
+4 -4
View File
@@ -58,8 +58,8 @@ void main(void) {
// Trucco di centraggio (Hardware Scrolling): // Trucco di centraggio (Hardware Scrolling):
move_bkg(252, 252); move_bkg(252, 252);
// Inizializza il topo autonomo nel punto di partenza (1, 1) // Inizializza i topi autonomi
init_rat(1, 1); init_rats();
// Inizializza il cursore controllato dal giocatore // Inizializza il cursore controllato dal giocatore
init_cursor(); init_cursor();
@@ -76,8 +76,8 @@ void main(void) {
// Aggiorna la musica in background // Aggiorna la musica in background
update_music(); update_music();
// Aggiorna l'intelligenza artificiale e l'animazione del topo // Aggiorna l'intelligenza artificiale e l'animazione dei topi
update_rat(); update_rats();
// Aggiorna l'input del cursore del giocatore // Aggiorna l'input del cursore del giocatore
update_cursor(); update_cursor();
+26
View File
@@ -192,8 +192,29 @@ const uint8_t* const drum_parts[4] = { trk_drum_0, trk_drum_1, trk_drum_2, trk_d
static uint16_t tick = 0; static uint16_t tick = 0;
static uint8_t frame_counter = 0; static uint8_t frame_counter = 0;
static uint8_t sfx_timer = 0;
#define FRAMES_PER_TICK 8 // Velocità del tracker #define FRAMES_PER_TICK 8 // Velocità del tracker
void play_sfx_moan(void) {
// Gemito: sweep up e vibrato
NR10_REG = 0x45;
NR11_REG = 0x80;
NR12_REG = 0xF3;
NR13_REG = 0x40;
NR14_REG = 0x86;
sfx_timer = 40; // Blocca CH1 per 40 frame
}
void play_sfx_plop(void) {
// Plop: tono acuto e brevissimo
NR10_REG = 0x00;
NR11_REG = 0x80;
NR12_REG = 0xF1;
NR13_REG = 0xFF;
NR14_REG = 0x87;
sfx_timer = 15;
}
void init_music(void) { void init_music(void) {
// Reset e attivazione master sound // Reset e attivazione master sound
NR52_REG = 0x00; NR52_REG = 0x00;
@@ -211,21 +232,26 @@ void init_music(void) {
tick = 0; tick = 0;
frame_counter = 0; frame_counter = 0;
sfx_timer = 0;
} }
void update_music(void) { void update_music(void) {
if (sfx_timer > 0) sfx_timer--;
if (frame_counter == 0) { if (frame_counter == 0) {
uint8_t part = (tick >> 6) & 0x03; // tick / 64 uint8_t part = (tick >> 6) & 0x03; // tick / 64
uint8_t sub = tick & 0x3F; // tick % 64 uint8_t sub = tick & 0x3F; // tick % 64
// --- CH1: Arpeggio --- // --- CH1: Arpeggio ---
if (sfx_timer == 0) {
uint16_t f1 = arp_parts[part][sub]; uint16_t f1 = arp_parts[part][sub];
NR10_REG = 0x00; NR10_REG = 0x00;
NR11_REG = 0x80; NR11_REG = 0x80;
NR12_REG = 0x51; // Volume basso, decadimento rapido NR12_REG = 0x51; // Volume basso, decadimento rapido
NR13_REG = (uint8_t)(f1 & 0xFF); NR13_REG = (uint8_t)(f1 & 0xFF);
NR14_REG = 0x80 | ((f1 >> 8) & 0x07); NR14_REG = 0x80 | ((f1 >> 8) & 0x07);
}
// --- CH2: Melody --- // --- CH2: Melody ---
uint16_t f2 = mel_parts[part][sub]; uint16_t f2 = mel_parts[part][sub];
+3
View File
@@ -4,4 +4,7 @@
void init_music(void); void init_music(void);
void update_music(void); void update_music(void);
void play_sfx_moan(void);
void play_sfx_plop(void);
#endif #endif
+175 -84
View File
@@ -1,15 +1,9 @@
/**
* @file rat.c
* @brief Implementazione del topo autonomo usando Meta-Sprite (16x8 / 8x16).
*/
#include "rat.h" #include "rat.h"
#include "maze.h" #include "maze.h"
#include "music.h"
#include <gb/gb.h> #include <gb/gb.h>
#include <rand.h> #include <rand.h>
// Dati dei 4 tile per creare i meta-sprite del topo. Usa solo la forma "Giù" ruotata per tutte le direzioni!
// Colori: 0=Trasparente (Bianco), 1=Grigio Chiaro (Corpo), 2=Grigio Scuro (Macchie/Naso), 3=Nero (Contorni/Pupille)
const unsigned char RatSpriteData[] = { const unsigned char RatSpriteData[] = {
// 0: Destra - Metà Sinistra (Coda/Posteriore) ruotata a partire da Giù // 0: Destra - Metà Sinistra (Coda/Posteriore) ruotata a partire da Giù
0x00, 0x00, 0x07, 0x07, 0x3F, 0x38, 0x5E, 0x71, 0x5B, 0x74, 0x3F, 0x38, 0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x3F, 0x38, 0x5E, 0x71, 0x5B, 0x74, 0x3F, 0x38, 0x07, 0x07, 0x00, 0x00,
@@ -22,13 +16,22 @@ const unsigned char RatSpriteData[] = {
0x7E, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x5A, 0x5A, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 0x7E, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x5A, 0x5A, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00
}; };
static uint8_t rat_x = 1; typedef struct {
static uint8_t rat_y = 1; uint8_t active;
static uint8_t target_x = 1; uint8_t rat_x;
static uint8_t target_y = 1; uint8_t rat_y;
static uint8_t pixel_x = 0; uint8_t target_x;
static uint8_t pixel_y = 0; uint8_t target_y;
static uint8_t current_dir = 255; uint8_t pixel_x;
uint8_t pixel_y;
uint8_t current_dir;
uint8_t sprite_base_idx;
uint8_t reproduce_timer;
uint8_t cooldown_timer;
uint8_t is_mother; // flag per far partorire un solo ratto
} Rat;
static Rat rats[MAX_RATS];
static uint8_t get_opposite(uint8_t dir) { static uint8_t get_opposite(uint8_t dir) {
if (dir == 0) return 1; if (dir == 0) return 1;
@@ -38,103 +41,191 @@ static uint8_t get_opposite(uint8_t dir) {
return 255; return 255;
} }
void init_rat(uint8_t start_x, uint8_t start_y) { void init_rats(void) {
rat_x = target_x = start_x;
rat_y = target_y = start_y;
pixel_x = rat_x * 8;
pixel_y = rat_y * 8;
current_dir = 255;
// Carica tutti i 4 tile per le combinazioni 16x8 e 8x16 // Carica tutti i 4 tile per le combinazioni 16x8 e 8x16
set_sprite_data(0, 4, RatSpriteData); set_sprite_data(0, 4, RatSpriteData);
OBP0_REG = 0xE4; // Palette standard OBP0_REG = 0xE4; // Palette standard
// Imposta tile di default (Giù) // Posizioni iniziali garantite vuote (coordinate dispari)
set_sprite_tile(0, 2); uint8_t start_pos[2][2] = {
set_sprite_tile(1, 3); {1, 1},
{17, 15}
};
for (uint8_t i = 0; i < MAX_RATS; i++) {
rats[i].active = 0;
rats[i].reproduce_timer = 0;
rats[i].cooldown_timer = 0;
rats[i].is_mother = 0;
rats[i].sprite_base_idx = i * 2;
// Nascondi sprite
move_sprite(rats[i].sprite_base_idx, 0, 0);
move_sprite(rats[i].sprite_base_idx + 1, 0, 0);
}
// Attiva i primi due topi
for (uint8_t i = 0; i < 2; i++) {
rats[i].active = 1;
rats[i].rat_x = start_pos[i][0];
rats[i].rat_y = start_pos[i][1];
rats[i].target_x = start_pos[i][0];
rats[i].target_y = start_pos[i][1];
rats[i].pixel_x = rats[i].rat_x * 8;
rats[i].pixel_y = rats[i].rat_y * 8;
rats[i].current_dir = 255;
}
} }
void update_rat(void) { void spawn_rat(uint8_t x, uint8_t y) {
if (rat_x == target_x && rat_y == target_y) { for (uint8_t i = 0; i < MAX_RATS; i++) {
if (!rats[i].active) {
rats[i].active = 1;
rats[i].rat_x = x;
rats[i].rat_y = y;
rats[i].target_x = x;
rats[i].target_y = y;
rats[i].pixel_x = x * 8;
rats[i].pixel_y = y * 8;
rats[i].current_dir = 255;
rats[i].reproduce_timer = 0;
rats[i].cooldown_timer = 120; // Il cucciolo non si riproduce subito
rats[i].is_mother = 0;
play_sfx_plop(); // Suono di nascita!
break;
}
}
}
void update_rats(void) {
static uint8_t frame_counter = 0;
frame_counter++;
uint8_t do_move = (frame_counter & 1); // Muovi di 1 pixel ogni 2 frame
// 1. Controlla collisioni per riproduzione (se sono sulla stessa cella e fermi)
for (uint8_t i = 0; i < MAX_RATS; i++) {
if (!rats[i].active || rats[i].reproduce_timer > 0 || rats[i].cooldown_timer > 0) continue;
for (uint8_t j = i + 1; j < MAX_RATS; j++) {
if (!rats[j].active || rats[j].reproduce_timer > 0 || rats[j].cooldown_timer > 0) continue;
if (rats[i].rat_x == rats[j].rat_x && rats[i].rat_y == rats[j].rat_y &&
rats[i].pixel_x == rats[j].pixel_x && rats[i].pixel_y == rats[j].pixel_y) {
// Incontro riproduttivo!
// Usa 64 frames invece di 60 perché è un multiplo esatto di 16 (il tempo che un topo impiega
// per attraversare esattamente un tile di 8 pixel). Così non perdono la sincronia di fase globale!
rats[i].reproduce_timer = 64;
rats[j].reproduce_timer = 64;
rats[i].is_mother = 1; // Solo i farà spawnare il cucciolo
rats[j].is_mother = 0;
play_sfx_moan(); // Suono d'amore
break;
}
}
}
// 2. Aggiorna lo stato di ogni topo
for (uint8_t i = 0; i < MAX_RATS; i++) {
Rat* r = &rats[i];
if (!r->active) continue;
if (r->cooldown_timer > 0) {
r->cooldown_timer--;
}
// Se si stanno riproducendo, aspetta
if (r->reproduce_timer > 0) {
r->reproduce_timer--;
if (r->reproduce_timer == 0) {
// Finito di riprodursi, imposta il cooldown
r->cooldown_timer = 120; // 2 secondi di pausa
if (r->is_mother) {
r->is_mother = 0;
spawn_rat(r->rat_x, r->rat_y);
}
}
continue;
}
if (r->rat_x == r->target_x && r->rat_y == r->target_y) {
uint8_t valid_dirs[4]; uint8_t valid_dirs[4];
uint8_t num_valid = 0; uint8_t num_valid = 0;
if (rat_y < MAZE_HEIGHT - 1 && maze[rat_y + 1][rat_x] == 0) valid_dirs[num_valid++] = 0; if (r->rat_y < MAZE_HEIGHT - 1 && maze[r->rat_y + 1][r->rat_x] == 0) valid_dirs[num_valid++] = 0;
if (rat_y > 0 && maze[rat_y - 1][rat_x] == 0) valid_dirs[num_valid++] = 1; if (r->rat_y > 0 && maze[r->rat_y - 1][r->rat_x] == 0) valid_dirs[num_valid++] = 1;
if (rat_x > 0 && maze[rat_y][rat_x - 1] == 0) valid_dirs[num_valid++] = 2; if (r->rat_x > 0 && maze[r->rat_y][r->rat_x - 1] == 0) valid_dirs[num_valid++] = 2;
if (rat_x < MAZE_WIDTH - 1 && maze[rat_y][rat_x + 1] == 0) valid_dirs[num_valid++] = 3; if (r->rat_x < MAZE_WIDTH - 1 && maze[r->rat_y][r->rat_x + 1] == 0) valid_dirs[num_valid++] = 3;
if (num_valid > 0) { if (num_valid > 0) {
if (num_valid > 1 && current_dir != 255) { if (num_valid > 1 && r->current_dir != 255) {
uint8_t opp = get_opposite(current_dir); uint8_t opp = get_opposite(r->current_dir);
uint8_t filtered[4]; uint8_t filtered[4];
uint8_t num_filtered = 0; uint8_t num_filtered = 0;
for (uint8_t i = 0; i < num_valid; i++) { for (uint8_t j = 0; j < num_valid; j++) {
if (valid_dirs[i] != opp) { if (valid_dirs[j] != opp) {
filtered[num_filtered++] = valid_dirs[i]; filtered[num_filtered++] = valid_dirs[j];
} }
} }
if (num_filtered > 0) current_dir = filtered[rand() % num_filtered]; if (num_filtered > 0) r->current_dir = filtered[rand() % num_filtered];
else current_dir = valid_dirs[rand() % num_valid]; else r->current_dir = valid_dirs[rand() % num_valid];
} else { } else {
current_dir = valid_dirs[rand() % num_valid]; r->current_dir = valid_dirs[rand() % num_valid];
} }
if (current_dir == 0) target_y++; if (r->current_dir == 0) r->target_y++;
else if (current_dir == 1) target_y--; else if (r->current_dir == 1) r->target_y--;
else if (current_dir == 2) target_x--; else if (r->current_dir == 2) r->target_x--;
else if (current_dir == 3) target_x++; else if (r->current_dir == 3) r->target_x++;
} }
} }
uint8_t target_px = target_x * 8; uint8_t target_px = r->target_x * 8;
uint8_t target_py = target_y * 8; uint8_t target_py = r->target_y * 8;
static uint8_t frame_counter = 0; if (do_move) {
frame_counter++; if (r->pixel_x < target_px) r->pixel_x++;
if (frame_counter & 1) { else if (r->pixel_x > target_px) r->pixel_x--;
if (pixel_x < target_px) pixel_x++; if (r->pixel_y < target_py) r->pixel_y++;
else if (pixel_x > target_px) pixel_x--; else if (r->pixel_y > target_py) r->pixel_y--;
if (pixel_y < target_py) pixel_y++;
else if (pixel_y > target_py) pixel_y--;
} }
if (pixel_x == target_px && pixel_y == target_py) { if (r->pixel_x == target_px && r->pixel_y == target_py) {
rat_x = target_x; r->rat_x = r->target_x;
rat_y = target_y; r->rat_y = r->target_y;
} }
// Aggiorna gli hardware sprite (Meta-Sprite system) // Aggiorna gli hardware sprite (Meta-Sprite system)
uint8_t base_x = pixel_x + 12; uint8_t base_x = r->pixel_x + 12;
uint8_t base_y = pixel_y + 20; uint8_t base_y = r->pixel_y + 20;
uint8_t s0 = r->sprite_base_idx;
uint8_t s1 = r->sprite_base_idx + 1;
if (current_dir == 0 || current_dir == 255) { // Giù (Verticale 8x16) if (r->current_dir == 0 || r->current_dir == 255) { // Giù
set_sprite_tile(0, 2); set_sprite_tile(s0, 2);
set_sprite_tile(1, 3); set_sprite_tile(s1, 3);
set_sprite_prop(0, 0); set_sprite_prop(s0, 0);
set_sprite_prop(1, 0); set_sprite_prop(s1, 0);
move_sprite(0, base_x, base_y - 4); move_sprite(s0, base_x, base_y - 4);
move_sprite(1, base_x, base_y + 4); move_sprite(s1, base_x, base_y + 4);
} else if (current_dir == 1) { // Su (Verticale 8x16, FLIPPATO IN Y) } else if (r->current_dir == 1) { // Su
set_sprite_tile(0, 3); // Naso diventa parte alta set_sprite_tile(s0, 3);
set_sprite_tile(1, 2); // Coda diventa parte bassa set_sprite_tile(s1, 2);
set_sprite_prop(0, S_FLIPY); set_sprite_prop(s0, S_FLIPY);
set_sprite_prop(1, S_FLIPY); set_sprite_prop(s1, S_FLIPY);
move_sprite(0, base_x, base_y - 4); move_sprite(s0, base_x, base_y - 4);
move_sprite(1, base_x, base_y + 4); move_sprite(s1, base_x, base_y + 4);
} else if (current_dir == 2) { // Sinistra (Flippato orizzontalmente per puntare a sinistra) } else if (r->current_dir == 2) { // Sinistra
set_sprite_tile(0, 1); // Naso flippato (va a sinistra) set_sprite_tile(s0, 1);
set_sprite_tile(1, 0); // Coda flippata (va a destra) set_sprite_tile(s1, 0);
set_sprite_prop(0, S_FLIPX); set_sprite_prop(s0, S_FLIPX);
set_sprite_prop(1, S_FLIPX); set_sprite_prop(s1, S_FLIPX);
move_sprite(0, base_x - 4, base_y); move_sprite(s0, base_x - 4, base_y);
move_sprite(1, base_x + 4, base_y); move_sprite(s1, base_x + 4, base_y);
} else if (current_dir == 3) { // Destra (Punta a destra nativamente) } else if (r->current_dir == 3) { // Destra
set_sprite_tile(0, 0); // Coda (metà sinistra) set_sprite_tile(s0, 0);
set_sprite_tile(1, 1); // Naso (metà destra) set_sprite_tile(s1, 1);
set_sprite_prop(0, 0); set_sprite_prop(s0, 0);
set_sprite_prop(1, 0); set_sprite_prop(s1, 0);
move_sprite(0, base_x - 4, base_y); move_sprite(s0, base_x - 4, base_y);
move_sprite(1, base_x + 4, base_y); move_sprite(s1, base_x + 4, base_y);
}
} }
} }
+3 -17
View File
@@ -1,25 +1,11 @@
/**
* @file rat.h
* @brief Header per la gestione del personaggio "ratto" autonomo.
*/
#ifndef RAT_H #ifndef RAT_H
#define RAT_H #define RAT_H
#include <stdint.h> #include <stdint.h>
/** #define MAX_RATS 15
* @brief Inizializza il ratto alle coordinate logiche specificate.
* @param start_x Coordinata X (in tiles) nel labirinto.
* @param start_y Coordinata Y (in tiles) nel labirinto.
*/
void init_rat(uint8_t start_x, uint8_t start_y);
/** void init_rats(void);
* @brief Aggiorna la posizione e la logica del ratto. void update_rats(void);
*
* Da chiamare ogni frame nel game loop principale.
*/
void update_rat(void);
#endif #endif