Il mio primo gioco, il Pong

Retropong, un semplice remake realizzato da Pix3l.Nel precedente articolo, abbiamo trattato l'argomento collisioni semplici ed implementato una sorta di mini gioco che le sfruttasse.

Partendo da quella stessa base, possiamo dare vita ad uno dei piu' classici dei giochi, il Pong!
Per chi non lo conoscesse (mi metto le mani nei capelli!), il Pong e' un semplice simulatore del gioco del Ping Pong.
In questo gioco, due giocatori con relative racchette rappresentate da due semplici rettangoli, devono cercare di mandare una pallina oltre la racchetta dell'avversario per effettuare un punto. Semplice!

Potendo fare riferimento a precedenti post riguardo alcune parti del codice, mi limitero' a spiegare solamente alcuni aspetti tecnici del gameplay e di come possano venire implementati.
Un esempio completo e funzionante compreso di effetti sonori lo si puo' trovare presso il Pix3lWorkshop sotto la sezione Retroplay Series (Vedi Retropong).

Partiamo come sempre dal nostro mainLoop()
int mainLoop()
{
//main game loop
while(!quit)
{
repeat = fps_sync ();
for (i = 0; i < repeat ; i++)
{

getInput(); // Gestiamo l'input del giocatore
moveBall(); // Muoviamo la pallina in modo autonomo
moveCpu(); // Muoviamo la racchetta del computer
}
if
(Game.player_score==9 || Game.cpu_score==9)
quit=1;
}
}
Rispetto a quanto visto negli articoli precedenti, qui l'unica variazione e' l'aggiunta di due nuove funzioni atte a gestire i movimenti della pallina nel campo di gioco e della racchetta dell'avversario in modo autonomo.
Inoltre e' stato piazzato un controllo sul punteggio conseguito da uno dei due giocatori, al raggiungimento di un certo valore, la partita termina.

Il giocatore come anche la cpu, potranno muoversi solo verso l'alto e il basso, mentre la pallina praticamente in ogni direzione.
int getInput()
{
keystate = SDL_GetKeyState( NULL );
if(Ball.x>Player1.x) // Se la pallina si trova davanti al giocatore ci si puo' muovere
{
if (keystate[SDLK_UP] && (Player1.y!=0))
//Controlliamo che la racchetta rimanga nel campo
Player1.y--;
if (keystate[SDLK_DOWN] && (Player1.y + PADDLE_HEIGHT != SCREEN_HEIGHT))
Player1.y++;
}
}

// In maniera analoga si aggiorna la posizione della cpu

void MoveCpu()
{
if(Ball.slopeX > 0)
{
if(Player2.y < Ball.y && Player2.y + PADDLE_HEIGHT < SCREEN_HEIGHT)
Player2.y += 1;
if(Player2.y > Ball.y)
Player2.y -= 1;
}
}
Le coordinate del giocatore saranno incrementate e decrementate in base alla pressione dei tasti appositi, quelle della cpu invece solamente all'avvenire di certe condizioni.
Se la traiettoria della pallina (slope) e' in direzione della cpu allora questa controlla sua posizione rispetta ad essa, se la pallina si trova al di sopra di essa allora sale, in caso contrario scende.
Limitare il movimento della cpu solo al momento in cui la pallina gli sta andando incontro e' di fondamentale importanza per un buon gameplay, in quanto se non vi fosse questo controllo sarebbe impossibile per il giocatore poter vincere in quanto la racchetta dell'avversario sara' sempre all'altezza giusta per poter rispondere.

Il movimento della pallina invece, viene gestito in maniera pseudo casuale e sfrutta la funzione gia' vista rectCollision per gestire eventuali collisioni con le racchette.
void moveBall()
{
//P1 ha fatto punto
if (Ball.x >= (SCREEN_WIDTH - BALL_WIDTH))
{ Game.player_score++; initStuff(); }

//P2 ha fatto punto
if (Ball.x < 0)
{ Game.cpu_score++; initStuff(); }
// Rimbalziamo contro i boardi alti e bassi dello schermo
if(Ball.y < 0 || Ball.y > SCREEN_HEIGHT - BALL_HEIGHT)
Ball.slopeY *= -1;

// Rimbalziamo contro le racchette
if(rectCollision(Ball.x, Ball.y, BALL_WIDTH, BALL_HEIGHT, Player1.x, Player1.y, PADDLE_WIDTH, PADDLE_HEIGHT)
||rectCollision(Ball.x, Ball.y, BALL_WIDTH, BALL_HEIGHT, Player2.x, Player2.y, PADDLE_WIDTH, PADDLE_HEIGHT))
{ Ball.slopeX *= -1; Ball.slopeY = rand() % 5 -2; }

// Aggiorniamo le coordinate della pallina nel campo di gioco
Ball.x += Ball.speedX * Ball.slopeX ;
Ball.y += Ball.speedY * Ball.slopeY ;
}
Per prima cosa controlliamo la posizione della pallina, se quest'ultima ha raggiunto i bordi laterali del campo di gioco, assegniamo un punto al giocatore o alla cpu a seconda dei casi e reimpostiamo la posizione degli oggetti.

Il secondo controllo consiste nel controllare le coordinate Y della nostra pallina, se superano i limiti superiore e inferiore del campo di gioco, invertiremo la traiettoria Y della pallina in modo da avere un rimbalzo.

Nel terzo controllo, grazie al supporto della funzione gia' vista rectCollision, controlleremo eventuali collisioni tra pallina e racchette, effettuando un rimbalzo in direzione opposta sull'asse X ed uno sull'asse Y in direzione casuale dando cosi' un pizzico di brio al gioco.

L'ultimo passaggio semplicemente aggiorna la posizione della pallina alle nuove coordinate, sommandole alla sua velocita' moltiplicata per la traiettoria. Notare che moltiplicando per la traiettoria potremmo avere valori anche negativi nel caso questa fosse negativa, cio' e' importante per permettere alla pallina il movimento in tutte le direzioni, senza questo piccolo accorgimento la pallina potra' muoversi solo in modo incrementale verso destra.

Ora che abbiamo la base tecnica del gioco, possiamo passare tranquillamente al disegno ma prima definiamo qualche costante, struttura e variabili globali che abbiamo usato nel codice precedente e anche qualche include necessario.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <SDL/SDL.h>

#define SCREEN_WIDTH 320

#define SCREEN_HEIGHT 240

#define PADDLE_WIDTH 10
#define PADDLE_HEIGHT 60

#define BALL_WIDTH 10
#define BALL_HEIGHT 10

struct _Player
{
int x, y;
} Player1, Player2;

struct _Ball
{
int x, y;
int slopeX, slopeY, speedX, speedY;
} Ball;

struct _Game
{
int player_score, cpu_score,;
} Game;
// Variabili globali
static int t, tl = 0, frequency = 1000 / 100, temp, t2;
int repeat, i, quit=0;
SDL_Event event;
SDL_Surface *screen;
SDL_Rect rect; //Struttura per la grafica di gioco
Uint8 *keystate; // keyboard state

Lo schermo avra' una dimensione di 320x240 pixel, mentre la nostra racchetta sara' un rettangolo di dimensione 10x60, la nostra pallina sara' un cubetto 10x10.
L'apposita struttura _Player conterra' le informazioni relative alla posizione delle racchette nel campo di gioco, come quella _Ball per la pallina.
Mentre struttura _Game conterra' le informazioni riguardo il punteggio dei due giocatori, nulla di piu'.

Implementiamo ora la nostra funzione Draw() per disegnare gli oggetti sullo schermo, che semplicemente si avvale della gia' vista DrawRect().
void Draw()
{
//Players
DrawRect(Player1.x, Player1.y, PADDLE_WIDTH, PADDLE_HEIGHT, 0xC046C0);
DrawRect(Player2.x, Player2.y, PADDLE_WIDTH, PADDLE_HEIGHT, 0xC046C0);

//Ball
DrawRect(Ball.x, Ball.y, BALL_WIDTH, BALL_HEIGHT, 0xff0000);
}

Il nostro gioco e' quasi giunto al termine, rimangono solo due cose da implementare, una funzione di setup del gioco e la main().

void initStuff()
{
// Ball posizione iniziale
Ball.x = SCREEN_WIDTH/2;
Ball.y = SCREEN_HEIGHT/2;

// Player1 posizione iniziale
Player1.x = 20;
Player1.y = SCREEN_HEIGHT/2 - PADDLE_HEIGHT/2;

// Player2 posizione iniziale
Player2.x = SCREEN_WIDTH - 20;
Player2.y = SCREEN_HEIGHT/2 - PADDLE_HEIGHT/2;

Ball.speedX = 2;
Ball.speedY = 1;

// Traiettoria iniziale casuale
srand(time(NULL));
if ((int)(2.0 * rand() / (RAND_MAX + 1.0)))
Ball.slopeX *= -1;
if ((int)(2.0 * rand() / (RAND_MAX + 1.0)))
Ball.slopeY *= -1;
}

int main()
{
// init video stuff
if(SDL_Init(SDL_INIT_VIDEO) != 0)
{
fprintf(stderr, "Can't initialize SDL: %s\n", SDL_GetError());
exit(-1);
}
atexit(SDL_Quit);

// init screen
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 32, SDL_SWSURFACE);
if(screen == NULL)
{
fprintf(stderr, "Can't initialize SDL: %s\n", SDL_GetError());
exit(-1);
}

SDL_WM_SetCaption("IndieFanPong", "IndieFanPong");

initStuff(); // Impostiamo qualche valore iniziale
mainLoop();
printf("Thanks for playing!\nYour score was %d - %d", Game.player_score, Game.cpu_score);
SDL_Quit();
return 0;
}

Nella funzione initStuff() semplicemente impostiamo qualche valore iniziale come la posizione degli elementi di gioco e la velocita' della pallina, non che la sua traiettoria iniziale.
Piazziamo un messaggino di ringraziamento verso la fine della funzione, al termine della partita verra' mostrato insieme al punteggio di gioco.

Questo e' clone del gioco del Pong e' molto semplice qunato povero, puo' essere arricchito aggiungendo suoni o qualche fronzolo grafico ad esempio, o magari implementare una modalita' a due giocatori reali.
Questa versione presentata in questo articolo e' un figlio minore del gioco Retropong, scaricabile dal sito Pix3lWorkshop sotto la sezione Retroplay Series.

1 commento :

Emozione ha detto...

Io ho avuto l' onore di giocarci :)

Posta un commento

Related Posts Plugin for WordPress, Blogger...