Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make SFML Snake game more responsive to key presses?

I found a SFML C++ snake game and I've been messing around with it and changing a few things, but one of the things I can't figure out is how to make it more smooth/responsive with the arrow key presses. Right now it's using enum Direction {Up, Down, Left, Right}; with

while (window.isOpen())
{

    sf::Vector2f lastPosition(snakeBody[0].getPosition().x, snakeBody[0].getPosition().y);

    // Event
    sf::Event event;
    while (window.pollEvent(event))
    {
        //.....

        if (event.type == sf::Event::KeyPressed && event.key.code
                == sf::Keyboard::Return)
        {
            //clock.restart;
            if (!currentlyPlaying)
                currentlyPlaying = true;
            move = Down;

            New_Game(snakeBody, window_width, window_height, engine, apple, score, scoreText, lowBounds);
            mode = 1;
            moveClock.restart();
            //start game
        }
            if(inputClock.getElapsedTime().asSeconds() >= 0.07)
            {
                if(event.key.code == sf::Keyboard::Up && move != Down)
                    move = Up;
                inputClock.restart();
                if(event.key.code == sf::Keyboard::Down && move != Up)
                    move = Down;
                inputClock.restart();
                if(event.key.code == sf::Keyboard::Left && move != Right)
                    move = Left;
                inputClock.restart();
                if(event.key.code == sf::Keyboard::Right && move != Left)
                    move = Right;
                inputClock.restart();
            }
}

It's frustrating to play currently because I can't move as precisely as I would like. Is there a way to make the controls more responsive or is it already responding to key presses as quickly as it's able to with my hardware?

I'm a complete beginner to OOP and SFML so I'm having a bit of trouble understanding the clock as well as looking at snake games in other languages in order to translate it to this. I can post the entire code if needed.

So I know it's not pretty, but here's the entire code:

#include <iostream>
#include <SFML/Graphics.hpp>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>


// Global directions
enum Direction {Up, Down, Left, Right};

// Reads high scores
void High_Scores(std::vector<int> &top10scores)
{
    top10scores.clear();
    std::string line;
    std::ifstream highscores("highscores.txt");
    while (std::getline(highscores, line))
        top10scores.push_back(stoi(line));
    highscores.close();
}

// Checks new score against high scores
void New_High(std::vector<int> newScores)
{
    std::ofstream fileoutput;
    fileoutput.open("highscores.txt");
    for (int i = 0; i < 10; i++)
        fileoutput << newScores[i] << "\n";
    fileoutput.close();
}

// Writes over highscores.txt file
int Update_Scores(int &score, std::vector<int> &top10scores)
{
    for (int i = 0; i < 10; i++)
    {
        if (score >= top10scores[i])
        {
            for (int j = 9; j >= 0+i; j--)
            {
                top10scores[j] = top10scores[j-1];
            }

            top10scores[i] = score;

            New_High(top10scores);

            High_Scores(top10scores);
            return i;
        }
    }
    return 11;
}

// Start menu
void Welcome_Screen(sf::RenderWindow &window)
{
    sf::Texture texture;
    texture.loadFromFile("welcome2.png");
    sf::Sprite background(texture);
    // Create welcome text
    sf::Font font;
    if (!font.loadFromFile("bonzai.ttf"))
        std::cout << "Can't find the font file 'bonzai.ttf'" << std::endl;
    sf::Text text("\n\n\n\n  \t\t\t Welcome!\n\t   Press 'Enter' to begin.", font, 50);
    text.setColor(sf::Color::White);
    sf::Text quitText(" 'Esc' to quit", font, 17);
    quitText.setColor(sf::Color::Black);
    window.clear();
    window.draw(background);
    window.draw(text);
    window.draw(quitText);
    window.display();
}

// Basic collision check for apple placement
bool Collision_Detect(std::vector<sf::RectangleShape> &snakeBody, sf::CircleShape &apple)
{
    for (int i = 0; i != snakeBody.size(); i++)
    {
        if (snakeBody[i].getPosition() == apple.getPosition())
            return true;
    }
    return false;
}

// Sets up starting values for game
void New_Game(std::vector<sf::RectangleShape> &snakeBody, int window_width, int window_height,
              std::default_random_engine &engine, sf::CircleShape &apple, int score, sf::Text &scoreText,
              int lowBounds)
{
    score = 0;
    scoreText.setString("Score: 0");
    snakeBody.clear();
    snakeBody.push_back(sf::RectangleShape(sf::Vector2f(20,20))); // one square
    snakeBody[0].setPosition(window_width / 2, window_height / 2 - 120);
    snakeBody[0].setFillColor(sf::Color(200,255,200));
    snakeBody[0].setOutlineThickness(-1);
    snakeBody[0].setOutlineColor(sf::Color::Black);
    std::uniform_int_distribution<int> xPosition(lowBounds, 39);
    auto randX = std::bind(xPosition, std::ref(engine));
    std::uniform_int_distribution<int> yPosition(lowBounds, 23);     // path length of 20 pixels I think
    auto randY = std::bind(yPosition, std::ref(engine));
    do
        apple.setPosition(randX()*20+10, randY()*20+10);
    while (Collision_Detect(snakeBody, apple));

    for (int i = 0; i < 2; i++)
    {
        snakeBody.push_back(sf::RectangleShape(sf::Vector2f(20,20)));
        snakeBody[snakeBody.size()-1].setFillColor(sf::Color(100,150,100));
        snakeBody[snakeBody.size()-1].setOutlineThickness(-1);
        snakeBody[snakeBody.size()-1].setOutlineColor(sf::Color::Black);
        snakeBody.back().setPosition(snakeBody.begin()->getPosition().x,
                                     snakeBody.begin()->getPosition().y);
    }

}

//Display all blocks of snake
void Draw_Snake(sf::RenderWindow &window, std::vector<sf::RectangleShape> &snakeBody)
{
    for (int i = 0; i != snakeBody.size(); i++)
        window.draw(snakeBody[i]);
}

// Moves snake's head and tail
void Move_Snake(std::vector<sf::RectangleShape> &snakeBody, sf::Vector2f &lastPosition, int move)
{
    switch (move)
    {
    case Up:
        snakeBody[0].move(0, -20);
        break;
    case Down:
        snakeBody[0].move(0, 20);
        break;
    case Left:
        snakeBody[0].move(-20, 0);
        break;
    case Right:
        snakeBody[0].move(20, 0);
        break;
    default:
        break;
    }
    sf::Vector2f newPosition(lastPosition);
    if (snakeBody.size() > 1)
    {
        for (int i = 1; i != snakeBody.size(); i++)
        {
            lastPosition = snakeBody[i].getPosition();
            snakeBody[i].setPosition(newPosition);
            newPosition = lastPosition;
        }
    }
}

// Apple placement
bool Apple_Placement(int lowBounds, std::default_random_engine &engine,
                     std::vector<sf::RectangleShape> &snakeBody, sf::CircleShape &apple, sf::Clock &immuneTimer)
{
    std::uniform_int_distribution<int> xPosition(lowBounds, 39);
    auto randX = std::bind(xPosition, std::ref(engine));
    std::uniform_int_distribution<int> yPosition(lowBounds, 23);
    auto randY = std::bind(yPosition, std::ref(engine));

    if ((apple.getPosition().x == snakeBody[0].getPosition().x + 10) &&
            (apple.getPosition().y == snakeBody[0].getPosition().y + 10))
    {
        //  for (int i = 0; i < 2; i++)
        //  {
        snakeBody.push_back(sf::RectangleShape(sf::Vector2f(20,20)));
        snakeBody[snakeBody.size()-1].setFillColor(sf::Color(100,150,100));
        snakeBody[snakeBody.size()-1].setOutlineThickness(-1);
        snakeBody[snakeBody.size()-1].setOutlineColor(sf::Color::Black);
        snakeBody.back().setPosition(snakeBody.begin()->getPosition().x, snakeBody.begin()->getPosition().y);
        //  }
        do
            apple.setPosition(randX()*20+10, randY()*20+10);
        while (Collision_Detect(snakeBody, apple));

        immuneTimer.restart();
        return true;
    }
    else
        return false;
}






// Checks body collision and out of bounds
bool Snake_Alive(std::vector<sf::RectangleShape> &snakeBody, sf::Clock &immuneTimer)
{
    if (snakeBody[0].getPosition().x < 0 || snakeBody[0].getPosition().x > 790
            || snakeBody[0].getPosition().y < 0 || snakeBody[0].getPosition().y > 460)
    {
        // snakeBody[0].setFillColor(sf::Color::Yellow);
        return false;
    }


    if(immuneTimer.getElapsedTime().asSeconds() >= .15)
    {
        for (int i = 1; i != snakeBody.size(); i++)
        {
            if (snakeBody[0].getPosition() == snakeBody[i].getPosition())
            {
                // snakeBody[i].setFillColor(sf::Color::Yellow);
                return false;
            }
        }
    }
    return true;
}

int main()
{
    int window_width = 800, window_height = 600;
    int bitsPerPixel = 24, start = 0, mode = 0, score = 0, difficulty = 2, lowBounds = 0;
    std::vector<int> top10scores;
    std::vector<sf::RectangleShape> snakeBody;
    int move;
    bool currentlyPlaying = false;


    // Create main window
    sf::RenderWindow window(sf::VideoMode(window_width, window_height,
                                          bitsPerPixel), "Snake!", sf::Style::Close);
    window.setVerticalSyncEnabled(true);

    // Set the icon
    sf::Image icon;
    if (!icon.loadFromFile("icon.png"))
        return EXIT_FAILURE;
    window.setIcon(icon.getSize().x, icon.getSize().y, icon.getPixelsPtr());

    // Game board
    sf::Texture texture;
    texture.loadFromFile("grass.png");   //replace with game board
    sf::Sprite grass(texture);

    // Apple
    sf::CircleShape apple(10);
    apple.setOutlineThickness(-1); // should be diameter of 20
    apple.setOutlineColor(sf::Color::Black);
    apple.setFillColor(sf::Color::Red);
    apple.setOrigin(apple.getRadius(), apple.getRadius());

    // Random generator
    std::random_device seed_device;
    std::default_random_engine engine(seed_device());

    // Clocks
    sf::Clock moveClock;
    sf::Clock inputClock;
    sf::Clock immuneTimer;

    // Score box
    sf::RectangleShape scoreBox(sf::Vector2f(window_width, window_height - 480));
    scoreBox.setFillColor(sf::Color(0,200,0));
    scoreBox.setOutlineColor(sf::Color::Black);
    scoreBox.setOutlineThickness(-3.f);
    scoreBox.setPosition(0, 480);

    sf::Font font;
    if (!font.loadFromFile("bonzai.ttf"))
        std::cout << "Can't find the font file 'bonzai.ttf'" << std::endl;
    sf::Text scoreText("Score: ", font, 60);
    scoreText.setColor(sf::Color::White);
    scoreText.setPosition(314, 497);

    sf::Text pauseText("GAME PAUSED", font, 80);
    pauseText.setColor(sf::Color::Black);
    pauseText.setPosition(174, 185);

    sf::Text overText("  GAME OVER", font, 78);
    overText.setColor(sf::Color(150,0,0));
    overText.setPosition(174, 185);

    sf::Text newquitText("Pause: 'P'\nNew game: 'Enter'\nQuit to main menu: 'Q'", font, 20);
    newquitText.setColor(sf::Color::Black);
    newquitText.setPosition(5, 525);

    sf::Text highScoreText("", font, 85);
    highScoreText.setColor(sf::Color::Black);

    //High scores
    High_Scores(top10scores);

    if (difficulty == 2)
        lowBounds = 0;

    New_Game(snakeBody, window_width, window_height, engine, apple, score, scoreText, lowBounds);
    // Main game loop
    while (window.isOpen())
    {
        sf::Vector2f lastPosition(snakeBody[0].getPosition().x, snakeBody[0].getPosition().y);
        // Event
        sf::Event event;
        while (window.pollEvent(event))
        {
            // Welcome screen
            if (start <= 1)
            {
                Welcome_Screen(window);
                start++;
            }
            // Close window: Exit
            if (event.type == sf::Event::Closed)
                window.close();
            // Esc pressed: Exit
            if (event.type == sf::Event::KeyPressed && event.key.code
                    == sf::Keyboard::Escape)
                window.close();
            // Q pressed: Exit to main menu
            if (event.type == sf::Event::KeyPressed && event.key.code
                    == sf::Keyboard::Q)
            {
                start = 1;
                mode = 0;
            }
            if (event.type == sf::Event::KeyPressed && event.key.code
                    == sf::Keyboard::Return)
            {
                if (!currentlyPlaying)
                    currentlyPlaying = true;
                move = Down;

                New_Game(snakeBody, window_width, window_height, engine, apple, score, scoreText, lowBounds);
                mode = 1;
                score = 0;
                moveClock.restart();
                inputClock.restart();
                immuneTimer.restart();
            }
            if(event.type == sf::Event::KeyPressed && inputClock.getElapsedTime().asSeconds() >= 0.06) //0.07
            {
                if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Up && move != Down)
                {
                    move = Up;
                    inputClock.restart();
                }
                if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Down && move != Up)
                {
                    move = Down;
                    inputClock.restart();
                }
                if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Left && move != Right)
                {
                    move = Left;
                    inputClock.restart();
                }
                if(event.key.code == sf::Keyboard::Right && move != Left)
                {
                    move = Right;
                    inputClock.restart();
                }
            }
            // P pressed: Pause simulation
            if (event.type == sf::Event::KeyPressed && event.key.code
                    == sf::Keyboard::P)
            {
                if (mode == 1)
                {
                    window.draw(pauseText);
                    window.display();
                }
                mode *= -1;
                moveClock.restart();
                immuneTimer.restart();
                inputClock.restart();
            }
        }
        if (mode == 1)
        {
            window.clear();
            window.draw(grass);
            window.draw(apple);
            Draw_Snake(window, snakeBody);
            window.draw(scoreBox);
            window.draw(scoreText);
            window.draw(newquitText);

            if(moveClock.getElapsedTime().asSeconds() >= .075)         // change back to 0.09
            {
                Move_Snake(snakeBody, lastPosition, move);
                moveClock.restart();
            }
            if(Apple_Placement(lowBounds, engine, snakeBody, apple, immuneTimer))
            {
                if (difficulty == 1)
                    score += 5;
                else if (difficulty == 2)
                    score += 10;
                else
                    score += 20;
                std::string newScore = std::to_string(score);
                scoreText.setString("Score: " + newScore);
            }
            if(!Snake_Alive(snakeBody, immuneTimer))
            {
                window.draw(overText);
                int scorePlacement = Update_Scores(score, top10scores);
                if (scorePlacement != 11)
                {
                    std::string newHighText = std::to_string(scorePlacement+1);
                    highScoreText.setString("#" + newHighText
                                            + " out of top 10 scores!");
                    if (scorePlacement == 9)
                        highScoreText.setPosition(15, 50);
                    else
                        highScoreText.setPosition(30, 50);
                    window.draw(highScoreText);
                }
                window.display();
                mode = 0;
                window.display();
            }

            window.display();
        }
    }
    return EXIT_SUCCESS;
}
like image 424
Austin Avatar asked Jul 31 '15 04:07

Austin


Video Answer


3 Answers

If I read read correctly, your goal(s) are:

Is there a way to make the controls more responsive

For instance it's very difficult to make a tight U-turn (where no additional space is used) consistently.

(1) The following: (Put in an update function that is called every frame)

if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)

will implement smooth movement has indicated here. Also, here is a little article that provides some interesting insight on "event-checking" vs. "isKeyPressed()".

(2) If that doesn't work, then it could possibly be something with:

if(inputClock.getElapsedTime().asSeconds() >= 0.07)

If "inputClock" is delaying your ability to move again, then any barrier (even the small increment of 0.07) could cause non-desired output.

Otherwise, provide more code so I can test it myself, or let me know what you get from my suggestions.

like image 112
Donald Avatar answered Oct 18 '22 18:10

Donald


So what I ended up doing was removing the controls from the timing loop, but making them control a second movement variable instead. The original move is then set to equal the second movement variable within the timing loop. This ended up removing the bug and maintaining good responsiveness.

like image 4
Austin Avatar answered Oct 18 '22 18:10

Austin


You have two bugs in your code that I can see in your original sample code:

  1. You are not paying attention to whether the event is a KeyPressed or KeyReleased event when deciding how to move.
  2. You are then using a timer to workaround this by ignoring other events for a set time after a movement key is pressed.

You need to act only on the KeyPressed events and then remove the timing logic entirely.

Looking through the rest of the trails\answers, I don't think both bugs have been fixed at once...

Just removing the timing logic (as covered in the comments) would result in apparently bizarre behaviour where releasing the movement keys could change the direction of your snake, depending on the order of your multiple key presses\releases.

Checking isKeyPressed (As per @Donald's answer) instead will resolve the press\release issue, but you will also need to decide what to do when more than one is pressed at once. This will probably over-complicate your code when the events will resolve that all for you by returning a chronological ordering of what was pressed.

Your current full program appears to have fixed the KeyPressed issue, but not removed the timer. This will prevent the keys from working as responsively as you need.

like image 2
Peter Brittain Avatar answered Oct 18 '22 20:10

Peter Brittain