Skip to content

Core Architettura

Alessandro Bria edited this page Oct 12, 2026 · 6 revisions

Architettura e game loop

Game è il coordinatore dell'applicazione. È un singleton polimorfico: ogni progetto crea una propria sottoclasse, la registra con Game::setInstance() e ridefinisce almeno init().

Chi fa cosa

Componente Responsabilità
Game Conserva la pila di scene, misura il tempo, distribuisce eventi e governa il ciclo.
Window Possiede SDL_Window e SDL_Renderer, pulisce e presenta il frame.
Scene Organizza oggetti, aggiornamento, query e rendering della propria area.
GameScene Aggiunge simulazione con semi-fixed timestep, camera, overlay e indice spaziale.
UIScene Aggiorna l'interfaccia con il tempo effettivo del frame.

Avvio

Una sequenza tipica è:

agp::Game::setInstance(new MyGame());
agp::Game::instance()->init();
agp::Game::instance()->run();

Il costruttore di Game crea e inizializza la finestra, quindi il renderer è disponibile prima di creare texture nelle factory del progetto. init() non viene chiamato automaticamente dal costruttore: il main() dei prototipi lo invoca esplicitamente prima di run(). Alla fine del ciclo, Game::destroy() elimina le scene, la finestra e chiama SDL_Quit().

Il costruttore di Game sceglie anche il sistema di rendering:

Modalità Finestra creata Uso
Rendering::SDL Window Rendering SDL3 normale.
Rendering::SDL_CPU_SHADERS CPUShaderWindow Post-processing eseguito sulla CPU.
Rendering::SDL_GPU_SHADERS GPUShaderWindow Post-processing SDL_GPU opzionale, con effetti CPU ancora disponibili.

Un frame

sequenceDiagram
    participant G as Game
    participant S as Scene stack
    participant W as Window
    G->>G: processEvents()
    G->>S: update(frameTime)
    Note over S: dall'alto verso il basso<br/>fino a una scena blocking
    G->>W: render(scenes)
    W->>S: render()
    Note over S: dal basso verso l'alto
    W->>W: SDL_RenderPresent()
Loading

Il tempo trascorso dall'ultimo frame viene misurato e passato alle scene. Game calcola anche gli FPS correnti e supporta una scala temporale utile per debug e dimostrazioni.

L'ordine è preciso: prima tutti gli eventi SDL in attesa, poi il tempo del frame, quindi l'aggiornamento delle scene dall'alto della pila verso il basso e infine il rendering dal basso verso l'alto. Window::render() pulisce il renderer una volta sola e chiama SDL_RenderPresent() alla fine. Il VSync viene richiesto quando viene creato il renderer SDL.

Per seguire le chiamate dall'alto verso il basso, usa Dal game loop al singolo oggetto: diagramma di sequenza. La pagina sviluppa il frame completo e poi approfondisce l'aggiornamento di Mario e la rimozione degli oggetti.

Stack delle scene

Le scene sono organizzate come una pila:

  • pushScene() aggiunge una scena in primo piano;
  • popSceneLater() richiede la rimozione in modo differito;
  • blocking() interrompe la propagazione di update ed eventi verso le scene sottostanti;
  • il rendering continua a includere le scene sottostanti, utile per menu in sovraimpressione.

blocking riguarda eventi e aggiornamento, non la visibilità. Una scena sovrapposta può quindi fermare il gioco senza nasconderlo. L'aggiornamento parte dalla scena in cima; quando incontra una scena bloccante, non raggiunge quelle più in basso. popScene() elimina subito la scena in cima; popSceneLater() ne rinvia la rimozione al successivo processEvents(), scelta utile mentre si gestisce un evento. Anche reset() rinvia la ricostruzione delle scene: le elimina e richiama init() nel ciclo seguente.

Gli eventi di finestra vengono distribuiti a tutte le scene, perché un ridimensionamento può cambiare ogni viewport. Gli altri eventi procedono dalla scena superiore verso il basso fino alla prima scena bloccante.

Due modelli temporali

GameScene usa un semi-fixed timestep. Accumula il tempo reale e aggiorna gli oggetti con passi costanti _dt; questo rende più stabile la simulazione.

UIScene usa invece il tempo reale del frame. Per menu, cursori e HUD è spesso preferibile una risposta immediata senza simulazione fisica.

Per esempio, se _dt vale 0,01 s e un frame dura 0,025 s, la simulazione esegue due update da 0,01 s e conserva 0,005 s nell'accumulatore. GameScene::updateControls() e updateCamera() ricevono invece il delta del frame; updateWorld() usa l'accumulatore. Il codice non impone un massimo di passi di recupero: un frame molto lungo può produrre molti update consecutivi.

Game moltiplica il delta per un fattore di scala temporale prima di consegnarlo alle scene. I tasti Ctrl+Shift+Su/Giù cambiano quel fattore; non cambiano _dt e non sono un controllo dell'FPS. currentFPS() riporta la misura aggiornata periodicamente dal contatore dei frame.

Il refresh del monitor e il VSync regolano la presentazione dei frame; _dt regola la simulazione. In Box2DPlatformer anche i controlli che applicano forze vengono eseguiti a ogni passo fisico tramite updatePhysicsControls(), e ogni oggetto riceve update(_dt). Questo evita che il movimento o le animazioni cambino velocità passando, per esempio, a un proiettore a 60 Hz. I dettagli sono in World, body e contatti.

Estendere il ciclo

Il punto normale di estensione non è riscrivere run(), ma:

  • ridefinire Game::init() per costruire le scene;
  • ridefinire GameScene::updateControls() per i comandi;
  • ridefinire Scene::event() per eventi specifici;
  • ridefinire Object::update() per il comportamento degli oggetti.

In questo modo il ciclo principale rimane unico e prevedibile.

Per capire come blocking e input interagiscono, prosegui con Input ed eventi.

Sorgenti di riferimento

Clone this wiki locally