Design architetturale
Il design architetturale del sistema è stato elaborato a partire dai requisiti funzionali e non funzionali identificati. L’obiettivo principale è stato creare una struttura modulare, performante ed estensibile che potesse gestire la complessità di un gioco tower defense come Wizards vs Trolls, garantendo alte performance con numerose entità simultanee e una chiara separazione delle responsabilità tra i vari componenti.
Pattern Architetturale MVC (Model-View-Controller)
Questo pattern è ampiamente utilizzato nello sviluppo di applicazioni software per separare la logica dalla sua rappresentazione grafica e dall’interazione con l’utente. I suoi tre componenti principali svolgono ruoli specifici:
-
Model: Il Model rappresenta il cuore dell’applicazione. Contiene i dati e la logica di business del sistema. La sua responsabilità è gestire lo stato dell’applicazione, manipolare i dati e implementare tutte le regole del gioco. È completamente disaccoppiato dalla rappresentazione grafica e non ha conoscenza dell’interfaccia utente. Nel nostro caso, il Model contiene tutte le entità di gioco (maghi, troll, proiettili), le loro caratteristiche, la logica di combattimento, movimento, gestione risorse e progressione delle ondate. Per implementare il Model abbiamo scelto di utilizzare il pattern Entity Component System (ECS), che verrà descritto in dettaglio nella sezione successiva
-
View: La View è l’interfaccia utente. Il suo scopo è presentare i dati del Model all’utente e raccogliere gli input dell’utente. La View non contiene alcuna logica di gioco e si limita a visualizzare lo stato corrente del Model. Nel nostro progetto, la View include la griglia di gioco, la visualizzazione delle entità (maghi, troll, proiettili), l’HUD con informazioni su elisir e ondata corrente, il menu principale e la schermata di Game Over. Quando l’utente interagisce con la View (ad esempio, cliccando per posizionare un mago), questa inoltra l’input al Controller per la sua elaborazione
-
Controller: Il Controller agisce come intermediario tra il Model e la View. Riceve l’input dall’utente tramite la View, lo elabora, aggiorna il Model di conseguenza e istruisce la View a riflettere i cambiamenti di stato. Nel nostro caso, il Controller coordina l’esecuzione del game loop, gestisce il posizionamento dei maghi, valida gli input dell’utente e determina le condizioni di game over. In sostanza, il Controller garantisce che le diverse parti dell’applicazione rimangano indipendenti e comunichino attraverso interfacce ben definite
Questa separazione è la motivazione per la quale abbiamo scelto di utilizzare il pattern MVC per il nostro progetto. Permette di sviluppare e testare ciascun componente in modo indipendente, facilita la manutenzione e l’estensione del sistema. Inoltre, consente di sostituire facilmente la View (ad esempio, passare da ScalaFX a un’altra libreria grafica) senza dover modificare il Model o il Controller.
Pattern Entity Component System (ECS) per il Model
All’interno del Model, abbiamo scelto di implementare la logica di gioco utilizzando il pattern Entity Component System (ECS), un pattern data-oriented ampiamente utilizzato nello sviluppo di videogiochi.
L’ECS si basa su tre concetti fondamentali:
-
Entity: Un identificatore unico che rappresenta un’entità di gioco (mago, troll, proiettile). L’Entity non contiene dati né comportamenti, è semplicemente un ID che collega diversi Component
-
Component: Strutture dati pure che rappresentano singoli aspetti di un’entità (posizione, salute, attacco, movimento). I Component non contengono logica, solo dati. Ad esempio,
PositionComponentcontiene le coordinate (x, y),HealthComponentla salute corrente e massima,AttackComponentil danno, raggio e cooldown -
System: Contengono tutta la logica di gioco e operano su gruppi di entità che possiedono specifici Component. Ogni System ha una responsabilità unica e ben definita:
MovementSystemaggiorna le posizioni,CombatSystemgestisce gli attacchi,ElixirSystemgestisce le risorse,HealthSystemapplica i danni
Questa scelta architetturale è stata motivata da diversi fattori:
-
Performance: La separazione tra dati (Component) e logica (System) favorisce la località dei dati in memoria, migliorando l’efficienza della cache CPU. Questo è cruciale in un tower defense dove possono esistere decine di entità simultanee che devono essere aggiornate 60 volte al secondo
-
Composizione flessibile: Invece di gerarchie di ereditarietà rigide, le entità sono definite dalla combinazione di Component che possiedono. Un mago attaccante ha Component di posizione, salute e attacco, mentre un Mago Generatore ha un Component generatore di elisir al posto dell’attacco. Questo permette di creare nuovi tipi di entità senza modificare gerarchie esistenti
-
Modularità: Ogni System gestisce un aspetto specifico del gioco in modo indipendente, rendendo il codice più comprensibile e manutenibile
-
Estensibilità: Aggiungere nuove funzionalità significa creare nuovi Component e/o System senza modificare il codice esistente, rispettando il principio Open/Closed
-
Testabilità: Ogni System può essere testato in isolamento creando scenari specifici con solo le entità e i Component necessari
Struttura del Progetto

La struttura del progetto è organizzata in quattro moduli principali, che riflettono una chiara separazione delle responsabilità ispirata al pattern Model-View-Controller (MVC), con una distinzione esplicita per il motore di gioco (Engine).
-
Model (ECS): Rappresenta il nucleo logico del gioco. Implementato con il pattern Entity-Component-System, questo modulo definisce la struttura dei dati (
Component), la logica di gioco (System) e il contenitore dello stato del mondo (World). È completamente disaccoppiato dagli altri moduli e si occupa esclusivamente delle regole e dello stato della simulazione. La EntityFactory astrae la creazione delle entità di gioco -
Engine: È il cuore pulsante dell’applicazione. Il
GameEnginegestisce il ciclo di vita del gioco, orchestrando le transizioni di stato (es.Playing,Paused) definite inGameState. Si appoggia alGameLoopper garantire un’esecuzione a timestep fisso, assicurando che la logica di gioco progredisca in modo deterministico e indipendente dal frame rate -
Controller: Agisce come intermediario, collegando l’input dell’utente, la logica di gioco e il motore. Il
GameControllerriceve eventi dall’EventHandlere comanda alGameEnginedi aggiornare lo stato. IlGameSystemsStateaggrega e gestisce l’esecuzione ordinata di tutti i sistemi logici del Model -
View: Costituisce l’interfaccia grafica. Il
ViewControllergestisce la navigazione tra le diverse schermate (GameView,MenuView, etc.). LaGameViewsi occupa del rendering degli elementi di gioco, inclusi componenti specifici comeShopPaneleWavePanel. LaViewcattura le interazioni dell’utente e le inoltra alControllersotto forma di eventi, senza contenere alcuna logica di gioco
Principi di Programmazione Funzionale
L’intero progetto è stato sviluppato seguendo principi di programmazione funzionale, come richiesto dai requisiti:
-
Immutabilità: Tutte le strutture dati (Component, configurazioni) sono immutabili. I System restituiscono nuove versioni di sé stessi invece di modificare lo stato interno. Questo elimina bug legati a modifiche inattese e garantisce thread-safety
-
Composizione su Ereditarietà: L’ECS favorisce la composizione tramite Component invece di gerarchie di classi rigide
-
Pure Function: I System sono implementati come funzioni il più possibile pure: dato un input (World), producono un output (nuovo World) in modo deterministico
-
Type Classes: Utilizzo di given instances e type classes per il polimorfismo ad-hoc nella creazione delle entità, garantendo type-safety ed estensibilità
-
Opaque Types: Utilizzo di opaque types (EntityId) per type-safety senza overhead runtime