基于STM32的贪吃蛇游戏实现(OLED屏)

一、项目概述

本项目使用STM32微控制器和OLED显示屏实现经典贪吃蛇游戏,通过按键控制蛇的移动方向,实现食物收集、碰撞检测和分数计算等功能。项目采用模块化设计,包含游戏逻辑、显示驱动和用户输入处理三大核心模块。

二、硬件配置

  • 主控芯片:STM32F103C8T6(ARM Cortex-M3,72MHz)

  • 显示模块:0.96寸OLED(SSD1306驱动,128×64像素,I2C接口)

  • 输入设备:4个轻触按键(上、下、左、右)

  • 电源:3.3V/5V DC供电

三、系统架构

c 复制代码
+-------------------+     +-------------------+     +-------------------+
|    输入处理模块    |     |    游戏逻辑模块    |     |    显示驱动模块    |
| (按键扫描与消抖)   |<--->| (蛇移动/碰撞检测)  |<--->| (OLED显示控制)    |
+-------------------+     +-------------------+     +-------------------+
                                 |  |  |  |  |  |  |  |  |  |  |  |  |
                                 |  |  |  |  |  |  |  |  |  |  +---->+-------------------+
                                 |  |  |  |  |  |  |  |  |  +-------->|  游戏状态管理     |
                                 |  |  |  |  |  |  |  |  +------------>|  分数计算         |
                                 |  |  |  |  |  |  |  +---------------->|  游戏难度控制     |
                                 |  |  |  |  |  |  +-------------------->|  音效反馈(可选)   |
                                 |  |  |  |  |  +-------------------------------->|                 |
                                 |  |  |  |  +------------------------------------>|                 |
                                 |  |  |  +---------------------------------------->|                 |
                                 |  |  +-------------------------------------------->|                 |
                                 |  +--------------------------------------------------->|                 |
                                 +--------------------------------------------------->|                 |

四、核心代码实现

1. 主程序框架

c 复制代码
#include "stm32f1xx_hal.h"
#include "ssd1306.h"
#include "game.h"
#include "input.h"

int main(void) {
    HAL_Init();
    SystemClock_Config();
    OLED_Init();
    Input_Init();
    
    GameState game = {0};
    InitGame(&game);
    
    while (1) {
        ProcessInput(&game);
        UpdateGame(&game);
        RenderGame(&game);
        HAL_Delay(100); // 控制游戏速度
    }
}

2. 游戏逻辑实现

c 复制代码
// game.h
#define GRID_SIZE 8
#define GRID_WIDTH 16
#define GRID_HEIGHT 8

typedef struct {
    int x, y;
} Point;

typedef struct {
    Point body[64];  // 蛇身最多64节
    int length;
    Point food;
    int direction;  // 0:上, 1:右, 2:下, 3:左
    int score;
    int game_over;
} GameState;

// game.c
void InitGame(GameState* game) {
    // 初始化蛇身
    game->body[0] = (Point){4, 4};
    game->body[1] = (Point){4, 5};
    game->body[2] = (Point){4, 6};
    game->length = 3;
    game->direction = 1; // 初始向右移动
    game->score = 0;
    game->game_over = 0;
    
    GenerateFood(game);
}

void GenerateFood(GameState* game) {
    do {
        game->food.x = rand() % GRID_WIDTH;
        game->food.y = rand() % GRID_HEIGHT;
    } while (IsPointOnSnake(game, game->food));
}

int IsPointOnSnake(GameState* game, Point p) {
    for (int i = 0; i < game->length; i++) {
        if (game->body[i].x == p.x && game->body[i].y == p.y) {
            return 1;
        }
    }
    return 0;
}

void UpdateGame(GameState* game) {
    if (game->game_over) return;
    
    // 保存蛇尾位置(可能用于增长)
    Point prev_tail = game->body[game->length - 1];
    
    // 移动蛇身(从尾部向前更新)
    for (int i = game->length - 1; i > 0; i--) {
        game->body[i] = game->body[i - 1];
    }
    
    // 根据方向移动头部
    switch (game->direction) {
        case 0: // 上
            game->body[0].y--;
            break;
        case 1: // 右
            game->body[0].x++;
            break;
        case 2: // 下
            game->body[0].y++;
            break;
        case 3: // 左
            game->body[0].x--;
            break;
    }
    
    // 检查是否吃到食物
    if (game->body[0].x == game->food.x && game->body[0].y == game->food.y) {
        // 增加长度
        game->body[game->length] = prev_tail;
        game->length++;
        game->score += 10;
        GenerateFood(game);
    }
    
    // 检查碰撞
    CheckCollision(game);
}

void CheckCollision(GameState* game) {
    Point head = game->body[0];
    
    // 检查墙壁碰撞
    if (head.x < 0 || head.x >= GRID_WIDTH || 
        head.y < 0 || head.y >= GRID_HEIGHT) {
        game->game_over = 1;
        return;
    }
    
    // 检查自身碰撞
    for (int i = 1; i < game->length; i++) {
        if (head.x == game->body[i].x && head.y == game->body[i].y) {
            game->game_over = 1;
            return;
        }
    }
}

3. 显示驱动实现

c 复制代码
// display.c
void RenderGame(GameState* game) {
    OLED_Clear();
    
    // 绘制蛇身
    for (int i = 0; i < game->length; i++) {
        DrawBlock(game->body[i].x, game->body[i].y, 1);
    }
    
    // 绘制食物
    DrawBlock(game->food.x, game->food.y, 1);
    
    // 显示分数
    char score_str[20];
    sprintf(score_str, "Score: %d", game->score);
    OLED_ShowString(0, 0, score_str);
    
    // 游戏结束提示
    if (game->game_over) {
        OLED_ShowString(20, 3, "GAME OVER!");
        OLED_ShowString(10, 4, "Press RESET");
    }
    
    OLED_Refresh();
}

void DrawBlock(int x, int y, int fill) {
    // 将网格坐标转换为像素坐标
    int pixel_x = x * GRID_SIZE;
    int pixel_y = y * GRID_SIZE;
    
    for (int i = 0; i < GRID_SIZE; i++) {
        for (int j = 0; j < GRID_SIZE; j++) {
            OLED_DrawPixel(pixel_x + i, pixel_y + j, fill);
        }
    }
}

4. 输入处理实现

c 复制代码
// input.c
#define UP_PIN    GPIO_PIN_0
#define DOWN_PIN  GPIO_PIN_1
#define LEFT_PIN  GPIO_PIN_2
#define RIGHT_PIN GPIO_PIN_3
#define GPIO_PORT GPIOA

void Input_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitStruct.Pin = UP_PIN | DOWN_PIN | LEFT_PIN | RIGHT_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIO_PORT, &GPIO_InitStruct);
}

void ProcessInput(GameState* game) {
    static uint32_t last_press = 0;
    uint32_t now = HAL_GetTick();
    
    if (now - last_press < 200) return; // 防抖延时
    
    if (HAL_GPIO_ReadPin(GPIO_PORT, UP_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 2) game->direction = 0; // 不能向下时才能向上
        last_press = now;
    }
    else if (HAL_GPIO_ReadPin(GPIO_PORT, DOWN_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 0) game->direction = 2; // 不能向上时才能向下
        last_press = now;
    }
    else if (HAL_GPIO_ReadPin(GPIO_PORT, LEFT_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 1) game->direction = 3; // 不能向右时才能向左
        last_press = now;
    }
    else if (HAL_GPIO_ReadPin(GPIO_PORT, RIGHT_PIN) == GPIO_PIN_RESET) {
        if (game->direction != 3) game->direction = 1; // 不能向左时才能向右
        last_press = now;
    }
}

五、游戏功能扩展

1. 难度分级

c 复制代码
// 在游戏状态中添加难度级别
typedef struct {
    // ...其他成员
    int difficulty; // 0:简单, 1:中等, 2:困难
} GameState;

// 根据难度调整游戏速度
void UpdateGameSpeed(GameState* game) {
    switch (game->difficulty) {
        case 0: // 简单
            game->speed = 150; // ms
            break;
        case 1: // 中等
            game->speed = 100;
            break;
        case 2: // 困难
            game->speed = 70;
            break;
    }
}

2. 特殊食物

c 复制代码
// 添加特殊食物类型
typedef enum {
    FOOD_NORMAL,
    FOOD_SPEED_UP,
    FOOD_SLOW_DOWN,
    FOOD_REVERSE
} FoodType;

typedef struct {
    Point pos;
    FoodType type;
    uint32_t spawn_time;
} SpecialFood;

// 随机生成特殊食物
void SpawnSpecialFood(GameState* game) {
    if (rand() % 10 == 0) { // 10%几率生成特殊食物
        SpecialFood sf;
        do {
            sf.pos.x = rand() % GRID_WIDTH;
            sf.pos.y = rand() % GRID_HEIGHT;
        } while (IsPointOnSnake(game, sf.pos) || 
                (sf.pos.x == game->food.x && sf.pos.y == game->food.y));
        
        sf.type = rand() % 4;
        sf.spawn_time = HAL_GetTick();
        
        // 添加到特殊食物列表
    }
}

3. 游戏存档功能

c 复制代码
// 使用Flash存储游戏状态
void SaveGameState(GameState* game) {
    // 擦除Flash扇区
    FLASH_EraseInitTypeDef erase;
    erase.TypeErase = FLASH_TYPEERASE_PAGES;
    erase.PageAddress = 0x0800F000; // 选择页地址
    erase.NbPages = 1;
    
    uint32_t page_error;
    HAL_FLASH_Unlock();
    HAL_FLASHEx_Erase(&erase, &page_error);
    
    // 写入数据
    uint32_t data[8];
    data[0] = game->score;
    data[1] = game->length;
    // ...其他数据
    
    for (int i = 0; i < 8; i++) {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 0x0800F000 + i*4, data[i]);
    }
    
    HAL_FLASH_Lock();
}

参考代码 基于stm32的贪吃蛇游戏实现 OLED屏 www.youwenfan.com/contentcss/183119.html

六、优化与改进

1. 显示优化

c 复制代码
// 使用位图字体提高显示效率
const uint8_t font[][8] = {
    {0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00, 0x00}, // 0
    {0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00}, // 1
    // ...其他字符
};

void OLED_ShowChar(uint8_t x, uint8_t y, char c) {
    if (c < ' ') return;
    c -= ' ';
    for (int i = 0; i < 8; i++) {
        OLED_DrawBuffer(x, y+i, font[c][i]);
    }
}

2. 性能优化

c 复制代码
// 使用双缓冲减少闪烁
uint8_t display_buffer[128][8]; // 显存缓冲区

void OLED_Refresh(void) {
    for (int page = 0; page < 8; page++) {
        OLED_SetPage(page);
        OLED_SetColumn(0);
        for (int col = 0; col < 128; col++) {
            OLED_WriteData(display_buffer[col][page]);
        }
    }
}

3. 增加音效

c 复制代码
// 使用PWM驱动蜂鸣器
void PlaySound(int frequency, int duration) {
    uint32_t period = 1000000 / frequency; // 微秒
    uint32_t cycles = duration * 1000 / period;
    
    for (uint32_t i = 0; i < cycles; i++) {
        HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);
        Delay_us(period / 2);
        HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET);
        Delay_us(period / 2);
    }
}

// 在游戏中添加音效
void UpdateGame(GameState* game) {
    // ...
    if (ate_food) {
        PlaySound(523, 50); // C5音符
    }
    if (game_over) {
        PlaySound(262, 200); // C4音符
    }
}

七、项目资源

1. 硬件连接

OLED引脚 STM32引脚 功能
SDA PB7 I2C数据线
SCL PB6 I2C时钟线
VCC 3.3V 电源
GND GND 地
按键引脚 STM32引脚 功能
UP PA0 上方向
DOWN PA1 下方向
LEFT PA2 左方向
RIGHT PA3 右方向

2. 开发环境

  • IDE: STM32CubeIDE

  • 编译器: GCC ARM Embedded

  • 调试器: ST-Link V2

  • 库依赖:

  • HAL库(STM32CubeMX生成)

  • SSD1306 OLED驱动库

3. 项目结构

c 复制代码
├── Core/
│   ├── Inc/            // 头文件
│   ├── Src/            // 源文件
│   └── Startup/        // 启动文件
├── Drivers/
│   ├── CMSIS/          // 芯片支持包
│   └── STM32F1xx_HAL_Driver/ // HAL库
├── MDK-ARM/            // Keil工程文件
└── STM32CubeIDE/       // Eclipse工程文件

八、总结

本实现展示了如何使用STM32和OLED显示屏创建经典的贪吃蛇游戏。项目包含完整的游戏逻辑、显示驱动和用户输入处理,并提供了多种扩展功能如难度分级、特殊食物和音效反馈。通过模块化设计和优化技术,实现了流畅的游戏体验和高效的资源利用。

该项目的核心价值在于:

  1. 展示了嵌入式游戏开发的基本流程

  2. 提供了OLED显示和按键输入的完整实现

  3. 演示了游戏状态管理和碰撞检测算法

  4. 为后续开发更复杂的嵌入式游戏奠定了基础

相关推荐
狂野小白兔2 小时前
AI游戏制作00——AI制作游戏需要准备的前置条件
人工智能·游戏
恒锐丰科技林技术员2 小时前
EG3014S 半桥驱动芯片:无刷电机与功率变换的优选栅极驱动器件
经验分享·嵌入式硬件·硬件工程
Martina_03213 小时前
AI生成的盔甲换动作后漂移?用6步检查挂点、骨架映射与 Bind Pose
人工智能·游戏·3d·ai·自然语言处理·aigc·游戏策划
沐欣工作室_lvyiyi3 小时前
基于单片机的智能提醒药盒设计(论文+源码)
单片机·嵌入式硬件
常州晟凯电子科技3 小时前
sigmaStar SSC8836Q/SSC8826Q开发笔记之环境搭建和SDK编译
笔记·嵌入式硬件·物联网·视频编解码
ebiobiz4 小时前
Zig 嵌入式底层库选择与 build.zig 组织指南
c语言·c++·嵌入式硬件·mcu
Zw-awa13 小时前
反馈:让系统知道自己做得对不对
stm32
沧沧凉凉14 小时前
一段需求、零素材,Claude Code 派了 33 个代理给我做了个 3D 动作游戏
游戏·ai编程·three.js
女神下凡15 小时前
芯参谋(38):NAND_MCP_电路设计指南
arm开发·单片机·嵌入式硬件·设计规范
嵌入式学习菌16 小时前
FreeRTOS 中断管理实战:BASEPRI 寄存器实验
单片机·嵌入式硬件