Da questa base possiamo espandere il gameplay del nostro gioco e realizzare un clone del mitico Breakout, il tutto con pochissime modifiche al codice.
Se non conoscete Breakout, e ne dubito fortemente, e' un gioco sviluppato originariamente da Atari in cui il giocatore muovendo una racchetta sullo schermo doveva far rimbalzare una pallina contro dei mattoni posti nella parte alta di esso per eliminarli tutti.
In questa versione, mi sono ispirato molto all'edizione per atari2600, qualcuno notera' una leggera somiglianza con il classico :]
Partiamo sempre dalla funzione mainLoop(), a questo giro non conterra' piu' la chiamata alla funzione moveCpu(), in quanto in questo gioco si avra' una sola racchetta su schermo.
I movimenti della pallina e della racchetta sono quasi analoghi a quelli gia' visti nel Pong, la condizione di uscita dal gioco e' distruggere tutti i mattoni presenti nel livello.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
}
if(bricks==0 || Game.lives==0)// Quando non ci sono piu' mattoni o vite si termina
quit=1;
}
}
Il giocatore a questo giro puo' muoversi solo orizzontalmente, la pallina ovunque come sempre.
Come gia' visto in Pong, a seconda del tasto premuto, si incrementano o decrementano le coordinate del giocatore, in questo caso agiamo sull'asse X.int getInput()
{
while(SDL_PollEvent(&event))
if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE))
quit=1; //Permettiamo al giocatore di uscire premendo ESC
keystate = SDL_GetKeyState( NULL );
if(Ball.y+BALL_HEIGHT)// Se la pallina si trova sopra il giocatore ci si puo' muovere
{
if (keystate[SDLK_LEFT] && (Player1.x!=0))//Controlliamo che la racchetta rimanga nel campo
Player1.x--;
if (keystate[SDLK_RIGHT] && (Player1.x + PADDLE_WIDTH <= SCREEN_WIDTH))
Player1.x++;
}
}
Prima di proseguire, credo sia bene affrontare il discorso dei mattoncini per avere le idee piu' chiare su come il resto del programma lavori.
Per prima cosa aggiungiamo le costanti, variabili e strutture necessarie per far esistere i mattoni:
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define COLS 16
#define ROW 6
#define BRICK_WIDTH 20
#define BRICK_HEIGHT 10
#define BRICKS 96
#define PADDLE_WIDTH 60
#define PADDLE_HEIGHT 10
#define BALL_WIDTH 10
#define BALL_HEIGHT 10
int repeat, i,j,k, quit=0, bricks = 96;
SDL_Event event;
SDL_Surface *screen;
SDL_Rect rect;
Uint8 *keystate;
struct _Brick
{
int x, y, w, h, color, hit;
} Brick[96];
struct _Game
{
int player_score, lives;
} Game;
I nomi delle costanti sono autoesplicativi, non credo ci sia molto da dire al riguardo, aggiorniamo anche le nostre variabili globali e strutture ed aggiungiamone una nuova, Brick.La nuova struttura conterra' le informazioni di ogni singolo mattone, in tutto saranno 96.
Ora che abbiamo il tutto, aggiorniamo anche la nostra funzione initStuff() come segue:
void initStuff()
{
//Impostiamo gli elementi della struttura Brick
int j, k=0, index=0;
for (i = 0; i <ROWS; i++)
{
for (j = 0; j <COLS; j++)
{
Brick[index].x=j*BRICK_WIDTH;
Brick[index].y=i*BRICK_HEIGHT;
Brick[index].w=BRICK_WIDTH;
Brick[index].h=BRICK_HEIGHT;
Brick[index].color=k;
index++;
}
k++;
}
//Posizione iniziale della pallina
Ball.x = SCREEN_WIDTH/2;
Ball.y = 80;
//Velocita' della pallina
Ball.speedX = 1;
Ball.speedY = 1;
//Numero di mattoni iniziale (96)
bricks=BRICKS;
//Posizione iniziale Player1
Player.x = SCREEN_WIDTH/2 - PADDLE_WIDTH/2;
Player.y = SCREEN_HEIGHT - 20;
srand(time(NULL));
// Traiettori casuale per la pallina
if ((int)(2.0 * rand() / (RAND_MAX + 1.0)))
return Ball.slopeX *= -1;
if ((int)(2.0 * rand() / (RAND_MAX + 1.0)))
return Ball.slopeY *= -1;
//Impostiamo il numero di tentativi
Game.lives=3;
}
La modifica sostanziale e' data da due cicli for annidati ad inizio funzione, il ciclo piu' interno imposta la posizione dei mattoni da sinistra verso destra, realizzando colonne di mattoni.Il ciclo piu' esterno, imposta l'inizio di una nuova riga su cui piazzare i mattoni, dall'alto verso il basso.
Le coordinate X e Y dei mattoni sono date dal prodotto dell'indice del ciclo per la larghezza o altezza del mattone, per le colonne ad esempio.
Al primo giro la posizione X sara' data da 0*20 = 0, al secondo da 1*20 = 20 e cosi' via, ogni 20 pixel verra' piazzato un mattone a destra di quello precedente, nel ciclo esterno invece gestiremo in maniera analoga il posizionamento verticale.
Con questi due cicli possiamo impostare le coordinate di ogni singolo elemento Brick quasi come stessimo leggendo una matrice, la variabile index ci aiuta nel impostare ogni elemento della struttura Brick.
Passiamo alla funzione MoveBall() adesso, il tutto e' gestito come nel vecchio progetto, in aggiunta e' stata inserito un controllo su eventuali collisioni tra pallina e mattone:
void MoveBall()
{
//Collisione con i bordi dello schermo
if (Ball.x + BALL_WIDTH >= SCREEN_WIDTH | Ball.x < 0)
Ball.slopeX *=-1;
if(Ball.y < 0)
Ball.slopeY *=-1;
//Collisione con il fondo
if(Ball.y > SCREEN_HEIGHT - BALL_HEIGHT)
{
Ball.slopeY *= -1;
Game.lives--;
Ball.x = SCREEN_WIDTH/2;
Ball.y = 80;
}
//Rimbalzo sulla racchetta
if ((Ball.y + BALL_HEIGHT == Player.y) && (Ball.x >= Player.x && Ball.x <= Player.x + PADDLE_WIDTH))
{
Ball.slopeY *= -1;
}
//Abbiamo colpito un mattone?
for(i=0; i<BRICKS; i++)
{
if(Brick[i].hit ==0)
{
if (rectCollision(Ball.x+Ball.slopeX, Ball.y, BALL_WIDTH, BALL_HEIGHT,
Brick[i].x, Brick[i].y, Brick[i].w, Brick[i].h))
{
Brick[i].hit = 1;
Ball.slopeX *= -1;
bricks--;
Game.player_score += 10 - Brick[i].color;
}
else if (rectCollision(Ball.x, Ball.y+Ball.slopeY, BALL_WIDTH, BALL_HEIGHT,
Brick[i].x, Brick[i].y, Brick[i].w, Brick[i].h))
{
Brick[i].hit = 1;
Ball.slopeY *= -1;
bricks--;
Game.player_score += 10 - Brick[i].color;
} } }
Ball.x += Ball.speedX * Ball.slopeX ;
Ball.y += Ball.speedY * Ball.slopeY ;
}
Per controllare se vi e' collisione fra pallina ed un qualche mattone, eseguiamo un ciclo di tutti gli elementi Brick, quando uno di essi presenta la variabile hit (colpito) posta a zero, eseguiamo un controllo di tipo rectCollision sulle rispettive coordinate.Il primo controllo lo si effettua nel caso la pallina colpisca il mattone a destra o a sinistra, il secondo invece nel caso lo colpisca da sotto o da sopra.
Nel caso vi sia collisione, si sottrae 1 dalla variabile bricks (che tiene conto dei mattoni rimasti), si imposta il valore hit di Brick ad 1, si inverte la direzione della pallina e si somma il punteggio.
Un ultima modifica sostanziale l'abbiamo nel nostro metodo Draw(), come si puo' ben capire bisogna aggiornare il tutto per poter disegnare i nostri mattoni.
void draw()
{
for (i = 0; i <BRICKS; i++)
{
if(Brick[i].hit ==0)
{
if(Brick[i].color==0)
DrawRect(Brick[i].x, Brick[i].y, BRICK_WIDTH, BRICK_HEIGHT, 0x8B15AA);
if(Brick[i].color==1)
DrawRect(Brick[i].x, Brick[i].y, BRICK_WIDTH, BRICK_HEIGHT, 0xE73595);
if(Brick[i].color==2)
DrawRect(Brick[i].x, Brick[i].y, BRICK_WIDTH, BRICK_HEIGHT, 0xC91495);
if(Brick[i].color==3)
DrawRect(Brick[i].x, Brick[i].y, BRICK_WIDTH, BRICK_HEIGHT, 0xFF9D2E);
if(Brick[i].color==4)
DrawRect(Brick[i].x, Brick[i].y, BRICK_WIDTH, BRICK_HEIGHT, 0xB5DD56);
if(Brick[i].color==5)
DrawRect(Brick[i].x, Brick[i].y, BRICK_WIDTH, BRICK_HEIGHT, 0x7F6DFF);
}
}
//Players
DrawRect(Player.x, Player.y, PADDLE_WIDTH, PADDLE_HEIGHT, 0x8B15AA);
//Ball
DrawRect(Ball.x, Ball.y, BALL_WIDTH, BALL_HEIGHT, 0x8B15AA);
}
Ricordiamoci di aggiornare la riga finale della funzione main() per mostrare il punteggio e siamo apposto, abbiamo appena realizzato un gioco di breakout in meno di 15 minuti!printf("Thanks for playing!\nYour score was %d", Game.player_score);
Anche questo piccolo gioco come il precedente puo' essere arricchito con suoni e frivolezze grafiche se lo si desidera.Questa e' una versione semplificata a scopo didattico di RetroBreak, scaricabile dal sito Pix3lWorkshop sotto la sezione Retroplay Series.
Nessun commento :
Posta un commento