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&&current_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&&current_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, transforme código em diversão

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.