imx6ull 开发板 贪吃蛇, C++11 SDL2 无硬件GPU优化版

imx6ull 单核CPU软件渲染,无硬件GPU加速,贴图会让CPU满负荷运行,所以做了优化。

纯色方块 + 降低到30fps + 不透明粒子 + 点阵数字,软渲染下最低开销。

重要提示:开发板 显示游戏画面的方法,参考前面的 mame 模拟器的文章,烧录或者挂载 Ubuntu 20.04.5 LTS 最小根文件系统,然后安装 xorg 、xserver-xorg-video-fbdev 、 xinit,才能显示画面,buildroot 制作的最小系统安装的 xorg 无法显示游戏画面,也可能是我没配置对,很耗费时间。

|------|-------------------------------|
| 游戏区域 | 40×24 格,800×480 全屏 |
| 普通食物 | 2 个,始终刷新 |
| 毒方块 | 3-7 个,2×2 紫色,15 秒存活,15-25 秒刷新 |
| 中毒 | 强制加速 6 秒,7 级+ 更猛,蛇身变紫 |
| 升级 | 每 5 分升1级,12 级封顶 |
| 分数显示 | 吃1个食物 加1分 |
| 粒子特效 | 吃食物 金色爆发,不透明金色,life 控制消失 |
| 蛇眼 | 方向自适应 |
| 帧率 | 30fps |

开发板,开启高性能模式, CPU 占用率 可再降低 10%左右。

**1.**先看看现在的 CPU 频率策略:

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq

如果输出是 ondemand ,说明 CPU 在动态调频。

**2.**开发板,临时开启高性能模式(792MHz):

echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

注意:performance 高性能模式会增加功耗和发热。

**3.**再次查看策略:

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq

4. CPU 改回 ondemand 模式:

echo ondemand > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

验证:

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

查看当前 CPU 温度(单位通常是毫摄氏度,除以 1000 即为摄氏度)

cat /sys/class/thermal/thermal_zone0/temp

例如:显示 53660 -> 53660 / 1000 = 53.66 °C

下面是贪吃蛇代码:

SnakeGame.cpp

复制代码
#include <SDL2/SDL.h>
#include <deque>
#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <vector>
#include <algorithm>
#include "DigitFont.h"
 
/*
    控制方向:  WSAD 或 方向键
    暂停:      P
    重新开始:  结束后,按 R
    退出游戏:  ESC
*/
 
// 在老式的 C/C++ 代码中,定义常量通常用宏定义 #define
// 现代写法,使用 constexpr 替代宏 #define ,类型更安全
constexpr int Width = 40;
constexpr int Height = 24;
constexpr int CELL = 20;  // 每格像素
constexpr int WIN_W = Width * CELL;   // 800
constexpr int WIN_H = Height * CELL;  // 480
 
namespace Color // 颜色常量
{
    // 背景 深灰
    constexpr Uint8 BG_R = 10,      BG_G = 10,      BG_B = 10;
    // 边框 灰色
    constexpr Uint8 BORDER_R = 60,  BORDER_G = 60,  BORDER_B = 60;
    // 食物 亮红色
    constexpr Uint8 FOOD_R = 255,   FOOD_G = 80,    FOOD_B = 80;
    // 蛇头 翠绿色
    constexpr Uint8 HEAD_R = 0,     HEAD_G = 200,   HEAD_B = 100;
    // 蛇身 暗绿色
    constexpr Uint8 BODY_R = 0,     BODY_G = 150,   BODY_B = 60;
    // 蛇身 中毒效果
    constexpr Uint8 POISON_R = 160,    POISON_G = 0,   POISON_B = 200;
}
 
// 在老式的 C/C++ 代码中,定义枚举用 enum
// 现代写法(强类型枚举), 使用 enum class 防止命名污染, 使用时需要加前缀:Direction::UP
enum class Direction 
{
    UP,DOWN,LEFT,RIGHT
};
 
// 游戏状态枚举
enum class GameState {
    PLAYING,
    PAUSED,
    GAME_OVER
};
 
struct Point 
{
    int x;
    int y;
};
 
// 粒子结构体,用于吃食物特效
struct Particle {
    float x, y;     // 坐标
    float vx, vy;   // 速度
    int life;       // 生命周期
};
 
// 蛇
class Snake
{
public:
    std::deque<Point> body;
    Direction dir;
 
    Snake() : dir(Direction::RIGHT) { Reset(); }
 
    void Reset() {
        body.clear();
        dir = Direction::RIGHT;
        // 初始位置 居中
        int startX = Width / 2;
        int startY = Height / 2;
        body.push_back({startX, startY});   // 蛇头
        body.push_back({startX - 1, startY});
        body.push_back({startX - 2, startY});
    }
 
    // 获取蛇头
    Point Head() const { return body.front(); }
 
    void Move(bool grow) {
        Point next = Head(); // 获取头部
 
        // 计算下一步
        switch (dir) {
            case Direction::UP:    next.y--; break;
            case Direction::DOWN:  next.y++; break;
            case Direction::LEFT:  next.x--; break;
            case Direction::RIGHT: next.x++; break;
        }
 
        // 蛇头移动
        body.push_front(next);
 
        // 没吃到食物:删尾巴,保持长度
        if(!grow) {
            body.pop_back();
        }
    }
 
    // 撞自己检测
    bool CollidesSelf() const {
        for(size_t i = 1; i < body.size(); i++){
            if(body[i].x == body[0].x && body[i].y == body[0].y)
                return true;
        }
        return false;
    }
 
    // 撞墙检测
    bool CollidesWall() const {
        return body[0].x < 0 || body[0].x >= Width || 
               body[0].y < 0 || body[0].y >= Height;
    }
 
    // 检查某个坐标是否被蛇身占据 ( 食物是否出现在蛇身上 )
    bool Occupies(int x, int y) const {
        for(const auto& seg : body) {
            if(seg.x == x && seg.y == y)
                return true;
        }
        return false;
    }
};
 
// 食物
class FoodManager
{
public:
    std::vector<Point> m_normalFoods;   // 普通食物
    std::vector<Point> m_poisonBlocks;  // 毒方块
    Uint32 m_poisonSpawnTime = 0;       // 毒方块出现时间
    bool m_poisonActive = false;

    // 2个食物
    void FoodInit(const Snake& snake) {
        m_normalFoods.clear();
        SpawnOne(snake);
        SpawnOne(snake);
    }

    void SpawnOne(const Snake& snake) {
        Point food;
        do {
            food.x = rand() % Width;
            food.y = rand() % Height;
        } while (snake.Occupies(food.x, food.y) || IsOnAnyFood(food));
        m_normalFoods.push_back(food);
    }

    // 检查蛇头是否吃到某个普通食物,返回索引,-1 表示没吃到
    int CheckEat(const Point& head) {
        for(size_t i = 0; i < m_normalFoods.size() ; i++) {
            if (head.x == m_normalFoods[i].x && head.y == m_normalFoods[i].y) {
                return i;
            }
        }
        return -1;
    }

    void RemoveAndRespawn(int index, const Snake& snake) {
        m_normalFoods.erase(m_normalFoods.begin() + index);
//        SpawnOne(snake);
        if(m_normalFoods.empty()) {
            FoodInit(snake);
        }
    }

    // 毒方块逻辑: 每 15-25 秒随机刷新一波
    void UpdatePoison(const Snake& snake, Uint32 now) {
        if(!m_poisonActive) {
            Uint32 interval = 15000 + (rand() %10000);
            if(now - m_poisonSpawnTime > interval) {
                SpawnPoisonBlocks(snake);
                m_poisonSpawnTime = now;
                m_poisonActive = true;
            }
        }
        // 15 秒后消失
        if(m_poisonActive && now - m_poisonSpawnTime > 15000) {
            m_poisonBlocks.clear();
            m_poisonActive = false;
            m_poisonSpawnTime = now;
        }
    }

    bool IsOnAnyPoison(int x, int y) {
        for (auto& p : m_poisonBlocks) {
            if (x >= p.x && x < p.x + 2 && y >= p.y && y < p.y + 2)
                return true;
        }
        return false;
    }
    // 随即生成 大的毒方块 (  ) 
    void SpawnPoisonBlocks(const Snake& snake) {
        m_poisonBlocks.clear();
        int count = 3 + rand() % 5; // 3-7 个
        for (int i = 0; i < count; i++) {
            Point p;
            bool valid;
            do {
                valid = true;
                p.x = rand() % (Width - 1);
                p.y = rand() % (Height - 1);
                // 检查 2×2 四个格子:不能和蛇身重叠,不能和其他食物/毒方块重叠
                for (int dy = 0; dy < 2 && valid; dy++)
                    for (int dx = 0; dx < 2 && valid; dx++)
                        if (snake.Occupies(p.x + dx, p.y + dy) || 
                            IsOnAnyFood({p.x + dx, p.y + dy}) ||
                            IsOnAnyPoison(p.x + dx, p.y + dy))
                            valid = false;
            } while (!valid);
            m_poisonBlocks.push_back(p);
        }
    }

    // 检查是否碰到毒方块
    int CheckPoison(const Point& head) {
        for (size_t i = 0; i < m_poisonBlocks.size(); i++) {
            Point& p = m_poisonBlocks[i];
            if (head.x >= p.x && head.x < p.x + 2 &&
                head.y >= p.y && head.y < p.y + 2) {
                return i;
            }
    }
    return -1;
}

    void RemovePoison(int index) {
        m_poisonBlocks.erase(m_poisonBlocks.begin() + index);
    }

private:
    bool IsOnAnyFood(const Point& p) {
        for(auto& food : m_normalFoods) {
            if (food.x == p.x && food.y == p.y) {
                return true;
            }
        }
        for(auto& food : m_poisonBlocks) {
            if (food.x == p.x && food.y == p.y) {
                return true;
            }
        }
        return false;
    }
};

// 渲染
class Renderer
{
private:
    SDL_Window* m_Win;
    SDL_Renderer* m_Ren;
    std::vector<Particle> m_Particles; // 粒子容器
public:
    Renderer() {
        // 初始化 SDL2 视频子系统
        if (SDL_Init(SDL_INIT_VIDEO) < 0) {
            fprintf(stderr,"SDL Init Error\n");
            exit(1); // 初始化失败直接退出
        }
        
        // 创建一个窗口 ( 画框 )
        m_Win = SDL_CreateWindow(
            "Snake SDL2",               // 窗口标题
            SDL_WINDOWPOS_CENTERED,     // 窗口 x 坐标: 居中 (自动计算)
            SDL_WINDOWPOS_CENTERED,     // 窗口 y 坐标: 居中
            WIN_W, WIN_H,               // 窗口宽度,窗口高度
            SDL_WINDOW_SHOWN            // 窗口状态标志: 显示窗口
        );
        if (!m_Win) {
            fprintf(stderr,"SDL_CreateWindow Error\n");
            exit(1);
        }
 
        // 创建渲染器, 渲染器是绑定在窗口上的"画笔",所有的绘图命令都通过它执行
        // -1: 让 SDL 自动选择当前平台支持的渲染驱动
        // SDL_RENDERER_ACCELERATED:使用硬件加速(显卡渲染),比软件渲染快得多
        // SDL_RENDERER_PRESENTVSYNC:垂直同步(防止画面撕裂,限制帧率与屏幕刷新率一致)
        m_Ren = SDL_CreateRenderer(m_Win,-1,    
            SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
        if(!m_Ren) {
            fprintf(stderr,"SDL_CreateRenderer Error");
            SDL_DestroyWindow(m_Win);   // 清理已创建的窗口
            exit(1);
        }
    }
 
    ~Renderer() {
        SDL_DestroyRenderer(m_Ren); // 销毁画笔
        SDL_DestroyWindow(m_Win);
        SDL_Quit();
    }

    // 清全屏
    void Clear() {
        // 设置画笔颜色
        SDL_SetRenderDrawColor(m_Ren, Color::BG_R, Color::BG_G, Color::BG_B, 255);
        SDL_RenderClear(m_Ren);
    }
 
    void DrawGrid() {
        SDL_SetRenderDrawColor(m_Ren, 30, 30, 30, 255);  // 比背景稍亮
        // 竖线
        for (int x = 0; x <= Width; x++) {
            SDL_RenderDrawLine(m_Ren, x * CELL, 0, x * CELL, WIN_H);
        }
        // 横线
        for (int y = 0; y <= Height; y++) {
            SDL_RenderDrawLine(m_Ren, 0, y * CELL, WIN_W, y * CELL);
        }
    }
 
    void DrawBorder() {
        SDL_SetRenderDrawColor(m_Ren, Color::BORDER_R, Color::BORDER_G, Color::BORDER_B, 255);
        SDL_Rect r = {0, 0, WIN_W, WIN_H};
        SDL_RenderDrawRect(m_Ren, &r);  // 画空心矩形
    }
 
    // 食物 加脉冲动态效果, tick 程序启动后的毫秒数
    void DrawFood(int x, int y, Uint32 tick) {
        SDL_SetRenderDrawColor(m_Ren, Color::FOOD_R, Color::FOOD_G, Color::FOOD_B, 255);
        
        int pulse = 3 * sin(tick * 0.01);  // 大小波动 ±3 像素
        int size = CELL - 4 + pulse;    // CELL=20 → 16 + pulse → 13~19 像素
        int offset = (CELL - size) / 2; // 居中偏移
        
        SDL_Rect r = {x * CELL + offset, y * CELL + offset, size, size};
        SDL_RenderFillRect(m_Ren, &r);  // 画实心矩形
    }

    void DrawNormalFoods(FoodManager& food ) {
        for(auto& f : food.m_normalFoods) {
            DrawFood(f.x, f.y , SDL_GetTicks());
        }
    }

    // 画 大的毒方块(紫色)
    void DrawPoison(FoodManager& poison) {
        for (auto& p : poison.m_poisonBlocks) {
            SDL_SetRenderDrawColor(m_Ren, Color::POISON_R, Color::POISON_G, Color::POISON_B,255);
            SDL_Rect r = {p.x * CELL, p.y * CELL, CELL*2 - 1, CELL*2 - 1};
            SDL_RenderFillRect(m_Ren, &r);
        }
    }
 
    void DrawSnake(const std::deque<Point>& body , Direction snakeDir, bool poisoned) {
        for (size_t i = 0; i < body.size(); i++) {
            int x = body[i].x * CELL;
            int y = body[i].y * CELL;
 
            if (i == 0) {
                // 蛇头
                SDL_SetRenderDrawColor(m_Ren, Color::HEAD_R, Color::HEAD_G, Color::HEAD_B, 255);
            } else {
                // 蛇身
                if(poisoned)
                    SDL_SetRenderDrawColor(m_Ren, Color::POISON_R, Color::POISON_G, Color::POISON_B,255);
                else
                    SDL_SetRenderDrawColor(m_Ren, Color::BODY_R, Color::BODY_G, Color::BODY_B, 255);
            } 
            SDL_Rect r = {x, y, CELL - 1, CELL - 1};
            SDL_RenderFillRect(m_Ren, &r); // 画实心矩形
 
            // 画蛇头眼睛 (i == 0) , 根据 m_Snake.dir 来判断眼睛位置
            if (i == 0) {
                SDL_SetRenderDrawColor(m_Ren, 255, 255, 255, 255); // 白眼
 
                const int eyeSize = 3;  // 眼睛大小
                const int margin = 4;   // 眼睛距离边缘的距离
                SDL_Rect eye1, eye2;
                switch (snakeDir) {
                    case Direction::UP:
                        // 向上:眼睛在顶部的左右两边
                        eye1 = {x + margin, y + margin, eyeSize, eyeSize};             // 左眼
                        eye2 = {x + CELL - margin - eyeSize, y + margin, eyeSize, eyeSize}; // 右眼
                        break;
                    case Direction::DOWN:
                        // 向下:眼睛在底部的左右两边
                        eye1 = {x + margin, y + CELL - margin - eyeSize, eyeSize, eyeSize};
                        eye2 = {x + CELL - margin - eyeSize, y + CELL - margin - eyeSize, eyeSize, eyeSize};
                        break;
                    case Direction::LEFT:
                        // 向左:眼睛在左侧的上下两边
                        eye1 = {x + margin, y + margin, eyeSize, eyeSize};              // 上眼
                        eye2 = {x + margin, y + CELL - margin - eyeSize, eyeSize, eyeSize}; // 下眼
                        break;
                    case Direction::RIGHT:
                        // 向右:眼睛在右侧的上下两边
                        eye1 = {x + CELL - margin - eyeSize, y + margin, eyeSize, eyeSize};
                        eye2 = {x + CELL - margin - eyeSize, y + CELL - margin - eyeSize, eyeSize, eyeSize};
                        break;
                }
                
                SDL_RenderFillRect(m_Ren, &eye1);
                SDL_RenderFillRect(m_Ren, &eye2);
            }
        }
    }
 
    // 添加粒子特效
    void AddParticles(int x, int y, int count) {
        for (int i = 0; i < count; i++) {
            Particle p;
            p.x = x * CELL + CELL / 2;
            p.y = y * CELL + CELL / 2;
            // 随机速度 (-3 到 3)
            p.vx = (rand() % 60 - 30) / 10.0f;
            p.vy = (rand() % 60 - 30) / 10.0f;
            p.life = 30 + rand() % 20; // 存活时间
            m_Particles.push_back(p);
        }
    }
 
    // 更新并绘制粒子
    void UpdateParticles() {
        // 更新所有粒子
        for (auto& p : m_Particles) {
            p.x += p.vx;
            p.y += p.vy;
            p.life--;
        }
        
        // 移除死亡的
        m_Particles.erase(
            std::remove_if(m_Particles.begin(), m_Particles.end(),
                [](const Particle& p) { return p.life <= 0; }),
            m_Particles.end()
        );
        
        // 绘制存活粒子
        SDL_SetRenderDrawColor(m_Ren, 255, 255, 100, 255); // 金色粒子
        for (auto& p : m_Particles) {
            SDL_Rect r = {(int)p.x, (int)p.y, 4, 4};
            SDL_RenderFillRect(m_Ren, &r);
        }
    }

    void DrawDigit(int x, int y, int digit, int scale) {
        SDL_SetRenderDrawColor(m_Ren, 255, 255, 255, 255);
        for (int row = 0; row < 8; row++)
            for (int col = 0; col < 5; col++)
                if (DIGIT[digit][row][col]) {
                    SDL_Rect r = {x + col*scale, y + row*scale, scale, scale};
                    SDL_RenderFillRect(m_Ren, &r);
                }
    }

    void DrawScore(int x, int y, int num, int scale) {
        char buf[16];
        snprintf(buf, sizeof(buf), "%d", num);
        for (int i = 0; buf[i]; i++)
            DrawDigit(x + i*6*scale, y, buf[i]-'0', scale);
    }
 
    void Present() {
        // 把画好的内容, 呈现到屏幕上
        SDL_RenderPresent(m_Ren);
    }
};
 
// 游戏
class Game
{
    Snake m_Snake;
    FoodManager m_Food;
    Renderer m_Renderer;
    int m_Score, m_Level;
    Uint32 m_Speed;
    Uint32 m_LastTick;
    Uint32 m_PoisonTimer;   // 中毒时间
    bool m_Poisoned ;
    bool m_Running;
    GameState m_State;
 
    // 本帧暂存的方向, 修复"自杀 Bug" :一帧内连续按两个键,蛇反向撞到自己
    Direction m_PendingDir;  

public:
    Game() : m_Score(0),m_Level(1),m_Speed(200),m_LastTick(0),
             m_PoisonTimer(0), m_Poisoned(false), m_Running(true),
             m_State(GameState::PLAYING), m_PendingDir(m_Snake.dir)
    {
        srand(time(0));             // 初始化随机种子
        m_Food.FoodInit(m_Snake);   // 初始化食物
    }
 
    void Run() {
        const Uint32 FRAME_TIME = 33;  // 30fps
        SDL_Event e;
        m_LastTick = SDL_GetTicks(); // ms
 
        while(m_Running) {
            Uint32 frameStart = SDL_GetTicks();
 
            HandleEvents(e);    // 事件处理
            if (m_State == GameState::PLAYING) { // 游戏暂停按键
                Update();
            }
            else{
                // 非游戏进行中(暂停/死亡),加延迟防止 CPU 空转
                SDL_Delay(32); 
            }

            Render(); // 渲染
 
            Uint32 frameTime = SDL_GetTicks() - frameStart;
            
            // 强制至少休息 5ms,防止把系统卡死
            if (frameTime < FRAME_TIME) {
                SDL_Delay(FRAME_TIME - frameTime);
            } else {
                // 如果卡顿了,也要让出 CPU 给系统 1ms
                SDL_Delay(1); 
            }
        }
    }
 
private:
    void HandleEvents(SDL_Event& e) {
        // SDL_PollEvent 会从事件队列里取出事件(如键盘按下、鼠标移动、点击关闭按钮)
        while(SDL_PollEvent(&e)) {
            if (e.type == SDL_QUIT ) { 
                m_Running = false;  // 点击关闭按钮,退出循环
                return;
            } 
            if (e.type == SDL_KEYDOWN) {
                if(e.key.keysym.sym == SDLK_ESCAPE) {
                    m_Running = false;
                    return; // ESC 直接退出循环
                }
                if(m_State == GameState::PLAYING) {
                    HandlePlayingInput(e.key.keysym.sym);
                }
                else if (m_State == GameState::PAUSED) {
                    HandlePausedInput(e.key.keysym.sym);
                }
                else if (m_State == GameState::GAME_OVER) {
                    HandleGameOverInput(e.key.keysym.sym);
                }
            }
        }
    }
 
    void HandlePlayingInput(SDL_Keycode key) {
        switch (key) {
            case SDLK_w: case SDLK_UP:
                    if (m_Snake.dir != Direction::DOWN) 
                        m_PendingDir = Direction::UP; 
                break;
            case SDLK_s: case SDLK_DOWN:
                if (m_Snake.dir != Direction::UP) 
                    m_PendingDir = Direction::DOWN; 
                break;
            case SDLK_a: case SDLK_LEFT:
                if (m_Snake.dir != Direction::RIGHT) 
                    m_PendingDir = Direction::LEFT; 
                break;
            case SDLK_d: case SDLK_RIGHT:
                if (m_Snake.dir != Direction::LEFT) 
                    m_PendingDir = Direction::RIGHT; 
                break;
            case SDLK_p: m_State = GameState::PAUSED;
                break;
        }
    }
 
    void HandlePausedInput(SDL_Keycode key) {
        if (key == SDLK_p) m_State = GameState::PLAYING;
    }
 
    void HandleGameOverInput(SDL_Keycode key) {
        if (key == SDLK_r) ResetGame();
    }
 
    void Update() {
        Uint32 now = SDL_GetTicks();

        // 中毒时间 6秒
        if(m_Poisoned && (now - m_PoisonTimer > 6000)) {
            m_Poisoned = false;
            UpdateLevel();  // 恢复当前等级速度
        }

        // 毒方块更新
        m_Food.UpdatePoison(m_Snake, now);

        // 中毒时 用中毒速度,否则用正常速度
        Uint32 Speed = 60;
        if (m_Level > 9) 
            Speed = 25;
        else if(m_Level > 6)
            Speed = 35;
        Uint32 currentSpeed = m_Poisoned ? Speed : m_Speed;
        if (now - m_LastTick < currentSpeed) 
            return;
        m_LastTick = now;
 
        // 预判下一步位置
        m_Snake.dir = m_PendingDir;
        Point nextPos = m_Snake.Head();
        switch (m_Snake.dir) {
            case Direction::UP:    nextPos.y--; break;
            case Direction::DOWN:  nextPos.y++; break;
            case Direction::LEFT:  nextPos.x--; break;
            case Direction::RIGHT: nextPos.x++; break;
        }

        int poisonIndex = m_Food.CheckPoison(nextPos);
        if(poisonIndex >= 0 ) {
            m_Poisoned = true;
            m_PoisonTimer = now;
            m_Food.RemovePoison(poisonIndex);
        }

        // 检测吃到哪个食物
        int ateIndex = m_Food.CheckEat(nextPos);
        bool ate = (ateIndex >= 0);
 
        // 蛇吃到食物时 尾巴不删除 身体增长, 没吃到食物时 尾巴删除 移动
        m_Snake.Move(ate);

        // 碰撞墙壁检测,碰撞自身检测
        if (m_Snake.CollidesWall() || m_Snake.CollidesSelf()) {
            m_State = GameState::GAME_OVER;
            return;
        }
 
        // 如果吃到食物,更新分数和食物位置
        if(ate) {
            m_Score ++;
            // 粒子特效在被吃食物的位置
            Point foodPos = m_Food.m_normalFoods[ateIndex];
            // 生成粒子特效
            m_Renderer.AddParticles(foodPos.x, foodPos.y, 20);
            // 更新食物
            m_Food.RemoveAndRespawn(ateIndex, m_Snake);

            // 升级逻辑
            UpdateLevel();
        }

        
    }
 
    /*
        绘制流程:三步走
        1. Clear(清空画布):用背景色覆盖掉上一帧的内容。
        2. Draw(绘制内容):画矩形、画线、画贴图。
        3. Present(呈现):这一步最关键
    */
    void Render() {
        m_Renderer.Clear();
//        m_Renderer.DrawGrid();    // 无硬件GPU,网格会增加 cpu 占用率
        m_Renderer.DrawBorder();
        m_Renderer.DrawNormalFoods(m_Food);
        m_Renderer.DrawPoison(m_Food);
        m_Renderer.DrawSnake(m_Snake.body, m_Snake.dir,m_Poisoned);
        m_Renderer.UpdateParticles();
        m_Renderer.DrawScore(385, 405, m_Score, 2);
        m_Renderer.Present();
    }

    void ResetGame() {
        m_Snake.Reset();
        m_PendingDir = m_Snake.dir;
        m_Food.FoodInit(m_Snake);
        m_Score = 0;
        m_Level = 1;
        m_Speed = 200;
        m_State = GameState::PLAYING;
        m_LastTick = SDL_GetTicks();
    }

    void UpdateLevel() {
        m_Level = m_Score / 5 + 1;
        if (m_Level > 12) m_Level = 12;
        m_Speed = 200 - (m_Level - 1) * 15;  // 1级200ms → 12级35ms
    }
};
 
int main()
{
    Game game;
    game.Run();
 
    return 0;
}

数字点阵 DigitFont.h 文件代码:

复制代码
#pragma once

// 5×8 点阵数字 0-9
static constexpr bool DIGIT[10][8][5] = {
    // 0
    {{0,1,1,1,0},{1,0,0,0,1},{1,0,0,0,1},{1,0,0,0,1},
    {1,0,0,0,1},{1,0,0,0,1},{1,0,0,0,1},{0,1,1,1,0}},
    // 1
    {{0,0,1,0,0},{0,1,1,0,0},{0,0,1,0,0},{0,0,1,0,0},
    {0,0,1,0,0},{0,0,1,0,0},{0,0,1,0,0},{0,1,1,1,0}},
    // 2
    {{0,1,1,1,0},{1,0,0,0,1},{0,0,0,0,1},{0,0,0,1,0},
    {0,0,1,0,0},{0,1,0,0,0},{1,0,0,0,0},{1,1,1,1,1}},
    // 3
    {{1,1,1,1,0},{0,0,0,0,1},{0,0,0,0,1},{0,0,1,1,0},
    {0,0,0,0,1},{0,0,0,0,1},{0,0,0,0,1},{1,1,1,1,0}},
    // 4
    {{1,0,0,0,1},{1,0,0,0,1},{1,0,0,0,1},{0,1,1,1,1},
    {0,0,0,0,1},{0,0,0,0,1},{0,0,0,0,1},{0,0,0,0,1}},
    // 5
    {{1,1,1,1,1},{1,0,0,0,0},{1,0,0,0,0},{1,1,1,1,0},
    {0,0,0,0,1},{0,0,0,0,1},{0,0,0,0,1},{1,1,1,1,0}},
    // 6
    {{0,1,1,1,0},{1,0,0,0,0},{1,0,0,0,0},{1,1,1,1,0},
    {1,0,0,0,1},{1,0,0,0,1},{1,0,0,0,1},{0,1,1,1,0}},
    // 7
    {{1,1,1,1,1},{0,0,0,0,1},{0,0,0,1,0},{0,0,1,0,0},
    {0,1,0,0,0},{0,1,0,0,0},{0,1,0,0,0},{0,1,0,0,0}},
    // 8
    {{0,1,1,1,0},{1,0,0,0,1},{1,0,0,0,1},{0,1,1,1,0},
    {1,0,0,0,1},{1,0,0,0,1},{1,0,0,0,1},{0,1,1,1,0}},
    // 9
    {{0,1,1,1,0},{1,0,0,0,1},{1,0,0,0,1},{0,1,1,1,1},
    {0,0,0,0,1},{0,0,0,0,1},{0,0,0,0,1},{0,1,1,1,0}}
};

在开发板直接编译 源码:

g++ -std=c++11 SnakeGame.cpp -o SnakeGame -lSDL2

g++ -std=c++11 -O2 SnakeGame.cpp -o SnakeGame -lSDL2 ( -O2 优化 )

在开发板的屏幕,运行游戏:

xinit ./SnakeGame -- /usr/bin/X :0

相关推荐
anscos1 小时前
为CUDA 代码引入静态分析
c++·工业软件·功能检测
众少成多积小致巨2 小时前
C++ 规范参考(中)
c++
小保CPP2 小时前
OpenCV C++基于AI模型的场景文本识别(OCR)
c++·人工智能·opencv·计算机视觉·ocr
shylyly_2 小时前
C++中的类型转换
开发语言·c++·匿名对象·隐式类型转换·拷贝优化
jinyishu_2 小时前
哈希表原理与开放定址法C++实现
c++·哈希算法·散列表
我头发多我先学2 小时前
Linux入门:简要认识Linux和基础指令
linux·运维·服务器
逍遥德2 小时前
运维技术栈Linux+docker+Kubernetes+Jenkins/GitLab CI 知识点详细列表
linux·运维·docker
小龙报3 小时前
【优选算法】1. 水果成蓝 2.找到字符串中所有字母的异位词
java·c语言·数据结构·数据库·c++·redis·算法
冰封之寂3 小时前
Docker 部署与基础命令详解:从安装到容器管理全流程指南
linux·运维·docker·容器