In questa terza parte, infatti, imbastiremo il codice che ci porterà a visualizzare la title screen e ad uscire premendo ESC.
Le prime classi
Vogliamo sviluppare il progetto con un modello a oggetti, quindi è il momento di fare una ragionata sulla struttura di classi che potrebbero servirci.
Faccio una piccola premessa a riguardo: lo scopo di questo tutorial è scrivere un gioco. E’ vero che molti componenti si possono (si devono!) riutilizzare tra diversi giochi, ma qui non faremo distinzioni nette tra componenti di engine e componenti di gioco. Scriveremo semplicemente il nostro Tris.
Avremo una classe principale, Tris, che governerà il gioco. Tale classe non sarà però un monolite che racchiude in sè tutti gli aspetti del sistema, bensì un saggio e illuminato sovrano che delegherà ad altri compiti ben specifici, limitandosi a coordinare ed organizzare il lavoro altrui.
Il primo dei compiti da delegare riguarda la creazione, l’inizializzazione e la distruzione del contesto grafico di SDL. Di questo si occuperà una classe apposita, che chiameremo SDLManager. La classe Tris utilizzerà SDLManager per impostare il sistema e per rilasciare le risorse a fine gioco. SDLManager sarà poi una ottima candidata al riutilizzo, ma non pensiamoci più di tanto per ora: restiamo concentrati sul gioco!
L’obiettivo dichiarato di questa terza puntata è però il disegno della title screen e la gestione della tastiera, in particolare della pressione del tasto ESC. Quel che dobbiamo fare è impostare una prima, grezza, versione del game loop, ovvero di quel ciclo infinito che prevede la rilevazione dell’input (la pressione del tasto ESC), l’aggiornamento delle entità coinvolte nel gioco (per ora nulla) e il disegno del frame corrente (la title screen).
Classe SDLManager
Bene, partiamo con la classe SDLManager. La definisco in questo modo:
#ifndef SDLMANAGER_H #define SDLMANAGER_H #include <SDL/SDL.h> #include <SDL/SDL_ttf.h> class SDLManager { public: SDLManager(); virtual ~SDLManager(); /** Inizializza il sistema SDL e SDL_ttf. * \return 0 in caso di successo o un codice di errore. */ int Init(); /** Termina l'esecuzione di SDL e SDL_ttf liberando le risorse */ void Quit(); protected: private: }; #endif // SDLMANAGER_H
La sua implementazione è piuttosto diretta. Non facciamo altro che spostare nei due metodi
Init
e Quit
le chiamate già messe nel main di test dipendenze, aggiungendo qualche controllo.#include "../include/SDLManager.h" #include <iostream> using namespace std; //----------------------------------------------------------------------------- SDLManager::SDLManager() { //ctor } //----------------------------------------------------------------------------- SDLManager::~SDLManager() { //dtor } //----------------------------------------------------------------------------- int SDLManager::Init() { if(SDL_Init(SDL_INIT_VIDEO) < 0) { cerr << "Errore inizializzando SDL: " << SDL_GetError() << endl; return -1; } if(TTF_Init() < 0) { cerr << "Errore inizializzando SDL_ttf: " << TTF_GetError() << endl; return -2; } if(SDL_SetVideoMode(320, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF) == 0) { cerr << "Impossibile impostare il video a 320x480, 32bpp, double buffer" << endl; return -3; } return 0; } //----------------------------------------------------------------------------- void SDLManager::Quit() { if(TTF_WasInit()) { TTF_Quit(); } if(SDL_WasInit(SDL_INIT_VIDEO)) { SDL_Quit(); } }
Ho scelto come risoluzione un 320x480 per due ragioni: tris è un gioco occasionale, da ufficio, da partitella mordi e fuggi e, possibilmente, da potersi nascondere rapidamente ;). Dovendo optare per dimensioni piccole ho preferito utilizzare una risoluzione tipica da smartphone... vuoi mai che un domani non lo si voglia portare su una piattaforma mobile?
Classe Tris
Possiamo ora definire la classe principale del gioco: Tris. Dovrà usare opportunamente SDLManager e gestire il game loop. Io la imposto così:
#ifndef TRIS_H #define TRIS_H #include "SDLManager.h" class Tris { public: Tris(); virtual ~Tris(); // gestione sistema int StartApplication(); void StopApplication(); // controllo stato bool IsRunning(); // main loop void HandleInput(); void Update(); void DrawAll(); protected: private: SDLManager* m_pSDLManager; bool m_IsRunning; }; #endif // TRIS_H
e la sua implementazione:
#include "../include/Tris.h" #include <iostream> using namespace std; //----------------------------------------------------------------------------- Tris::Tris() { //ctor m_pSDLManager = 0; m_IsRunning = false; } //----------------------------------------------------------------------------- Tris::~Tris() { //dtor delete m_pSDLManager; m_pSDLManager = 0; SDL_FreeSurface(tmp_TitleScreen); } //----------------------------------------------------------------------------- int Tris::StartApplication() { m_pSDLManager = new SDLManager(); int result = m_pSDLManager->Init(); if(result < 0) { return 1; // errore } m_IsRunning = true; // carico l'immagine della titleScreen SDL_Surface* pTitleImage = SDL_LoadBMP("title_screen.bmp"); if(!pTitleImage) { cerr << "Impossibile caricare title_screen.bmp" << endl; return 2; // errore } tmp_TitleScreen = SDL_DisplayFormat(pTitleImage); if(!tmp_TitleScreen) { // non riesco a creare la versione ottimizzata della surface... poco male, uso quella originale tmp_TitleScreen = pTitleImage; } else { SDL_FreeSurface(pTitleImage); } return 0; // tutto ok } //----------------------------------------------------------------------------- void Tris::StopApplication() { m_pSDLManager->Quit(); } //----------------------------------------------------------------------------- bool Tris::IsRunning() { return m_IsRunning; } //----------------------------------------------------------------------------- void Tris::HandleInput() { SDL_Event event; while(SDL_PollEvent(&event)) { switch(event.type) { case SDL_KEYDOWN: if(event.key.keysym.sym == SDLK_ESCAPE) { m_IsRunning = false; } break; } } } //----------------------------------------------------------------------------- void Tris::Update() { // per ora nulla } //----------------------------------------------------------------------------- void Tris::DrawAll() { SDL_Surface* screen = SDL_GetVideoSurface(); // pulisco lo schermo SDL_FillRect(screen, &screen->clip_rect, 0x000000); // disegno la title screen SDL_BlitSurface(tmp_TitleScreen, 0, screen, 0); // svuoto il buffer (invio le istruzioni per il disegno reale a schermo) SDL_Flip(screen); }
Il Main
Rimane solo da modificare opportunamente il main:
#include "include/Tris.h" int main(int argc, char** argv) { Tris game; if(game.StartApplication() < 0) { game.StopApplication(); exit(1); } while(game.IsRunning()) { game.HandleInput(); game.Update(); game.DrawAll(); } game.StopApplication(); return 0; }
Gestione degli errori
Se qualcosa va storto durante l’avvio del gioco il programma esce con codice 1. Sembra poco per una diagnostica efficace, ma abbiamo in realtà inserito delle chiamate a
std::cerr
nei punti in cui l’errore si è generato. SDL intercetta per noi lo stream di error standard e crea il file stderr.txt nella directory dell’eseguibile. Se il programma terminerà in modo inatteso non dovremo fare altro che leggere il file per un log di quanto è accaduto.NB: Allo stesso modo, lo stream di output standard
std::cout
viene reindirizzato nel file stdout.txt.Bene, compiliamo ed eseguiamo e questo è il risultato:
Nel prossimo post introdurremo lo stato di gioco e faremo un opportuno refactor per gestire in modo più strutturato la title screen.
Buon coding!
Nessun commento:
Posta un commento