# Capitolo 5: Architettura del Codice ## Panoramica della Struttura Il progetto è organizzato in modo modulare con separazione delle responsabilità: ``` shader/ ├── config/ │ └── settings.py # Configurazione centralizzata ├── src/ │ ├── app.py # Applicazione principale │ ├── camera/ │ │ └── camera.py # Sistema camera │ ├── terrain/ │ │ └── generator.py # Generazione terreno │ └── rendering/ │ └── terrain_renderer.py # Rendering OpenGL └── main.py # Entry point ``` ## Principi di Design ### Separation of Concerns Ogni modulo ha una responsabilità specifica: - **Config**: Impostazioni - **Camera**: Vista 3D - **Generator**: Creazione terreno - **Renderer**: Visualizzazione - **App**: Coordinamento ### Single Responsibility Ogni classe fa una cosa e la fa bene. ### Dependency Injection Le classi ricevono dipendenze tramite parametri del costruttore. ## Modulo: config/settings.py Contiene tutte le costanti configurabili: ```python # Finestra WINDOW_WIDTH = 1200 WINDOW_HEIGHT = 800 FPS = 60 # Terreno TERRAIN = { 'grid_size': 20, 'tile_width': 30.0, 'noise_scale': 8.0, 'octaves': 4, ... } # Camera CAMERA = { 'initial_distance': 800.0, 'zoom_speed': 10.0, ... } # Rendering RENDERING = { 'grid_line_width': 5.0, 'background_color': (0.53, 0.81, 0.92, 1.0), ... } # Biomi BIOME_COLORS = {...} BIOME_THRESHOLDS = {...} ``` **Vantaggi**: - ✅ Tutto in un posto - ✅ Facile da modificare - ✅ Nessun valore "magico" nel codice ## Modulo: src/terrain/generator.py ### Classe TerrainGenerator Responsabilità: Generare heightmap usando Perlin Noise ```python class TerrainGenerator: def __init__(self, config): # Carica parametri da config self.grid_size = config['grid_size'] self.scale = config['noise_scale'] self.octaves = config['noise_octaves'] ... def generate(self): """Genera e restituisce heightmap 2D""" heightmap = np.zeros((self.grid_size, self.grid_size)) for i in range(self.grid_size): for j in range(self.grid_size): height = noise.pnoise2(...) heightmap[i][j] = (height + 0.5) * self.height_multiplier return heightmap def _smooth_terrain(self, heightmap): """Applica smoothing (opzionale)""" # Media dei vicini ... ``` **Input**: Configurazione **Output**: Array 2D con altezze **Dipendenze**: NumPy, noise library ## Modulo: src/rendering/terrain_renderer.py ### Classe TerrainRenderer Responsabilità: Disegnare il terreno con OpenGL ```python class TerrainRenderer: def __init__(self, terrain_config, rendering_config, biome_colors, biome_thresholds): self.tile_width = terrain_config['tile_width'] self.colors = biome_colors self.thresholds = biome_thresholds ... self.heightmap = None def set_heightmap(self, heightmap): """Imposta heightmap da renderizzare""" self.heightmap = heightmap def render(self): """Disegna tutto il terreno""" self._render_tiles() self._draw_grid() def draw_tile(self, x, z, h1, h2, h3, h4): """Disegna una singola tile""" # Faccia superiore glBegin(GL_QUADS) glColor3f(*color) glVertex3f(...) glEnd() # Facce laterali con shading ... def get_color_for_height(self, height): """Mappa altezza → bioma → colore""" if height < self.thresholds['water']: return self.colors['water'] ... ``` **Input**: Heightmap, configurazione **Output**: Draw calls OpenGL **Dipendenze**: OpenGL ## Modulo: src/camera/camera.py ### Classe Camera Responsabilità: Gestire vista 3D e input utente ```python class Camera: def __init__(self, config): self.distance = config['initial_distance'] self.height = config['initial_height'] self.zoom_speed = config['zoom_speed'] ... def handle_input(self, keys): """Processa input tastiera""" if keys[pygame.K_UP]: self.distance -= self.zoom_speed ... def apply(self, aspect_ratio): """Applica trasformazioni camera""" self.setup_projection(aspect_ratio) self.setup_modelview() def setup_projection(self, aspect_ratio): """Imposta matrice proiezione""" glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(self.fov, aspect_ratio, ...) def setup_modelview(self): """Imposta matrice vista""" glMatrixMode(GL_MODELVIEW) glLoadIdentity() gluLookAt(...) ``` **Input**: Tasti premuti **Output**: Matrici OpenGL **Dipendenze**: OpenGL, Pygame ## Modulo: src/app.py ### Classe IsometricTerrainApp Responsabilità: Coordinare tutti i componenti ```python class IsometricTerrainApp: def __init__(self, config): # Inizializza Pygame e OpenGL pygame.init() self.display = (config.WINDOW_WIDTH, config.WINDOW_HEIGHT) pygame.display.set_mode(self.display, DOUBLEBUF | OPENGL) # Crea componenti self.camera = Camera(config.CAMERA) self.terrain_generator = TerrainGenerator(config.TERRAIN) self.renderer = TerrainRenderer(...) # Setup OpenGL self._setup_opengl() # Genera terreno heightmap = self.terrain_generator.generate() self.renderer.set_heightmap(heightmap) self.clock = pygame.time.Clock() def _setup_opengl(self): """Configura stati OpenGL""" glEnable(GL_DEPTH_TEST) glEnable(GL_LIGHTING) ... def run(self): """Loop principale applicazione""" self.running = True while self.running: self.handle_events() self.update() self.render() self.clock.tick(self.config.FPS) def handle_events(self): """Gestisce eventi Pygame""" for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_r: self._regenerate_terrain() def update(self): """Aggiorna stato""" keys = pygame.key.get_pressed() self.camera.handle_input(keys) def render(self): """Renderizza scena""" glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glClearColor(*self.config.RENDERING['background_color']) aspect_ratio = self.display[0] / self.display[1] self.camera.apply(aspect_ratio) self.renderer.render() pygame.display.flip() ``` ## Modulo: main.py Entry point dell'applicazione: ```python #!/usr/bin/env python3 import sys import os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from config import settings from src.app import IsometricTerrainApp def main(): try: app = IsometricTerrainApp(settings) app.run() except KeyboardInterrupt: print("\nExiting...") sys.exit(0) if __name__ == "__main__": main() ``` ## Flusso dell'Applicazione ``` 1. AVVIO main.py └─→ Importa settings └─→ Crea IsometricTerrainApp 2. INIZIALIZZAZIONE IsometricTerrainApp.__init__() ├─→ Crea finestra Pygame/OpenGL ├─→ Crea Camera ├─→ Crea TerrainGenerator │ └─→ Genera heightmap ├─→ Crea TerrainRenderer │ └─→ Riceve heightmap └─→ Setup stati OpenGL 3. LOOP PRINCIPALE (60 FPS) app.run() │ ├─→ handle_events() │ └─→ ESC/R/Close window │ ├─→ update() │ └─→ camera.handle_input(keys) │ └─→ Aggiorna distance/height │ ├─→ render() │ ├─→ Pulisci buffer │ ├─→ camera.apply() │ │ ├─→ setup_projection() │ │ └─→ setup_modelview() │ ├─→ renderer.render() │ │ ├─→ Disegna tile │ │ └─→ Disegna grid │ └─→ pygame.display.flip() │ └─→ clock.tick(60) # Limita a 60 FPS 4. CHIUSURA pygame.quit() ``` ## Pattern di Design Utilizzati ### 1. Configuration Object Tutte le impostazioni in un oggetto centralizzato. ### 2. Dependency Injection ```python # Le dipendenze sono passate esplicitamente camera = Camera(config) renderer = TerrainRenderer(config, colors, thresholds) ``` ### 3. Single Responsibility Principle Ogni classe ha un solo motivo per cambiare. ### 4. Composition Over Inheritance App compone Camera, Generator, Renderer invece di ereditare. ### 5. Encapsulation Dettagli implementativi nascosti (metodi `_private`). ## Interazioni tra Componenti ``` ┌─────────────────┐ │ main.py │ └────────┬────────┘ │ crea ↓ ┌─────────────────────────┐ │ IsometricTerrainApp │ ├─────────────────────────┤ │ + camera │←─────┐ │ + terrain_generator │←─────┼─── Composizione │ + renderer │←─────┘ └───────┬─────────────────┘ │ ├──→ camera.handle_input(keys) ├──→ camera.apply(aspect) ├──→ terrain_generator.generate() └──→ renderer.render() ┌────────────┐ heightmap ┌─────────────────┐ │ Generator │──────────────→│ Renderer │ └────────────┘ └─────────────────┘ ``` ## Vantaggi dell'Architettura ### ✅ Modularità Ogni componente è indipendente e sostituibile. ### ✅ Testabilità Puoi testare Generator senza Renderer o Camera. ### ✅ Manutenibilità Modifiche localizzate, nessun effetto a cascata. ### ✅ Riutilizzabilità TerrainGenerator può essere usato in altri progetti. ### ✅ Leggibilità Codice organizzato logicamente, facile da navigare. ## Estensione del Progetto ### Aggiungere un Nuovo Bioma **1. Config**: ```python # config/settings.py BIOME_COLORS['lava'] = (1.0, 0.3, 0.0) BIOME_THRESHOLDS['lava'] = 85.0 ``` **2. Renderer**: ```python # src/rendering/terrain_renderer.py def get_color_for_height(self, height): ... elif height < self.thresholds['lava']: return self.colors['lava'] ``` ### Aggiungere Rotazione Camera **1. Config**: ```python # config/settings.py CAMERA = { ... 'rotation_angle': 0.0, 'rotation_speed': 2.0, } ``` **2. Camera**: ```python # src/camera/camera.py def handle_input(self, keys): ... if keys[pygame.K_q]: self.rotation_angle += self.rotation_speed if keys[pygame.K_e]: self.rotation_angle -= self.rotation_speed ``` ### Salvare/Caricare Terreni **1. Nuovo modulo**: ```python # src/terrain/serializer.py class TerrainSerializer: @staticmethod def save(heightmap, filename): np.save(filename, heightmap) @staticmethod def load(filename): return np.load(filename) ``` **2. App**: ```python # src/app.py def handle_events(self): ... elif event.key == pygame.K_s: TerrainSerializer.save(self.renderer.heightmap, 'terrain.npy') ``` ## Riepilogo ### Struttura - **config**: Configurazione - **src/camera**: Sistema vista - **src/terrain**: Generazione - **src/rendering**: Visualizzazione - **src/app**: Coordinamento ### Componenti Principali - **TerrainGenerator**: Crea heightmap - **TerrainRenderer**: Disegna mesh - **Camera**: Gestisce vista - **IsometricTerrainApp**: Orchestra tutto ### Flusso Init → Loop (Events → Update → Render) → Quit --- **Prossimo Capitolo**: [Il Sistema di Rendering →](06-rendering.md) [← Capitolo Precedente](04-perlin-noise.md) | [Torna all'indice](README.md)