Сделайте игру SFML Snake более отзывчивой к нажатиям клавиш?

Я нашел игру SFML c++ snake, и я возился с ней и изменил несколько вещей, но одна из вещей, которые я не могу понять, - это как сделать ее более гладкой/отзывчивой с помощью клавиш со стрелками. Прямо сейчас он использует enum Direction {Up, Down, Left, Right}; С

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();
            }
}

это расстраивает играть в настоящее время, потому что я не могу двигаться так точно, как хотел бы. Есть ли способ сделать элементы управления более отзывчивыми или они уже реагируют на нажатия клавиш так быстро, как это возможно с помощью my оборудование?

Я полный новичок в ООП и SFML, поэтому у меня возникли проблемы с пониманием часов, а также глядя на змеиные игры на других языках, чтобы перевести его на это. Я могу опубликовать весь код, если нужно.

поэтому я знаю, что это не очень красиво, но вот весь код:

#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("nnnn  ttt Welcome!nt   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;
}

3 ответов


Если я правильно прочитал, ваша цель(ы):

есть ли способ, чтобы сделать управление более отзывчивым

например, очень сложно сделать плотный разворот (где не используется дополнительное пространство) последовательно.

(1) следующее: (поместите функцию обновления, которая называется каждый кадр)

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)

будет осуществлять плавное движение указал здесь. Кроме того, вот немного статьи это дает некоторое интересное представление о "проверке событий" против " isKeyPressed ()".

(2) если это не работает, то это может быть что-то с:

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

Если "inputClock" задерживает вашу способность двигаться снова, то любой барьер (даже небольшое приращение 0.07) может вызвать нежелательный выход.

в противном случае, предоставьте больше кода, чтобы я мог проверить его сам, или дайте мне знать, что вы получаете от моего предложения.


Так что я закончил тем, что удалил элементы управления из цикла синхронизации, но вместо этого заставил их управлять второй переменной движения. Затем исходный ход устанавливается равным второй переменной движения в цикле синхронизации. Это привело к удалению ошибки и поддержанию хорошей отзывчивости.


У вас есть две ошибки в коде, которые я вижу в вашем исходном примере кода:

  1. вы не обращаете внимания на то, является ли событие нажатым или KeyReleased событием при принятии решения о том, как двигаться.
  2. затем вы используете таймер для обхода этого, игнорируя другие события в течение заданного времени после нажатия клавиши перемещения.

вам нужно действовать только на события нажатия клавиш, а затем удалить логику синхронизации полностью.

просматривая остальные тропы\ответы, я не думаю, что обе ошибки были исправлены сразу...

просто удаление логики синхронизации (как описано в комментариях) приведет к, по-видимому, странному поведению, когда освобождение клавиш движения может изменить направление вашей змеи, в зависимости от порядка ваших нескольких нажатий клавиш\релизов.

проверка isKeyPressed (в соответствии с ответом @Donald) вместо этого решит проблему press\release, но вам также нужно будет решить, что делать, когда одновременно нажимается более одного. Это, вероятно, усложнит ваш код, когда события разрешат это все для вас, вернув хронологический порядок того, что было нажато.

ваша текущая полная программа, похоже, исправила проблему нажатия клавиши, но не удалила таймер. Это предотвратит работу ключей так ответственно, как вам нужно.