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.

0
Subscribe to my newsletter

Read articles from Ferdinaldo Fernando Bernardo Pelembe directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ferdinaldo Fernando Bernardo Pelembe
Ferdinaldo Fernando Bernardo Pelembe