【STM32】贪吃蛇 [阶段2](嵌入式进阶方向)

这篇博客是 承接:【项目思维】贪吃蛇(嵌入式进阶方向)中 聚焦于阶段 2 增强游戏可玩性、观赏性,提升用户交互体验,以修正 菜单系统设计与实现,这是游戏的控制操作界面,提升项目的专业性 与 用户的交互式体验的关键环节。

🎮 菜单系统实现详解(贪吃蛇嵌入式版)

目标:为贪吃蛇游戏添加一个完整的图形菜单系统,支持选择游戏、设置、声音、难度等选项。

一、菜单系统的核心概念

菜单系统的本质是:

状态驱动界面: 每个菜单页是一个状态;
选项列表渲染: 显示当前菜单项;
按键导航控制: 上下选择,左右切换,确认/返回。

二、工程代码实现

模块职责:

复制代码
功能							说明
菜单结构定义					菜单项、页、选中项
菜单渲染						在OLED/TFT 上绘制菜单
按键响应						处理上下左右/确认返回按键事件
状态切换						进入游戏、设置参数、退出等
📦 菜单结构定义(menu.h)[基于模块化的结构设计来编程]
c 复制代码
#ifndef __MENU_H__
#define __MENU_H__

typedef enum {
    MENU_MAIN,
    MENU_DIFFICULTY,
    MENU_SOUND,
    MENU_ABOUT
} MenuPage;

typedef enum {
    DIFF_EASY,
    DIFF_NORMAL,
    DIFF_HARD
} Difficulty;

typedef struct {
    MenuPage current_page;
    uint8_t selected_index;
    uint8_t total_items;
} MenuState;

// 全局菜单状态
extern MenuState menu;

void Menu_Init(void);
void Menu_Draw(void);
void Menu_Next(void);       // 向下
void Menu_Previous(void);   // 向上
void Menu_Select(void);     // 确认
void Menu_Back(void);       // 返回

Difficulty Menu_GetDifficulty(void);
uint8_t Menu_IsSoundOn(void);
uint8_t Menu_IsGameStartRequested(void);

#endif

三、选项设计(菜单界面)

1. 主菜单(MENU_MAIN)
复制代码
> ▶ Start Game
  ▶ Difficulty
  ▶ Sound: ON
  ▶ About
2. 难度菜单(MENU_DIFFICULTY)
复制代码
> ◉ Easy
  ○ Normal
  ○ Hard
3. 声音菜单(MENU_SOUND)
复制代码
> Sound: ON
  Sound: OFF

🧩 四、实现主逻辑(menu.c

1. 全局状态
c 复制代码
MenuState menu = {
    .current_page = MENU_MAIN,
    .selected_index = 0,
    .total_items = 4
};

static Difficulty current_difficulty = DIFF_NORMAL;
static uint8_t sound_on = 1;
static uint8_t game_start = 0;
2. 渲染函数
c 复制代码
void Menu_Draw(void) {
    OLED_Clear();

    switch (menu.current_page) {
        case MENU_MAIN:
            OLED_ShowString(0, 0, menu.selected_index == 0 ? "> Start Game" : "  Start Game", 12);
            OLED_ShowString(0, 12, menu.selected_index == 1 ? "> Difficulty" : "  Difficulty", 12);
            OLED_ShowString(0, 24, menu.selected_index == 2 ? "> Sound: ON" : "  Sound: ON", 12);
            OLED_ShowString(0, 36, menu.selected_index == 3 ? "> About" : "  About", 12);
            break;

        case MENU_DIFFICULTY:
            OLED_ShowString(0, 0, current_difficulty == DIFF_EASY   ? "> ◉ Easy"   : "  ○ Easy", 12);
            OLED_ShowString(0, 12, current_difficulty == DIFF_NORMAL ? "> ◉ Normal" : "  ○ Normal", 12);
            OLED_ShowString(0, 24, current_difficulty == DIFF_HARD   ? "> ◉ Hard"   : "  ○ Hard", 12);
            break;

        case MENU_SOUND:
            OLED_ShowString(0, 0, sound_on ? "> ON" : "  ON", 12);
            OLED_ShowString(0, 12, !sound_on ? "> OFF" : "  OFF", 12);
            break;

        case MENU_ABOUT:
            OLED_ShowString(0, 0, "Snake v1.0", 12);
            OLED_ShowString(0, 12, "By You", 12);
            OLED_ShowString(0, 24, "Press Back", 12);
            break;
    }

    OLED_Refresh();
}
3. 按键响应函数
c 复制代码
void Menu_Next(void) {
    if (++menu.selected_index >= menu.total_items) {
        menu.selected_index = 0;
    }
    Menu_Draw();
}

void Menu_Previous(void) {
    if (menu.selected_index == 0)
        menu.selected_index = menu.total_items - 1;
    else
        menu.selected_index--;
    Menu_Draw();
}

void Menu_Select(void) {
    switch (menu.current_page) {
        case MENU_MAIN:
            switch (menu.selected_index) {
                case 0: game_start = 1; break;
                case 1: menu.current_page = MENU_DIFFICULTY; menu.selected_index = 0; menu.total_items = 3; break;
                case 2: menu.current_page = MENU_SOUND; menu.selected_index = 0; menu.total_items = 2; break;
                case 3: menu.current_page = MENU_ABOUT; menu.selected_index = 0; menu.total_items = 1; break;
            }
            break;
        case MENU_DIFFICULTY:
            current_difficulty = (Difficulty)menu.selected_index;
            menu.current_page = MENU_MAIN;
            menu.selected_index = 1;
            menu.total_items = 4;
            break;
        case MENU_SOUND:
            sound_on = menu.selected_index == 0 ? 1 : 0;
            menu.current_page = MENU_MAIN;
            menu.selected_index = 2;
            menu.total_items = 4;
            break;
        case MENU_ABOUT:
            menu.current_page = MENU_MAIN;
            menu.selected_index = 3;
            menu.total_items = 4;
            break;
    }
    Menu_Draw();
}

void Menu_Back(void) {
    menu.current_page = MENU_MAIN;
    menu.selected_index = 0;
    menu.total_items = 4;
    Menu_Draw();
}
4. 主循环中调用菜单逻辑(main.c
c 复制代码
int main(void) {
    OLED_Init();
    KEY_Init();
    Menu_Init();
    Menu_Draw();

    while (1) {
        if (KEY_UP_PRESSED())      Menu_Previous();
        else if (KEY_DOWN_PRESSED()) Menu_Next();
        else if (KEY_OK_PRESSED())   Menu_Select();
        else if (KEY_BACK_PRESSED()) Menu_Back();

        if (Menu_IsGameStartRequested()) {
            Snake_SetDifficulty(Menu_GetDifficulty());
            Sound_SetEnabled(Menu_IsSoundOn());
            Game_Start();
            Menu_Init(); // 重置菜单状态
            Menu_Draw();
        }

        HAL_Delay(100);
    }
}
最终效果:
复制代码
OLED 或 TFT 屏幕显示:

> ▶ Start Game
  ▶ Difficulty
  ▶ Sound: ON
  ▶ About

按键上下移动光标,确认后进入子菜单。
另外,还可以扩展功能:
复制代码
扩展							实现
可滑动菜单					支持超过 4 项,滚动显示
图标菜单						每项带图标,提升可视性
多语言支持					菜单文字抽象为可切换语言资源
动画切换						页面切换时添加过渡动画

虽然菜单系统相比于贪吃蛇 或者 其他项目的内部实现逻辑 显得过于简单,但是这却是很关键的环节之一,几乎在每一个项目中都会需要,菜单系统是游戏的"导航大脑",实现需要结合 状态机、输入控制和界面渲染三者的融合运用。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!

相关推荐
一枝小雨5 小时前
【DMA】深入解析DMA控制器架构与运作原理
stm32·单片机·嵌入式硬件·系统架构·dma·嵌入式·arm
时序数据说7 小时前
时序数据库 IoTDB:支撑万亿级物联网设备的基石
大数据·数据库·物联网·时序数据库·iotdb
时序数据说7 小时前
哪些行业需要使用时序数据库?
大数据·数据库·物联网·时序数据库
与你诗画9 小时前
为什么单片机的外接晶振要并连两个电容?
c语言·驱动开发·单片机·嵌入式硬件·硬件架构·硬件工程
翰霖努力成为专家9 小时前
STM32,新手学习
stm32·嵌入式硬件·学习
蓁蓁啊9 小时前
VMware 性能优化完整指南
开发语言·单片机·嵌入式硬件·物联网·性能优化·鸿蒙系统
一枝小雨9 小时前
【DMA】DMA入门:理解DMA与CPU的并行
单片机·系统架构·dma·嵌入式·arm
liujing1023292910 小时前
Day08_单片机-ADC和DMA
单片机·嵌入式硬件
华清远见IT开放实验室10 小时前
华清远见携STM32全矩阵产品及创新机器狗亮相2025 STM32研讨会,共启嵌入式技术探索新程
linux·人工智能·stm32·单片机·嵌入式硬件·虚拟仿真
蜀黍@猿11 小时前
【GD32】中断系统
单片机·嵌入式硬件