Como Criar um Jogo de Snake em C++ com SFML?
Neste artigo, vou guiá-lo através do processo de criação de um jogo simples de Snake em C++ utilizando a biblioteca SFML. Vamos usar um código-fonte que já está parcialmente desenvolvido. Vou explicar cada parte do código e como ele contribui para o jogo como um todo.
Requisitos
Para seguir este tutorial, você precisará de:
Conhecimento básico em C++.
SFML instalada no seu sistema.
Código-fonte
O código-fonte está dividido em dois arquivos principais: main.cpp
e food.hpp
. O main.cpp
contém a lógica principal do jogo, enquanto food.hpp
lida com a geração e desenho dos alimentos para a cobra.
Arquivo food.hpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <cmath>
#include <vector>
#include <time.h>
#include <stdlib.h>
class Food {
private:
sf::CircleShape food_shape;
sf::Vector2i food_pos;
int W;
int PX;
public :
Food (int _PX, int _W) {
srand(time(NULL));
this->W = _W;
this->PX= _PX;
this->food_shape.setRadius(static_cast<float>(_PX/2));
this->food_shape.setFillColor(sf::Color::Green);
do {
this->food_pos = sf::Vector2i(std::abs((rand()%this->W)-PX),std::abs((rand()%this->W)-this->PX));
} while ((this->food_pos.x)%PX!=0||(this->food_pos.y)%PX!=0);
}
void draw_food (sf::RenderWindow& _window, std::vector<sf::Vector2i>& _snake_body, int& _score, bool& _ate) {
if (_snake_body[0]==this->food_pos) {
bool go;
do {
go = (std::count(_snake_body.begin(),_snake_body.end(),food_pos)>0);
this->food_pos = sf::Vector2i(std::abs((rand()%this->W)-PX),std::abs((rand()%this->W)-this->PX));
} while (go||(this->food_pos.x)%PX!=0||(this->food_pos.y)%PX!=0);
_score++;
_ate = true;
}
this->food_shape.setPosition(static_cast<float>(this->food_pos.x),static_cast<float>(this->food_pos.y));
_window.draw(food_shape);
}
};
Arquivo main.cpp
#include "food.hpp"
const float DELAY = 0.6f;
const int WIN_WIDTH = 420, WIN_HEIGHT = 420;
const int PX = 15, MAX_SIZE = WIN_HEIGHT/PX;
enum DIRS {RIGHT,DOWN,UP,LEFT,PAUSE};
enum DIRS current_direction = PAUSE;
int score = 3;
sf::RectangleShape rect;
std::vector<sf::Vector2i> snake;
int x_increment = PX, y_increment = PX;
Food snake_food(PX,(WIN_HEIGHT+WIN_WIDTH)/2);
bool ate = false;
sf::Vector2i temp;
void init_sanke (std::vector<sf::Vector2i>& _snake, const int _score) {
for (int i = 0; i < _score; i++) {
_snake.push_back(sf::Vector2i((10-i)*PX,PX));
}
}
void init_rect (sf::RectangleShape& _rect) {
_rect .setFillColor(sf::Color::White);
_rect.setSize(sf::Vector2f(PX,PX));
}
void draw_snake (sf::RenderWindow& _window,sf::RectangleShape& _rect,std::vector<sf::Vector2i>& _snake) {
for (int i = 0; i < score; i++) {
_rect.setFillColor((i) ? sf::Color::White : sf::Color::Red);
_rect.setPosition(_snake[i].x,_snake[i].y);
_window.draw(_rect);
}
}
void change_direction (sf::Event& e, enum DIRS &_direction) {
if (e.type == sf::Event::KeyPressed) {
switch (e.key.code) {
case sf::Keyboard::W: _direction = (_direction==DOWN ) ? DOWN : UP ; break;
case sf::Keyboard::S: _direction = (_direction==UP ) ? UP : DOWN ; break;
case sf::Keyboard::A: _direction = (_direction==RIGHT) ? RIGHT: LEFT ; break;
case sf::Keyboard::D: _direction = (_direction==LEFT ) ? LEFT : RIGHT; break;
case sf::Keyboard::Space: _direction = PAUSE; break;
default: return;
}
}
}
void update_snake (std::vector<sf::Vector2i>& _snake, enum DIRS &_direction,int& _x, int& _y, sf::Vector2i& temp) {
switch (_direction) {
case RIGHT :
_x = PX;
_y = 0 ;
break ;
case LEFT :
_x =-PX;
_y = 0 ;
break ;
case UP :
_x = 0 ;
_y =-PX;
break ;
case DOWN :
_x = 0 ;
_y = PX;
break ;
case PAUSE :
_x = 0 ;
_y = 0 ;
break ;
}
_snake.insert(_snake.begin(),sf::Vector2i(_snake[0].x+_x,snake[0].y+_y));
temp = _snake[score-1];
if (!ate) {
_snake.pop_back();
} else {
_snake.push_back(temp);
ate = !ate;
}
if (snake[0].x<0) {
_snake.insert(_snake.begin(),sf::Vector2i(WIN_WIDTH-PX,snake[0].y));
_snake.pop_back();
} else if (snake[0].x>WIN_WIDTH) {
_snake.insert(_snake.begin(),sf::Vector2i(0,snake[0].y));
_snake.pop_back();
} else if (snake[0].y>WIN_HEIGHT) {
_snake.insert(_snake.begin(),sf::Vector2i(snake[0].x,0));
_snake.pop_back();
} else if (snake[0].y<0) {
_snake.insert(_snake.begin(),sf::Vector2i(snake[0].x,WIN_HEIGHT-PX));
_snake.pop_back();
}
}
int main () {
float timer = 0, t = 0;
init_rect(rect);
init_sanke(snake,score);
sf::Event e;
sf::Clock clock;
sf::RenderWindow window(sf::VideoMode(WIN_WIDTH,WIN_HEIGHT),"snake game [ferdinaldo]",sf::Style::Close);
while (window.isOpen()) {
while (window.pollEvent(e)) {
if (e.type==sf::Event::Closed) window.close();
change_direction(e,current_direction);
}
window.clear(sf::Color::Black);
snake_food.draw_food(window,snake,score,ate);
draw_snake(window,rect,snake);
t = clock.getElapsedTime().asSeconds();
timer+=t;
if (timer>DELAY&¤t_direction!=PAUSE) {
timer = 0.5f;
update_snake(snake,current_direction,x_increment,y_increment,temp);
}
clock.restart();
window.display();
window.setFramerateLimit(120);
}
return 0;
}
Explicação do Código
Inicialização da Cobra
A função init_sanke
inicializa a cobra com o tamanho inicial definido pela variável score
.
void init_sanke (std::vector<sf::Vector2i>& _snake, const int _score) {
for (int i = 0; i < _score; i++) {
_snake.push_back(sf::Vector2i((10-i)*PX,PX));
}
}
Inicialização do Retângulo
A função init_rect
define a cor e o tamanho do retângulo que representa cada segmento da cobra.
void init_rect (sf::RectangleShape& _rect) {
_rect .setFillColor(sf::Color::White);
_rect.setSize(sf::Vector2f(PX,PX));
}
Desenho da Cobra
A função draw_snake
desenha a cobra na janela do jogo. O primeiro segmento é desenhado em vermelho, e os demais em branco.
void draw_snake (sf::RenderWindow& _window,sf::RectangleShape& _rect,std::vector<sf::Vector2i>& _snake) {
for (int i = 0; i < score; i++) {
_rect.setFillColor((i) ? sf::Color::White : sf::Color::Red);
_rect.setPosition(_snake[i].x,_snake[i].y);
_window.draw(_rect);
}
}
Mudança de Direção
A função change_direction
altera a direção da cobra com base nas teclas pressionadas pelo usuário.
void change_direction (sf::Event& e, enum DIRS &_direction) {
if (e.type == sf::Event::KeyPressed) {
switch (e.key.code) {
case sf::Keyboard::W: _direction = (_direction==DOWN ) ? DOWN : UP ; break;
case sf::Keyboard::S: _direction = (_direction==UP ) ? UP : DOWN ; break;
case sf::Keyboard::A: _direction = (_direction==RIGHT) ? RIGHT: LEFT ; break;
case sf::Keyboard::D: _direction = (_direction==LEFT ) ? LEFT : RIGHT; break;
case sf::Keyboard::Space: _direction = PAUSE; break;
default: return;
}
}
}
Atualização da Cobra
A função update_snake
atualiza a posição da cobra e lida com a detecção de colisões com as bordas da janela e a comida.
void update_snake (std::vector<sf::Vector2i>& _snake, enum DIRS &_direction,int& _x, int& _y, sf::Vector2i& temp) {
switch (_direction) {
case RIGHT :
_x = PX;
_y = 0 ;
break ;
case LEFT :
_x =-PX;
_y = 0 ;
break ;
case UP :
_x = 0 ;
_y =-PX;
break ;
case DOWN :
_x = 0 ;
_y = PX;
break ;
case PAUSE :
_x = 0 ;
_y = 0 ;
break ;
}
_snake.insert(_snake.begin(),sf::Vector2i(_snake[0].x+_x,snake[0].y+_y));
temp = _snake[score-1];
if (!ate) {
_snake.pop_back();
} else {
_snake.push_back(temp);
ate = !ate;
}
if (snake[0].x<0) {
_snake.insert(_snake.begin(),sf::Vector2i(WIN_WIDTH-PX,snake[0].y));
_snake.pop_back();
} else if (snake[0].x>WIN_WIDTH) {
_snake.insert(_snake.begin(),sf::Vector2i(0,snake[0].y));
_snake.pop_back();
} else if (snake[0].y>WIN_HEIGHT) {
_snake.insert(_snake.begin(),sf::Vector2i(snake[0].x,0));
_snake.pop_back();
} else if (snake[0].y<0) {
_snake.insert(_snake.begin(),sf::Vector2i(snake[0].x,WIN_HEIGHT-PX));
_snake.pop_back();
}
}
Função Principal
A função main
inicializa a janela do jogo, configura a cobra e a comida, e entra no loop principal do jogo, onde a cobra é desenhada e atualizada.
int main () {
float timer = 0, t = 0;
init_rect(rect);
init_sanke(snake,score);
sf::Event e;
sf::Clock clock;
sf::RenderWindow window(sf::VideoMode(WIN_WIDTH,WIN_HEIGHT),"snake game [ferdinaldo]",sf::Style::Close);
while (window.isOpen()) {
while (window.pollEvent(e)) {
if (e.type==sf::Event::Closed) window.close();
change_direction(e,current_direction);
}
window.clear(sf::Color::Black);
snake_food.draw_food(window,snake,score,ate);
draw_snake(window,rect,snake);
t = clock.getElapsedTime().asSeconds();
timer+=t;
if (timer>DELAY&¤t_direction!=PAUSE) {
timer = 0.5f;
update_snake(snake,current_direction,x_increment,y_increment,temp);
}
clock.restart();
window.display();
window.setFramerateLimit(120);
}
return 0;
}
Resultado final
Conclusão
Este é um exemplo básico de como criar um jogo de Snake em C++ usando a biblioteca SFML. O código pode ser expandido para adicionar mais funcionalidades, como níveis, pontuação mais complexa, e obstáculos. Com este conhecimento, você pode explorar mais recursos da SFML e desenvolver jogos mais avançados.
Ups! ☺ Talvez eu tenha esquecido de adicionar o Game over ao jogo
Isso fica como sua tarefa! boa sorte.