×

Search anything:

Snake Game using C++ & SFML

C++

Binary Tree book by OpenGenus

Open-Source Internship opportunity by OpenGenus for programmers. Apply now.

In this article, we'll dive into, a journey of mine, where I built a classic snake game. In this journey we will be building a Classic Snake Game through which we will learn how to build a game with boundaries, a growing snake, and food that the snake can eat. This game will be challenging and exciting to play, and by the end of this article, you'll have a clear understanding of how it was created. Get ready to embark on an amazing journey of coding and gaming!

Table of Contents

  • Overview
  • Features of The Game
  • Working Environment
  • State Management
  • Asset Management
  • Game Class
  • Creating Menu
  • Asset Integration
  • Snake Movement
  • Pausing The Game

Overview

Here we will be creating a 2-D snake game using C++ and SFML. Knowledge about C++ is necessary to move on as we will be using STL containers, shared pointers etc. So knowledge on C++ is a must. If you don't know SFML its not a problem you can learn on the go. Using Simple and Fast Multimedia Library (SFML) we can create windows, playing audio, drawing texts etc. If you want help with SFML, I would recommend you to check this out. So if you are ready lets move on.

Features of The Game

The game window is of 650px in length and 352px in width. When the game starts-up, you are greeted with a main menu which has the title of the game along with options to play or exit the game.The option highlighted with green color is the selected option. You can control the option selected using the arrow keys. You can use enter key to select an option. If you select the exit option the game stops running and the window closes.

main-menu

Once you click on the play option the game starts instantly. Now you can see the the whole layout is bordered with grey colored stone, inside it you have green colored grass, a small picture of an apple which is food and an red colored snake which starts moving and is supposed to eat the food.

Your aim is to get as much points as possible by controlling the snake and eating the food without hitting the wall and to itself. The snake keeps growing as you eat a food. Once you eat a food, another food is placed randomly in the grass. You can control the snake with the arrow keys. Your score is tracked at the top left corner of the window.
game-play

If you need a break you can pause the game by pressing the Esc key, which would show a pause menu, from where you can see your score on top left corner and then when you want to resume the game you can press the Esc key again.
pause-menu

Once the snake hits to the wall or to itself the game is over and the game over menu is shown where you can retry or exit the game
game-over

Working Environment

I was working in a debian based linux distribution, Ubuntu. You can work with other OS as well. Use Visual Studio if you are in windows. Checkout my code here.

We need the following things:

  • C++ Compiler (GNU G++)
  • Build system (make)
  • IDE/Text editor (Visual Studio Code)
  • Make Command
  • SFML

There are a lot of videos on how to install C++ compiler, make command and Visual Studio Code on the web, so refer those. Regarding SFML refer their official website here. A build system is a software which compiles the source files into a standalone software artifact which can be run on a computer. make is a command in linux used to do the same, there are videos on how to install it on the web.

Since you have the working environment ready you could run my code in your local machine. Just follow the steps:

  • Fork the repository, and if you liked the project do give it a star, and then clone it.
  • After cloning it, run the following code:
cd snake-game-in-cpp
make
bin/main

The make command would create an executable file inside the bin folder named main.exe which would be opened by the bin/main command. By this point the working game window would be on the screen and you can play the game.

In the above code snippet you would have seen a command to create executable files. Yes the make command. Lets get to know more about it. The make command is a build automation tol in linux and other unix like operating systems. It's used to compile software projects even the complex ones by automating the process of recompiling only the source files that have changed. This command have made the process so easier.

The make command uses a makefile which specifies how the software should be built. The makefile contains some set of rules and dependencies which specifies how different component of the software depend on each other. So the make command reads the makefile and determines which files need to be recompiled, based on the dependencies and timestamps of the files.

Using the make command helps us to simplify and automate the process of building software, especially large and complex projects with many dependencies. By only recompiling the necessary source files, make command can same time and also improves the speed of the build process. So we can say that the make command is indeed on of the important tool in development in linux and other unix like operating systems.

State Management

The state in our game is a piece of code which can process inputs, update itself and load new state. In a normal game a splash screen appears, then after some time the main menu will be displayed where it will be waiting for input, after giving it an input it will load up a new state. eg: if we select play, it will load up the game state. While in the game, when certain conditions are met the Game Over state will start. This is what state management means.

Here I have created abstract based class for all such states. We will make a state manager class which will manage a stack of states. with which we can push, pop and replace states.

We will create a State.hpp file and then create an engine block in it. Then create a class inside it with necessary destructors, methods. The following is the code.

#pragma once

#include <SFML/System/Time.hpp>

namespace Engine
{
    class State
    {
    public:
        State(/* args */){};
        virtual ~State(){};

        virtual void Init() = 0;
        virtual void ProcessInput() = 0;
        virtual void Update(sf::Time deltaTime) = 0;
        virtual void Draw() = 0;

        virtual void Pause(){};
        virtual void Start(){};
    };

} // namespace Engine

The class is kept inside the namespace to avoid name collisions with others parts of the code. This is being done in the rest of the files as well. The virtual functions are:

  • Init: Its called when the state is created and is used to initialize any data or resources needed for the state.
  • ProcessInput: It processes the users input and updates the state accordingly.
  • Update: It updates the state over time.
  • Draw: This function is responsible for rendering the state to the screen.
  • Pause: This function is called when the state is paused.
  • Start: This function is called when the state is resumed/started.

Now to manage the state we create StateMan.hpp and StateMan.cpp.

StateMan.hpp

#pragma once

#include <stack>
#include <memory>

#include <State.hpp>

namespace Engine
{
    class StateMan
    {
    private:
        std::stack<std::unique_ptr<State>> m_stateStack;
        std::unique_ptr<State> m_newState;

        bool m_add;
        bool m_replace;
        bool m_remove;

    public:
        StateMan();
        ~StateMan();

        void Add(std::unique_ptr<State> toAdd, bool replace = false);
        void PopCurrent();
        void ProcessStateChange();
        std::unique_ptr<State> &GetCurrent();
    };

} // namespace Engine

StateMan.cpp

#include "StateMan.hpp"

Engine::StateMan::StateMan() : m_add(false), m_replace(false), m_remove(false)
{
}

Engine::StateMan::~StateMan()
{
}

void Engine::StateMan::Add(std::unique_ptr<State> toAdd, bool replace)
{
    m_add = true;
    ;
    m_newState = std::move(toAdd);

    m_replace = replace;
}
void Engine::StateMan::PopCurrent()
{
    m_remove = true;
}
void Engine::StateMan::ProcessStateChange()
{
    if (m_remove && (!m_stateStack.empty()))
    {
        m_stateStack.pop();
        if (!m_stateStack.empty())
        {
            m_stateStack.top()->Start();
        }
        m_remove = false;
    }
    if (m_add)
    {
        if (m_replace && (!m_stateStack.empty()))
        {
            m_stateStack.pop();
            m_replace = false;
        }
        if (!m_stateStack.empty())
        {
            m_stateStack.top()->Pause();
        }
        m_stateStack.push(std::move(m_newState));
        m_stateStack.top()->Init();
        m_stateStack.top()->Start();
        m_add = false;
    }
}
std::unique_ptr<Engine::State> &Engine::StateMan::GetCurrent()
{
    return m_stateStack.top();
}

The engine namespace has several member variables like:

  • m_stateStack: This is a std::stack of std::unique_ptr to State objects. It stores the current game state and checks if any previous states that are active.
  • m_newState: Its similar to m_stateStack but its used to store a new state that is added to the m_stateStack.
  • m_add: A boolean value to indicate whether new state should be added or not.
  • m_replace: A boolean value that indicates whether the current state should be replaced by the new state.
  • m_remove: A boolean value which indicates whether the current state should be removed from the m_stateStack.

It has several methods like:

  • Add(std::unique_ptr<State> toAdd, bool replace): This method adds a new state to the m_stateStack .
  • PopCurrent(): This method removes the current state from the m_stateStack.
  • ProcessStateChange: It checks whether any state Change was requested, like adding a new state, replacing etc.
  • GetCurrent(): This method returns a reference to the current state, which would be stored on top of the m_stateStack.

Asset Management

The following are codes from AssetMan.hpp & AssetMan.cpp file.

AssetMan.hpp

#pragma once

#include <map>
#include <memory>
#include <string>

#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/Texture.hpp>

namespace Engine
{
    class AssetMan
    {
    private:
        std::map<int, std::unique_ptr<sf::Texture>> m_textures;
        std::map<int, std::unique_ptr<sf::Font>> m_fonts;

    public:
        AssetMan();
        ~AssetMan();

        void AddTexture(int id, const std::string &filePath, bool wantRepeated = false);
        void AddFont(int id, const std::string &filePath);

        const sf::Texture &GetTexture(int id) const;
        const sf::Font &GetFont(int id) const;
    };

} // namespace Engine

AssetMan.cpp

#include "AssetMan.hpp"

Engine::AssetMan::AssetMan()
{
}

Engine::AssetMan::~AssetMan()
{
}

void Engine::AssetMan::AddTexture(int id, const std::string &filePath, bool wantRepeated)
{
    auto texture = std::make_unique<sf::Texture>();
    if (texture->loadFromFile(filePath))
    {
        texture->setRepeated(wantRepeated);
        m_textures[id] = std::move(texture);
    }
}
void Engine::AssetMan::AddFont(int id, const std::string &filePath)
{
    auto font = std::make_unique<sf::Font>();

    if (font->loadFromFile(filePath))
    {
        m_fonts[id] = std::move(font);
    }
}
const sf::Texture &Engine::AssetMan::GetTexture(int id) const
{
    return *(m_textures.at(id).get());
}
const sf::Font &Engine::AssetMan::GetFont(int id) const
{
    return *(m_fonts.at(id).get());
}

As the name suggest here we are managing the assets needed for the project.

  • AddTexture(): This creates a new texture object and loads the image from the file path. If its successful it sets texture to be repeated and stores the texture to be repeated.
  • AddFont(): It creates a new font object and loads the font from the file path. If its successful it stores the font in the fonts map.
  • GetTexture: It takes in id and returns a constant reference to the texture stored in the textures map.
  • GetFont: It takes in an id and returns a constant reference to the font stored in the fonts map.

Game Class

The Game class is going to be the entry point and driver of the game. Its been created by the following two files.

Game.hpp

#pragma once

#include <memory>

#include <SFML/Graphics/RenderWindow.hpp>

#include "AssetMan.hpp"
#include "StateMan.hpp"

enum AssetId
{
    MAIN_FONT = 0,
    GRASS,
    FOOD,
    STONE,
    SNAKE
};

struct Context
{
    std::unique_ptr<Engine::AssetMan> m_assets;
    std::unique_ptr<Engine::StateMan> m_states;
    std::unique_ptr<sf::RenderWindow> m_window;

    Context()
    {
        m_assets = std::make_unique<Engine::AssetMan>();
        m_states = std::make_unique<Engine::StateMan>();
        m_window = std::make_unique<sf::RenderWindow>();
    }
};

class Game
{
private:
    std::shared_ptr<Context> m_context;
    const sf::Time TIME_PER_FRAME = sf::seconds(1.f / 60.f);

public:
    Game();
    ~Game();

    void Run();
};

Game.cpp

#include "Game.hpp"
#include "MainMenu.hpp"

#include <SFML/Graphics/CircleShape.hpp>

Game::Game() : m_context(std::make_shared<Context>())
{
    m_context->m_window->create(sf::VideoMode(650, 352), "Classic Snake", sf::Style::Close);
    m_context->m_states->Add(std::make_unique<MainMenu>(m_context));
}

Game::~Game()
{
}

void Game::Run()
{
    sf::CircleShape shape(100.f);
    shape.setFillColor(sf::Color::Green);

    sf::Clock clock;
    sf::Time timeSinceeLastFrame = sf::Time::Zero;

    while (m_context->m_window->isOpen())
    {
        timeSinceeLastFrame += clock.restart();

        while (timeSinceeLastFrame > TIME_PER_FRAME)
        {
            timeSinceeLastFrame -= TIME_PER_FRAME;

            m_context->m_states->ProcessStateChange();
            m_context->m_states->GetCurrent()->ProcessInput();
            m_context->m_states->GetCurrent()->Update(TIME_PER_FRAME);
            m_context->m_states->GetCurrent()->Draw();
        }
    }
}

Here the Game class has a Context struct as a member which holds pointers to the unique instances of the AssetMan class for managing assets, StateMan class for managing the game states, and sf::RenderWindow class for rendering the game window.

The Run method of the Game class is the main loop of the game where it continuosly processes input, updates and draws the current state of the game. The game runs at 60 frames per second as defined by TIME_PER_FRAME. The Run method uses a clock to keep track of the time and updates the game state accordingly. The game window is created with a specified width, height and title.

We also have AssetId which holds the IDs of different assets like font and textures.

Creating Menu

The main menu and the game over menu is made almost in the same manner. Lets look at the main menu first.

MainMenu.hpp

#pragma once

#include <memory>

#include <SFML/Graphics/Text.hpp>

#include "Game.hpp"
#include "State.hpp"

class MainMenu : public Engine::State
{
private:
    std::shared_ptr<Context> m_context;
    sf::Text m_gameTitle;
    sf::Text m_playButton;
    sf::Text m_exitButton;

    bool m_isPlayButtonPressed;
    bool m_isPlayButtonSelected;

    bool m_isExitButtonSelected;
    bool m_isExitButtonPressed;

public:
    MainMenu(std::shared_ptr<Context> &context);
    ~MainMenu();

    void ProcessInput() override;
    void Init() override;
    void Update(sf::Time deltaTime) override;
    void Draw() override;
};

MainMenu.cpp

#include "MainMenu.hpp"
#include "GamePlay.hpp"

#include <SFML/Window/Event.hpp>

MainMenu::MainMenu(std::shared_ptr<Context> &context)
    : m_context(context), m_isPlayButtonSelected(true),
      m_isPlayButtonPressed(false), m_isExitButtonSelected(false),
      m_isExitButtonPressed(false)
{
}

MainMenu::~MainMenu()
{
}

void MainMenu::ProcessInput()
{
    sf::Event event;
    while (m_context->m_window->pollEvent(event))
    {
        if (event.type == sf::Event::Closed)
        {
            m_context->m_window->close();
        }
        else if (event.type == sf::Event::KeyPressed)
        {
            switch (event.key.code)
            {
            case sf::Keyboard::Up:
            {
                if (!m_isPlayButtonSelected)
                {
                    m_isPlayButtonSelected = true;
                    m_isExitButtonSelected = false;
                }
                break;
            }
            case sf::Keyboard::Down:
            {
                if (!m_isExitButtonSelected)
                {
                    m_isPlayButtonSelected = false;
                    m_isExitButtonSelected = true;
                }
                break;
            }
            case sf::Keyboard::Return:
            {
                m_isExitButtonPressed = false;
                m_isPlayButtonPressed = false;
                if (m_isExitButtonSelected)
                {
                    m_isExitButtonPressed = true;
                }
                else
                {
                    m_isPlayButtonPressed = true;
                }
                break;
            }
            default:
            {
                break;
            }
            }
        }
    }
}
void MainMenu::Init()
{
    m_context->m_assets->AddFont(MAIN_FONT, "assets/fonts/Itim-Regular.ttf");
    // Title
    m_gameTitle.setFont(m_context->m_assets->GetFont(MAIN_FONT));
    m_gameTitle.setString("Classic Snake Game");
    m_gameTitle.setOrigin(m_gameTitle.getLocalBounds().width / 2, m_gameTitle.getLocalBounds().height / 2);
    m_gameTitle.setPosition(m_context->m_window->getSize().x / 2, m_context->m_window->getSize().y / 2 - 100.f);

    // Play button
    m_playButton.setFont(m_context->m_assets->GetFont(MAIN_FONT));
    m_playButton.setString("Play");
    m_playButton.setOrigin(m_playButton.getLocalBounds().width / 2, m_playButton.getLocalBounds().height / 2);
    m_playButton.setPosition(m_context->m_window->getSize().x / 2, m_context->m_window->getSize().y / 2 - 25.f);
    m_playButton.setCharacterSize(20);

    // exit button
    m_exitButton.setFont(m_context->m_assets->GetFont(MAIN_FONT));
    m_exitButton.setString("Exit");
    m_exitButton.setOrigin(m_exitButton.getLocalBounds().width / 2, m_exitButton.getLocalBounds().height / 2);
    m_exitButton.setPosition(m_context->m_window->getSize().x / 2, m_context->m_window->getSize().y / 2 + 25.f);
    m_exitButton.setCharacterSize(20);
}
void MainMenu::Update(sf::Time deltaTime)
{
    if (m_isPlayButtonSelected)
    {
        m_playButton.setFillColor(sf::Color::Green);
        m_exitButton.setFillColor(sf::Color::White);
    }
    else
    {
        m_exitButton.setFillColor(sf::Color::Green);
        m_playButton.setFillColor(sf::Color::White);
    }

    if (m_isPlayButtonPressed)
    {
        m_context->m_states->Add(std::make_unique<GamePlay>(m_context), true);
    }
    else if (m_isExitButtonPressed)
    {
        m_context->m_window->close();
    }
}

void MainMenu::Draw()
{
    m_context->m_window->clear();
    m_context->m_window->draw(m_gameTitle);
    m_context->m_window->draw(m_playButton);
    m_context->m_window->draw(m_exitButton);
    m_context->m_window->display();
}

The above code implements the main menu of the game. The class has a constructor which initializes the state of the class. The context is shared between different parts of the game such as window, assets and input.

The ProcessInput() method listens for user input from the keyboard and updates the class variables based on the input received, ie, when the up and down arrow keys are pressed the selection changes from play to exit.

The Init() method initializes the text objects that display the game title, play and exit button. The font required is loaded from the assets using the context object.

The Update() method updates the class state based on the inputs received. The Draw() method draws the text objects representing the elements in the menu.

Using similar techniques we can make the game over menu too. Try to figure it out your-self.

Asset Integration

Here we will be using the following files to have the game layout and to handle core gameplay logic.
GamePlay.hpp

#pragma once

#include <memory>
#include <array>

#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/Text.hpp>

#include "Game.hpp"
#include "State.hpp"
#include "Snake.hpp"

class GamePlay : public Engine::State
{
private:
    std::shared_ptr<Context> m_context;
    sf::Sprite m_grass;
    sf::Sprite m_food;
    std::array<sf::Sprite, 4> m_stone;

    Snake m_snake;

    sf::Text m_scoreText;
    int m_score;

    sf::Vector2f m_snakeDirection;
    sf::Time m_elapsedTime;

    bool m_isPaused;

public:
    GamePlay(std::shared_ptr<Context> &context);
    ~GamePlay();

    void Init() override;
    void ProcessInput() override;
    void Update(sf::Time deltaTime) override;
    void Draw() override;
    void Pause() override;
    void Start() override;
};

GamePlay.cpp

#include "GamePlay.hpp"
#include "GameOver.hpp"
#include "PauseGame.hpp"

#include <SFML/Window/Event.hpp>

#include <stdlib.h>
#include <time.h>

GamePlay::GamePlay(std::shared_ptr<Context> &context) : m_context(context),
                                                        m_snakeDirection({16.f, 0.f}),
                                                        m_elapsedTime(sf::Time::Zero),
                                                        m_score(0), m_isPaused(false)
{
    srand(time(nullptr));
}

GamePlay::~GamePlay()
{
}

void GamePlay::Init()
{
    m_context->m_assets->AddTexture(GRASS, "assets/texture/grass.png", true);
    m_context->m_assets->AddTexture(FOOD, "assets/texture/food.png");
    m_context->m_assets->AddTexture(STONE, "assets/texture/stone.png", true);
    m_context->m_assets->AddTexture(SNAKE, "assets/texture/snake.png");

    m_grass.setTexture(m_context->m_assets->GetTexture(GRASS));
    m_grass.setTextureRect(m_context->m_window->getViewport(m_context->m_window->getDefaultView()));

    for (auto &stone : m_stone)
    {
        stone.setTexture(m_context->m_assets->GetTexture(STONE));
    }

    m_stone[0].setTextureRect({0, 0, m_context->m_window->getSize().x, 16});
    m_stone[1].setTextureRect({0, 0, m_context->m_window->getSize().x, 16});
    m_stone[1].setPosition(0, m_context->m_window->getSize().y - 16);

    m_stone[2].setTextureRect({0, 0, 16, m_context->m_window->getSize().y});
    m_stone[3].setTextureRect({0, 0, 16, m_context->m_window->getSize().y});
    m_stone[3].setPosition(m_context->m_window->getSize().x - 16, 0);

    m_food.setTexture(m_context->m_assets->GetTexture(FOOD));
    m_food.setPosition(m_context->m_window->getSize().x / 2, m_context->m_window->getSize().y / 2);

    m_snake.Init(m_context->m_assets->GetTexture(SNAKE));

    m_scoreText.setFont(m_context->m_assets->GetFont(MAIN_FONT));
    m_scoreText.setString("Score: " + std::to_string(m_score));
    m_scoreText.setCharacterSize(15);
}
void GamePlay::ProcessInput()
{
    sf::Event event;
    while (m_context->m_window->pollEvent(event))
    {
        if (event.type == sf::Event::Closed)
        {
            m_context->m_window->close();
        }
        else if (event.type == sf::Event::KeyPressed)
        {
            sf::Vector2f newDirection = m_snakeDirection;
            switch (event.key.code)
            {
            case sf::Keyboard::Up:
                newDirection = {0.f, -16.f};
                break;
            case sf::Keyboard::Down:
                newDirection = {0.f, 16.f};
                break;
            case sf::Keyboard::Left:
                newDirection = {-16.f, 0.f};
                break;
            case sf::Keyboard::Right:
                newDirection = {16.f, 0.f};
                break;
            case sf::Keyboard::Escape:
                m_context->m_states->Add(std::make_unique<PauseGame>(m_context));
                break;

            default:
                break;
            }
            if (std::abs(m_snakeDirection.x) != std::abs(newDirection.x) ||
                std::abs(m_snakeDirection.y) != std::abs(newDirection.y))
            {
                m_snakeDirection = newDirection;
            }
        }
    }
}
void GamePlay::Update(sf::Time deltaTime)
{
    if (!m_isPaused)
    {

        m_elapsedTime += deltaTime;
        if (m_elapsedTime.asSeconds() > 0.1)
        {
            for (auto &stone : m_stone)
            {
                if (m_snake.IsOn(stone))
                {
                    m_context->m_states->Add(std::make_unique<GameOver>(m_context), true);
                    break;
                }
            }

            if (m_snake.IsOn(m_food))
            {
                m_snake.Grow(m_snakeDirection);
                int x = 0, y = 0;

                x = std::clamp<int>(rand() % m_context->m_window->getSize().x, 16, m_context->m_window->getSize().x - 2 * 16);
                y = std::clamp<int>(rand() % m_context->m_window->getSize().y, 16, m_context->m_window->getSize().y - 2 * 16);

                m_food.setPosition(x, y);
                m_score += 1;
                m_scoreText.setString("Score: " + std::to_string(m_score));
            }
            else
            {
                m_snake.Move(m_snakeDirection);
            }
            if (m_snake.IsSelfIntersecting())
            {
                m_context->m_states->Add(std::make_unique<GameOver>(m_context), true);
            }

            m_elapsedTime = sf::Time::Zero;
        }
    }
}
void GamePlay::Draw()
{
    m_context->m_window->clear();
    m_context->m_window->draw(m_grass);

    for (auto &stone : m_stone)
    {
        m_context->m_window->draw(stone);
    }
    m_context->m_window->draw(m_food);
    m_context->m_window->draw(m_snake);
    m_context->m_window->draw(m_scoreText);

    m_context->m_window->display();
}
void GamePlay::Pause()
{
    m_isPaused = true;
}
void GamePlay::Start()
{
    m_isPaused = false;
}

These two files draws the layout, changes direction of the snake according to users input, places the food at random positions, grows the snake etc.
The class is initialized with a Context object which provides access to shared resources such as the game window, assets etc. The images used for walls, grass, food, snake are 16x16 images.

The class has several member functions,

  • Init(): This is called once when the class is initialized, and it loads the textures for the game elements.
  • ProcessInput(): This function processes user input events, such as keyboard inputs, to control the movement of the snake and to pause the game.
  • Update(): This function is called every frame to update the game state, such as the position of the snake and the food. It also checks the collision between snake and the stones. Inside this function we also keep track of the score which would be displayed at the corner.
  • Render(): This function is called every frame to render the game elements on the screen.

Snake Movement

Here we will be making the snake and will give movements for it. We will be doing this with the following files.

Snake.hpp

#pragma once

#include <list>

#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
#include <SFML/Graphics/RenderStates.hpp>

class Snake : public sf::Drawable
{
private:
    std::list<sf::Sprite> m_body;
    std::list<sf::Sprite>::iterator m_head;
    std::list<sf::Sprite>::iterator m_tail;

public:
    Snake();
    ~Snake();

    void Init(const sf::Texture &texture);
    void Move(const sf::Vector2f &direction);
    bool IsOn(const sf::Sprite &other) const;
    void Grow(const sf::Vector2f &direction);
    bool IsSelfIntersecting() const;

    void draw(sf::RenderTarget &target, sf::RenderStates states) const override;
};

Snake.cpp

#include "Snake.hpp"

Snake::Snake() : m_body(std::list<sf::Sprite>(4))
{
    m_head = --m_body.end();
    m_tail = m_body.begin();
}

Snake::~Snake()
{
}

void Snake::Init(const sf::Texture &texture)
{
    float x = 16.f;
    for (auto &piece : m_body)
    {
        piece.setTexture(texture);
        piece.setPosition({x, 16.f});
        x += 16.f;
    }
}

void Snake::Move(const sf::Vector2f &direction)
{
    m_tail->setPosition(m_head->getPosition() + direction);
    m_head = m_tail;
    ++m_tail;

    if (m_tail == m_body.end())
    {
        m_tail = m_body.begin();
    }
}

bool Snake::IsOn(const sf::Sprite &other) const
{
    return other.getGlobalBounds().intersects(m_head->getGlobalBounds());
}
void Snake::Grow(const sf::Vector2f &direction)
{
    sf::Sprite newPiece;
    newPiece.setTexture(*(m_body.begin()->getTexture()));
    newPiece.setPosition(m_head->getPosition() + direction);

    m_head = m_body.insert(++m_head, newPiece);
}
bool Snake::IsSelfIntersecting() const
{
    bool flag = false;
    for (auto piece = m_body.begin(); piece != m_body.end(); ++piece)
    {
        if (m_head != piece)
        {
            flag = IsOn(*piece);
            if (flag)
            {
                break;
            }
        }
    }
    return flag;
}
void Snake::draw(sf::RenderTarget &target, sf::RenderStates states) const
{
    for (auto &piece : m_body)
    {
        target.draw(piece);
    }
}

Since the snake has to grow after every food it eat we cannot represent the snake with a single sprite object. Hence here we have used multiple sprite object to represent the snake, so that we can increase the the number of sprites to increase the size of the snake.

The class has an iterator named m_head to keep track of the head of the snake and another iterator named m_tail to keep track of the tail of the snake. The Snake class implements various methods such as Init, Move, IsOn, Grow, IsSelfIntersecting, and draw. The Init method sets the texture for the sprite objects in the list and sets their initial position. The Move method moves the tail of the snake to the position of the head and sets the head to be the new tail.

IsOn method returns true if the head of the snake is on top of another sprite. This way we can detect if the snake hits itself, for this we will also use IsSelfIntersecting which returns true if it happens. The Grow method adds a new piece to the snake's body in a given direction. The draw method draws all the sprite objects in the list to the render target.

Pausing The Game

For pausing we have to create a statement inside the switch case in the gameplay file, do check it out.

The files which made it possible are
PauseGame.hpp

#pragma once

#include <memory>

#include <SFML/Graphics/Text.hpp>

#include "Game.hpp"
#include "State.hpp"

class PauseGame : public Engine::State
{
private:
    std::shared_ptr<Context> m_context;
    sf::Text m_pauseTitle;

public:
    PauseGame(std::shared_ptr<Context> &context);
    ~PauseGame();

    void ProcessInput() override;
    void Init() override;
    void Update(sf::Time deltaTime) override;
    void Draw() override;
};

PauseGame.cpp

#include "PauseGame.hpp"

#include <SFML/Window/Event.hpp>

PauseGame::PauseGame(std::shared_ptr<Context> &context) : m_context(context)
{
}

PauseGame::~PauseGame()
{
}

void PauseGame::ProcessInput()
{
    sf::Event event;
    while (m_context->m_window->pollEvent(event))
    {
        if (event.type == sf::Event::Closed)
        {
            m_context->m_window->close();
        }
        else if (event.type == sf::Event::KeyPressed)
        {
            switch (event.key.code)
            {
            case sf::Keyboard::Escape:
            {
                m_context->m_states->PopCurrent();
                break;
            }

            default:
            {
                break;
            }
            }
        }
    }
}
void PauseGame::Init()
{
    // Title
    m_pauseTitle.setFont(m_context->m_assets->GetFont(MAIN_FONT));
    m_pauseTitle.setString("Paused");
    m_pauseTitle.setOrigin(m_pauseTitle.getLocalBounds().width / 2, m_pauseTitle.getLocalBounds().height / 2);
    m_pauseTitle.setPosition(m_context->m_window->getSize().x / 2, m_context->m_window->getSize().y / 2);
}
void PauseGame::Update(sf::Time deltaTime)
{
}

void PauseGame::Draw()
{
    m_context->m_window->draw(m_pauseTitle);
    m_context->m_window->display();
}

The above codes are similar to the main menu code. Here PauseGame is used to display the pause menu on the screen when esc key is pressed and viceversa.

The methods in the file are:

  • ProcessInput(): It polls for events from the window and handles the input. If the close event is triggered, the window is closed. If the esc key is pressed, the current state is popped from the state stack.
  • Inir(): This function initializes the font for the menu.
  • Draw(): This function draws the "Paused" text on the screen along with the score.

So by this point everything related to the game has been explained in an organized manner in this article. So I am hoping you understood everything related to it. Do checkout the repository.

Snake Game using C++ & SFML
Share this