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;
}
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.
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.
You have two bugs in your code that I can see in your original sample code:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With