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)
set_sprite_data(4, 1, CursorSpriteData);
// Usa lo sprite hardware numero 2 (0 e 1 sono il topo)
set_sprite_tile(2, 4);
// Usa lo sprite hardware numero 39 (lasciando 0-38 liberi per i ratti)
set_sprite_tile(39, 4);
}
void update_cursor(void) {
@@ -49,9 +49,9 @@ void update_cursor(void) {
// Effetto lampeggiante: nascondi il cursore per metà del ciclo (ogni 16 frame)
blink++;
if (blink & 0x10) {
move_sprite(2, 0, 0); // Nascondi spostandolo fuori schermo
move_sprite(39, 0, 0); // Nascondi spostandolo fuori schermo
} else {
// 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):
move_bkg(252, 252);
// Inizializza il topo autonomo nel punto di partenza (1, 1)
init_rat(1, 1);
// Inizializza i topi autonomi
init_rats();
// Inizializza il cursore controllato dal giocatore
init_cursor();
@@ -76,8 +76,8 @@ void main(void) {
// Aggiorna la musica in background
update_music();
// Aggiorna l'intelligenza artificiale e l'animazione del topo
update_rat();
// Aggiorna l'intelligenza artificiale e l'animazione dei topi
update_rats();
// Aggiorna l'input del cursore del giocatore
update_cursor();
+32 -6
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 uint8_t frame_counter = 0;
static uint8_t sfx_timer = 0;
#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) {
// Reset e attivazione master sound
NR52_REG = 0x00;
@@ -211,21 +232,26 @@ void init_music(void) {
tick = 0;
frame_counter = 0;
sfx_timer = 0;
}
void update_music(void) {
if (sfx_timer > 0) sfx_timer--;
if (frame_counter == 0) {
uint8_t part = (tick >> 6) & 0x03; // tick / 64
uint8_t sub = tick & 0x3F; // tick % 64
// --- CH1: Arpeggio ---
uint16_t f1 = arp_parts[part][sub];
NR10_REG = 0x00;
NR11_REG = 0x80;
NR12_REG = 0x51; // Volume basso, decadimento rapido
NR13_REG = (uint8_t)(f1 & 0xFF);
NR14_REG = 0x80 | ((f1 >> 8) & 0x07);
if (sfx_timer == 0) {
uint16_t f1 = arp_parts[part][sub];
NR10_REG = 0x00;
NR11_REG = 0x80;
NR12_REG = 0x51; // Volume basso, decadimento rapido
NR13_REG = (uint8_t)(f1 & 0xFF);
NR14_REG = 0x80 | ((f1 >> 8) & 0x07);
}
// --- CH2: Melody ---
uint16_t f2 = mel_parts[part][sub];
+3
View File
@@ -4,4 +4,7 @@
void init_music(void);
void update_music(void);
void play_sfx_moan(void);
void play_sfx_plop(void);
#endif
+193 -102
View File
@@ -1,15 +1,9 @@
/**
* @file rat.c
* @brief Implementazione del topo autonomo usando Meta-Sprite (16x8 / 8x16).
*/
#include "rat.h"
#include "maze.h"
#include "music.h"
#include <gb/gb.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[] = {
// 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,
@@ -22,13 +16,22 @@ const unsigned char RatSpriteData[] = {
0x7E, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x5A, 0x5A, 0x24, 0x3C, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00
};
static uint8_t rat_x = 1;
static uint8_t rat_y = 1;
static uint8_t target_x = 1;
static uint8_t target_y = 1;
static uint8_t pixel_x = 0;
static uint8_t pixel_y = 0;
static uint8_t current_dir = 255;
typedef struct {
uint8_t active;
uint8_t rat_x;
uint8_t rat_y;
uint8_t target_x;
uint8_t target_y;
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) {
if (dir == 0) return 1;
@@ -38,103 +41,191 @@ static uint8_t get_opposite(uint8_t dir) {
return 255;
}
void init_rat(uint8_t start_x, uint8_t start_y) {
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;
void init_rats(void) {
// Carica tutti i 4 tile per le combinazioni 16x8 e 8x16
set_sprite_data(0, 4, RatSpriteData);
OBP0_REG = 0xE4; // Palette standard
// Imposta tile di default (Giù)
set_sprite_tile(0, 2);
set_sprite_tile(1, 3);
// Posizioni iniziali garantite vuote (coordinate dispari)
uint8_t start_pos[2][2] = {
{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) {
if (rat_x == target_x && rat_y == target_y) {
uint8_t valid_dirs[4];
uint8_t num_valid = 0;
if (rat_y < MAZE_HEIGHT - 1 && maze[rat_y + 1][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 (rat_x > 0 && maze[rat_y][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 (num_valid > 0) {
if (num_valid > 1 && current_dir != 255) {
uint8_t opp = get_opposite(current_dir);
uint8_t filtered[4];
uint8_t num_filtered = 0;
for (uint8_t i = 0; i < num_valid; i++) {
if (valid_dirs[i] != opp) {
filtered[num_filtered++] = valid_dirs[i];
}
}
if (num_filtered > 0) current_dir = filtered[rand() % num_filtered];
else current_dir = valid_dirs[rand() % num_valid];
} else {
current_dir = valid_dirs[rand() % num_valid];
}
void spawn_rat(uint8_t x, uint8_t 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 (current_dir == 0) target_y++;
else if (current_dir == 1) target_y--;
else if (current_dir == 2) target_x--;
else if (current_dir == 3) target_x++;
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;
}
}
}
uint8_t target_px = target_x * 8;
uint8_t target_py = target_y * 8;
static uint8_t frame_counter = 0;
frame_counter++;
if (frame_counter & 1) {
if (pixel_x < target_px) pixel_x++;
else if (pixel_x > target_px) pixel_x--;
if (pixel_y < target_py) pixel_y++;
else if (pixel_y > target_py) pixel_y--;
}
if (pixel_x == target_px && pixel_y == target_py) {
rat_x = target_x;
rat_y = target_y;
}
// Aggiorna gli hardware sprite (Meta-Sprite system)
uint8_t base_x = pixel_x + 12;
uint8_t base_y = pixel_y + 20;
if (current_dir == 0 || current_dir == 255) { // Giù (Verticale 8x16)
set_sprite_tile(0, 2);
set_sprite_tile(1, 3);
set_sprite_prop(0, 0);
set_sprite_prop(1, 0);
move_sprite(0, base_x, base_y - 4);
move_sprite(1, base_x, base_y + 4);
} else if (current_dir == 1) { // Su (Verticale 8x16, FLIPPATO IN Y)
set_sprite_tile(0, 3); // Naso diventa parte alta
set_sprite_tile(1, 2); // Coda diventa parte bassa
set_sprite_prop(0, S_FLIPY);
set_sprite_prop(1, S_FLIPY);
move_sprite(0, base_x, base_y - 4);
move_sprite(1, base_x, base_y + 4);
} else if (current_dir == 2) { // Sinistra (Flippato orizzontalmente per puntare a sinistra)
set_sprite_tile(0, 1); // Naso flippato (va a sinistra)
set_sprite_tile(1, 0); // Coda flippata (va a destra)
set_sprite_prop(0, S_FLIPX);
set_sprite_prop(1, S_FLIPX);
move_sprite(0, base_x - 4, base_y);
move_sprite(1, base_x + 4, base_y);
} else if (current_dir == 3) { // Destra (Punta a destra nativamente)
set_sprite_tile(0, 0); // Coda (metà sinistra)
set_sprite_tile(1, 1); // Naso (metà destra)
set_sprite_prop(0, 0);
set_sprite_prop(1, 0);
move_sprite(0, base_x - 4, base_y);
move_sprite(1, base_x + 4, base_y);
// 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 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 (r->rat_y > 0 && maze[r->rat_y - 1][r->rat_x] == 0) valid_dirs[num_valid++] = 1;
if (r->rat_x > 0 && maze[r->rat_y][r->rat_x - 1] == 0) valid_dirs[num_valid++] = 2;
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 > 1 && r->current_dir != 255) {
uint8_t opp = get_opposite(r->current_dir);
uint8_t filtered[4];
uint8_t num_filtered = 0;
for (uint8_t j = 0; j < num_valid; j++) {
if (valid_dirs[j] != opp) {
filtered[num_filtered++] = valid_dirs[j];
}
}
if (num_filtered > 0) r->current_dir = filtered[rand() % num_filtered];
else r->current_dir = valid_dirs[rand() % num_valid];
} else {
r->current_dir = valid_dirs[rand() % num_valid];
}
if (r->current_dir == 0) r->target_y++;
else if (r->current_dir == 1) r->target_y--;
else if (r->current_dir == 2) r->target_x--;
else if (r->current_dir == 3) r->target_x++;
}
}
uint8_t target_px = r->target_x * 8;
uint8_t target_py = r->target_y * 8;
if (do_move) {
if (r->pixel_x < target_px) r->pixel_x++;
else if (r->pixel_x > target_px) r->pixel_x--;
if (r->pixel_y < target_py) r->pixel_y++;
else if (r->pixel_y > target_py) r->pixel_y--;
}
if (r->pixel_x == target_px && r->pixel_y == target_py) {
r->rat_x = r->target_x;
r->rat_y = r->target_y;
}
// Aggiorna gli hardware sprite (Meta-Sprite system)
uint8_t base_x = r->pixel_x + 12;
uint8_t base_y = r->pixel_y + 20;
uint8_t s0 = r->sprite_base_idx;
uint8_t s1 = r->sprite_base_idx + 1;
if (r->current_dir == 0 || r->current_dir == 255) { // Giù
set_sprite_tile(s0, 2);
set_sprite_tile(s1, 3);
set_sprite_prop(s0, 0);
set_sprite_prop(s1, 0);
move_sprite(s0, base_x, base_y - 4);
move_sprite(s1, base_x, base_y + 4);
} else if (r->current_dir == 1) { // Su
set_sprite_tile(s0, 3);
set_sprite_tile(s1, 2);
set_sprite_prop(s0, S_FLIPY);
set_sprite_prop(s1, S_FLIPY);
move_sprite(s0, base_x, base_y - 4);
move_sprite(s1, base_x, base_y + 4);
} else if (r->current_dir == 2) { // Sinistra
set_sprite_tile(s0, 1);
set_sprite_tile(s1, 0);
set_sprite_prop(s0, S_FLIPX);
set_sprite_prop(s1, S_FLIPX);
move_sprite(s0, base_x - 4, base_y);
move_sprite(s1, base_x + 4, base_y);
} else if (r->current_dir == 3) { // Destra
set_sprite_tile(s0, 0);
set_sprite_tile(s1, 1);
set_sprite_prop(s0, 0);
set_sprite_prop(s1, 0);
move_sprite(s0, 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
#define RAT_H
#include <stdint.h>
/**
* @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);
#define MAX_RATS 15
/**
* @brief Aggiorna la posizione e la logica del ratto.
*
* Da chiamare ogni frame nel game loop principale.
*/
void update_rat(void);
void init_rats(void);
void update_rats(void);
#endif