使SFML贪吃蛇游戏对按键的响应更加迅速?

8

我发现了一个基于SFML C++的贪吃蛇游戏,我已经对它进行了一些更改和调整,但其中有一件事情我无法解决,就是如何让箭头按键更加流畅/响应。目前它使用了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();
            }
}

目前玩起来很令人沮丧,因为我的移动不够精确。有没有办法使控件更加响应或者已经如硬件所能响应按键的速度一样快了?

我完全是OOP和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("\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;
}

如果(inputClock.getElapsedTime().asSeconds() >= 0.07),那么这有点可疑... - Manu343726
什么是“更平滑”的意思?按照定义来说,贪吃蛇游戏并不流畅,分段只是从一个位置突然切换到另一个位置。 - Maciej Dziuban
我的意思是更流畅地控制蛇的头部。例如,要始终保持紧密的 U 形转弯(不使用额外空间)非常困难。 - Austin
我也尝试过减少0.07,但结果更糟了。 - Austin
3个回答

6

如果我没理解错的话,您的目标是:

是否有方法使控制更加响应

例如,要在紧凑空间内进行U形转弯(不使用额外空间)非常困难,而且很难做到一致。

(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 的小增量)都可能导致非期望的输出。
否则,请提供更多代码以便我能够自行测试,或者让我知道您从我的建议中得到了什么。

4
所以我最终所做的是从时间循环中移除控制器,而是让它们控制第二个移动变量。然后在时间循环中将原始移动设置为等于第二个移动变量。这最终消除了错误并保持了良好的响应性。

2
您的原始示例代码中有两个错误:
  1. 您没有注意到事件是KeyPressed还是KeyReleased事件时如何移动。
  2. 然后,您使用定时器来解决这个问题,即在按下移动键后忽略其他事件一段时间。

您只需要对KeyPressed事件进行操作,然后完全删除计时逻辑。

浏览其余的trails\answers, 我认为这两个错误并没有同时被修复...

仅删除计时逻辑(如评论所述)将导致明显的奇怪行为,即释放移动键可能会更改蛇的方向,具体取决于多个按键按下/释放的顺序。

检查isKeyPressed(根据@Donald的回答),可以解决按下/释放问题,但是当同时按下多个键时,您还需要决定要做什么。 当事件返回按下的时间顺序时,这可能会使您的代码过于复杂化,事件将为您解决所有问题。

您当前的完整程序似乎已解决KeyPressed问题,但未删除计时器。 这将防止按键响应速度达到您需要的水平。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接