mercoledì 27 luglio 2011

Tutorial: scriviamo un gioco in C++... Tris! (parte 3)

Dopo aver visto come impostare l'ambiente di sviluppo e linkare correttamente le librerie scelte, passiamo finalmente a fare sul serio!

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!

Articoli collegati:


  1. Tutorial: scriviamo un gioco in C++... Tris!
  2. Tutorial: scriviamo un gioco in C++... Tris! (parte 2)

Nessun commento:

Posta un commento