Come creare un gioco in C++ esempio 1

Guida pratica su come creare un semplice gioco in C++ esempio 1

In questa guida vedremo come creare un gioco in C++ senza usare librerie grafiche , il gioco sarà simile a snake ma non avremo un serpente che cresce ogni punto che mangia, in questa versione avremo un cursore “ + ”  che dovrà mangiare i punti “ o ” e fare attenzione ai muri “ # ” che compariranno mangiando.

Faremo muovere il nostro cursore con i tasti W, A, S, D e renderemo possibile di scegliere la difficoltà del gioco.

Le librerie che utilizzeremo nel progetto esempio sono :

#include <iostream> 
#include <conio.h>
#include <Windows.h>
#include <time.h>

Ricordiamoci di usare :

using namespace std;

Le variabili che ci serve dichiarare e inizializzare prima di iniziare a creare il nostro gioco sono queste, in questo caso sono dichiarate come globali quindi prima del main :

#define difficile 30
#define medio 75
#define facile 150

bool gameOver;
const int larghezza = 40;
const int altezza = 20;
int x, y, puntoX, puntoY, score;
int wallX[100], wallY[100], nWall = 0;
enum Direzione { STOP = 0, LEFT, RIGHT, UP, DOWN };
Direzione dir;
int difficulty;

Vedremo man mano a cosa ci serve ogni singola variabile anche se è già intuibile dai nomi, ora iniziamo a scrivere le funzioni che ci servono :

–> Inizializzare una nuova partita

void Setup() {

srand(time(NULL));
gameOver = false;
dir = STOP;
x = larghezza/2;
y = altezza / 2;
puntoX = rand() % larghezza;
puntoY = rand() % altezza;
score = 0;

}

Questa funzione serve ad inizializzare le variabili ad uno stato di inizio partita , ” gameOver ” quando è true segnala che abbiamo perso, quindi di base la settiamo false, ” dir ” indica la direzione che sta percorrendo il nostro cursore, inizialmente sarà stop , quindi quando avvieremo una nuova partita il gioco sembrerà in pausa .

Le variabili x, y rappresentano le coordinate del nostro cursore, ad inizio partita vediamo che nell’esempio lo posizioniamo al centro del nostro spazio di gioco delimitato da altezza e larghezza, i livelli di gioco facile, medio, difficile ci serviranno per impostare la velocità del cursore.

–> Utilizzare i comandi

void Input() {

if (_kbhit()) {

switch (_getch()) {

case ‘a’:
case ‘A’:
dir = LEFT;
break;

case ‘d’:
case ‘D’:
dir = RIGHT;
break;

case ‘w’:
case ‘W’:
dir = UP;
break;

case ‘s’:
case ‘S’:
dir = DOWN;
break;

case ‘O’:
case ‘o’:
gameOver = true;
break;

case ‘p’:
case ‘P’:
dir = STOP;
break;

}
}
}

Questa funzione serve per poter far muovere il cursore nella direzione corrispondente al comando premuto, la funzione _kbhit() serve a rilevare se è stato premuto un tasto, qundi se ci restituisce true passiamo allo switch che utilizza nella condizione _getch() che ritorna quale tasto è stato schiacciato, quindi ricapitolando kbith ci permette di arrivare allo switch solo quando premiamo un tasto e tramite quest’ultimo il codice imposta quale direzione far prendere al puntatore assegnandola alla variabile dir.

I tasti p/P e o/O sono rispettivamente per mettere in pausa ed uscire dalla partita.

–> Implementiamo la logica del gioco

void Logic() {

/* 1 */

switch (dir) {

case LEFT:
x–;
break;
case RIGHT:
x++;
break;
case UP:
y–;
break;
case DOWN:
y++;
break;
default:
break;

}

in questa prima parte della funzione implementiamo una parte del meccanismo che permetterà al cursore di spostarsi, immaginiamo lo spazio di gioco come una matrice, facendo dei +1 o -1 ciclici sulle coordinate del cursore a seconda della variabile dir lo faremo spostare all’interno della matrice quando la stamperemo a video.

/* 2 */

if (x >= larghezza || x <= 0 || y >= altezza || y <= 0 )
gameOver = true;

for (int i = 0;i < nWall;i++) {
if (x == wallX[i] && y == wallY[i])
gameOver = true;
}

Nella seconda parte della funzione stabiliamo che le condizioni per perdere la partita sono due , se il puntatore si trova sui margini o se si trova su un muro, i muri da come si può vedere nel codice sono rappresentati da due vettori wallX e wallY che contengono rispettivamente le coordinate del nodo x e y del muro i.

/* 3 */

if (x == puntoX && y == puntoY) {

score += 1;
nWall += 1;
puntoX = rand() % (larghezza);
puntoY = rand() % (altezza);

wallX[nWall-1] = rand() % (altezza);
wallY[nWall-1] = rand() % (larghezza);

if ((wallX[nWall-1] == puntoX && wallY[nWall-1] == puntoY) || (wallX[nWall-2] == wallX[nWall – 1] && wallY[nWall-2 ] == wallY[nWall – 1])) {

wallX[nWall] += 20;
wallY[nWall] += 20;

}
}
}

Qui nella terza parte stiamo stabilendo cosa accade quando il nostro cursore si trova su un punto ” (x == puntoX && y == puntoY) ” , come si vede aumentiamo lo score e il numero di muri di uno e generiamo un nuovo punto da mangiare cambiando le variabili puntoX e puntoY, successivamente generiamo le coordinate per i nuovi muri da far comparire.

L’ultima condizione non è perfetta e puo essere perfezionata, serve per far capire che rendendo casuale la generazione delle coordinate degli ostacoli/punti potrebbe capitare che le coordinate di un muro siano anche quelle del punto o viceversa , le coordinate di due muri generati potrebbero anche coincidere o ancora potrebbe comparire un muro sul cursore, game over.

Ho scelto di non riapplicare il modulo per l’altezza e larghezza sulle coordinate degli elementi che si sovrappongono per evitare condizioni svantaggiose all’aumentare dello score, permettendo a questi di poter assumere coordinate al di fuori dello spazio di gioco cosi da non essere visualizzati e dare meno disturbo.

–> Mostriamo sullo schermo

void Draw() {

system(“cls”);
for (int i = 0;i< larghezza ;i++)
cout << “#”;
cout << endl;

for (int i = 0;i< altezza;i++) {
for (int j = 0;j< larghezza;j++) {

if (j == 0)
cout << “#”;

else if (i == y && j == x)
cout << “+”;
else if (i == puntoY && j == puntoX)
cout << “o”;

else {

bool stampa = false;
for (int k = 0;k < nWall;k++) {

if (wallX[k] == j && wallY[k] == i) {
cout << “#”;
stampa = true;
}
}

if (!stampa)
cout << ” “;
}
if (j == larghezza-1)
cout << “#”;
}
cout << endl;
}

for (int i = 0;i < larghezza + 2;i++)
cout << “#”;

cout << endl;
cout << “score :” << score << endl;
cout << “wX :” << wallX[nWall] << ” wY :” << wallY[nWall] << endl;

}

Questa è la funzione che da praticamente vita al nostro gioco, semplicemente scorre la matrice dello spazio di gioco e controlla ogni singola coordinata se è uno spazio vuoto o un muro / punto o il cursore quindi ne stampa il carattere associato, ad ogni chiamata in oltre pulisce il terminale prima di iniziare a stampare.

–> Mettiamo assieme

int main()

{

/* 1 */

system(“color 3B”);

cout << “\t EVITA I # \n \n”;
cout <<“comandi : \” W, A, S, D \” \n \n” << “premi un tasto per continuare()”;

_getch();

system(“cls”);

cout << “scegli un livello di difficolta :” << endl << “\n 1=Difficile \n 2=medio \n 3=facile \n ” << endl << ” scelta : “;
cin >> difficulty;

while (x < 1 && x>3) {

cout << “scegli un livello di difficolta :” << endl << “\n 1=Difficile \n 2=medio \n 3=facile \n ” << endl << ” scelta : “;
cin >> difficulty;

}

switch (difficulty) {

case 1:
difficulty = difficile;
break;
case 2:
difficulty= medio;
break;
case 3:
difficulty= facile;
break;

}

Setup();

In questa prima parte del main implementiamo un menù in cui scegliere la difficoltà

/* 2 */

while (!gameOver) {

Input();
Logic();
Draw();

Sleep(difficulty);

}

system(“color F”);
cout << “\n OOOOOPS FAI ATTENZIONE \n \n”;

system(“PAUSE”);

}

Nella seconda parte facciamo partire il gioco mettendo in un while true le funzioni Input , Logic , Draw la velocità di stampa e quindi la velocità di gioco è impostata con la difficoltà

–> Un ultimo commento

Il gioco risultante è alquanto semplice è soffre di difetti per rendere più comprensibile la lettura del codice in fase di apprendimento, lasciamo al lettore il compito di migliorarlo se vuole sperimentare quanto appreso.