You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SFML蛇形游戏输入丢失问题及代码结构优化咨询

SFML蛇形游戏问题求助:输入丢失与代码结构优化

问题概述

  • 存在输入丢失问题:有时按键无响应,未松开前一按键时新按键无法触发,怀疑与输入函数的判断顺序有关,希望实现多按键按下检测。
  • 作为首个大型SFML项目,希望获得代码结构、可读性及编程最佳实践的反馈,尤其对update()函数的定位和input()函数的职责划分存疑。

当前代码

#include <SFML/Graphics.hpp>
#include <vector>
#include <stdio.h>

// constants
const int WINDOW_WIDTH = 1024;
const int WINDOW_HEIGHT = 512;
const int FRAMERATE_LIMIT = 8;
const int GRID_SCALE = 32;

// enum
enum Direction {
    UP, DOWN, LEFT, RIGHT, NONE
};

// struct
struct Snake {
    sf::RectangleShape head;
    std::vector<sf::RectangleShape> body;
};

class Game {
    private:
        // movement vectors for each direction
        const sf::Vector2f UP_VECTOR    = sf::Vector2f(0,-GRID_SCALE);
        const sf::Vector2f DOWN_VECTOR  = sf::Vector2f(0, GRID_SCALE);
        const sf::Vector2f LEFT_VECTOR  = sf::Vector2f(-GRID_SCALE, 0);
        const sf::Vector2f RIGHT_VECTOR = sf::Vector2f(GRID_SCALE, 0);

    public:
        // attributes
        sf::RenderWindow window;
        struct Snake snake;
        Direction direction;
        sf::RectangleShape apple;
        std::vector<sf::Vector2f> snake_coordinates;

        // method
        void init() {
            window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "My window");
            window.setFramerateLimit(FRAMERATE_LIMIT);
            snake.head.setSize(sf::Vector2f(GRID_SCALE,GRID_SCALE));
            snake.head.setFillColor(sf::Color::Green);
            snake.head.setPosition(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2);
            snake.body = {snake.head};
            snake.body[0].setPosition(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2);
            snake.body[0].move(-GRID_SCALE,0);
            direction = RIGHT; // 临时修复初始向上移动的问题,有点hack
            draw_apple();
        }

        // method
        void input() {
            // 不确定放这里是否合适,需要重置蛇坐标向量用于自碰撞检测
            snake_coordinates = {};

            sf::Event event;
            while(window.pollEvent(event)) {
                if(event.type == sf::Event::Closed)
                    window.close();
            }

            // 用else if防止斜向移动,如果只用if则可以斜向移动
            if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)      && direction != LEFT)
                direction = RIGHT;
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)  && direction != RIGHT)
                direction = LEFT;
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)    && direction != DOWN)
                direction = UP;
            else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)  && direction != UP)
                direction = DOWN;
        
            // 更新蛇头位置
            sf::Vector2f temp_pos = snake.head.getPosition();
            snake.head.move(getDirectionVector(direction));

            // 更新蛇身位置,同时记录坐标用于自碰撞检测
            sf::Vector2f current_pos;
            snake_coordinates.push_back(current_pos);
            for(int i = 0; i < snake.body.size(); i++) {
                current_pos = snake.body[i].getPosition();
                snake.body[i].setPosition(temp_pos);
                snake_coordinates.push_back(current_pos);
                temp_pos = current_pos;
            }
        }

        // method
        void update() {
            collision_detection();
        }

        // method
        void draw() {
            window.clear(sf::Color::Black);
            window.draw(apple);
            window.draw(snake.head);
            for(sf::RectangleShape body_part : snake.body) {
                window.draw(body_part);
            }
        }

        // method
        void display() {
            window.display();
        }   

        
        // 返回方向对应的移动向量
        sf::Vector2f getDirectionVector(Direction& direction) {
            switch(direction) {
                case UP:
                    return UP_VECTOR;
                case DOWN:
                    return DOWN_VECTOR;    
                case LEFT:
                    return LEFT_VECTOR;
                case RIGHT:
                    return RIGHT_VECTOR;
                default:
                    return sf::Vector2f(0,0);
            }
        }

        // 生成苹果
        void draw_apple() {
            apple.setSize(sf::Vector2f(GRID_SCALE,GRID_SCALE));
            apple.setFillColor(sf::Color::Red);
            // 设置苹果随机位置(ChatGPT复制的代码)
            apple.setPosition(rand() % (WINDOW_WIDTH / GRID_SCALE) * GRID_SCALE, rand() % (WINDOW_HEIGHT / GRID_SCALE) * GRID_SCALE);
        }

        // 碰撞检测
        void collision_detection() {
            sf::Vector2f snake_head_position = snake.head.getPosition();
            sf::Vector2f apple_position = apple.getPosition();
            
            // 蛇头吃苹果
            if(snake_head_position == apple_position) {
                draw_apple();
                snake.body.push_back(snake.head);
            }

            // 蛇头撞边界
            if(snake_head_position.x < 0 || snake_head_position.x > WINDOW_WIDTH - GRID_SCALE || snake_head_position.y < 0 || snake_head_position.y > WINDOW_HEIGHT - GRID_SCALE)
                window.close();

            // 蛇头撞自身
            for(sf::Vector2f current_coord : snake_coordinates) {
                if(current_coord == snake.head.getPosition()) {
                    window.close();
                }
            }
        }
};


int main() {
    Game game;
    game.init();
    while(game.window.isOpen()) {
        game.input();
        game.update();
        game.draw();
        game.display();
    }

    return 0;
}

编译运行说明(Linux)

  • 安装SFML:sudo apt-get install libsfml-dev
  • 编译命令:g++ snake.cpp -lsfml-graphics -lsfml-window -lsfml-system
  • 运行命令:./a.out

已尝试的方法

使用if/else if语句阻止斜向移动,但未解决输入丢失问题。


解决方案与优化建议

1. 解决输入丢失问题

当前else if逻辑会导致同一帧内只有第一个符合条件的按键生效,无法处理连续按键切换。改为记录当前帧所有有效按键,优先保留最后一个输入,同时保留方向反转限制:

void input() {
    snake_coordinates.clear(); // 用clear()替代空赋值,更高效

    sf::Event event;
    while(window.pollEvent(event)) {
        if(event.type == sf::Event::Closed)
            window.close();
    }

    // 记录当前帧的有效按键,最后一个输入覆盖之前的
    Direction new_dir = direction;
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right) && direction != LEFT)
        new_dir = RIGHT;
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left) && direction != RIGHT)
        new_dir = LEFT;
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up) && direction != DOWN)
        new_dir = UP;
    if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down) && direction != UP)
        new_dir = DOWN;
    direction = new_dir;

    // 蛇的移动逻辑移到update()中,让input()只负责处理输入
}

2. 代码结构与职责划分优化

  • input()职责:仅处理用户输入(按键、窗口事件),不负责蛇的移动逻辑,当前移动逻辑属于游戏状态更新,应放到update()。
  • update()职责:整合所有游戏状态更新,包括蛇的移动、碰撞检测、苹果生成逻辑。
  • 拆分函数:把蛇的移动逻辑单独抽成moveSnake(),提升代码可读性和可维护性。
  • 封装私有成员:将Game类的公共成员(如window、snake)改为私有,通过公共接口访问,符合面向对象封装原则。
  • 常量与枚举优化:把Direction枚举和全局常量放到Game类内部或命名空间中,避免全局命名污染。
  • 随机数优化:rand()随机性不足,建议用C++11的<random>库替代:
    #include <random>
    // 在Game类中添加私有随机数对象
    std::random_device rd;
    std::mt19937 gen{rd()};
    std::uniform_int_distribution<int> x_dist{0, (WINDOW_WIDTH/GRID_SCALE)-1};
    std::uniform_int_distribution<int> y_dist{0, (WINDOW_HEIGHT/GRID_SCALE)-1};
    
    // 生成苹果时使用
    apple.setPosition(x_dist(gen)*GRID_SCALE, y_dist(gen)*GRID_SCALE);
    

3. 可读性优化

  • 规范注释:统一用中文或英文注释,避免混合非通用语言注释。
  • 变量命名:保持风格一致,比如snake_coordinates可改为snakeBodyPositions,更直观。
  • 代码格式化:统一缩进,长条件语句拆分换行,提升代码可读性。

调整后的核心流程

// main循环保持不变,但内部函数职责更清晰
while(game.window.isOpen()) {
    game.input();    // 仅处理输入
    game.update();   // 更新游戏状态(移动、碰撞)
    game.draw();     // 绘制画面
    game.display();  // 显示画面
}

内容的提问来源于stack exchange,提问作者aaliyah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 15:54:52