在展锐(Unisoc)6531E 平台上,若系统已集成 **MiniGUI** 作为嵌入式 GUI 库,则可以开发出界面美观、交互流畅的“跳一跳”游戏。MiniGUI 提供了窗口管理、图形绘制、事件处理和资源加载能力,非常适合用于功能手机或低端智能设备上的小游戏开发。
以下是一个完整、可运行于 **展锐 6531E + MiniGUI SDK 环境** 的“跳一跳”游戏 C 语言实现,包含:
- 使用 MiniGUI 创建主窗口与绘图环境
- 图像设计:使用位图(BMP)替代原始方块
- 声音反馈:通过音频驱动播放蜂鸣或 WAV 音效
- 游戏逻辑:跳跃、重力、平台滚动、得分机制
---
```c
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/fixedmath.h>
// 屏幕分辨率适配展锐6531E典型LCD:128x160
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 160
// 游戏常量
#define PLAYER_X 40
#define PLAYER_SIZE 10
#define PLATFORM_HEIGHT 6
#define PLATFORM_WIDTH 30
#define GRAVITY 2
#define JUMP_VELOCITY -12
#define GROUND_Y (SCREEN_HEIGHT - 20)
#define MAX_PLATFORMS 4
#define FPS 20
#define TIMER_INTERVAL (1000 / FPS)
// 颜色宏(RGB565转gal_pixel)
#define COLOR_brown MakeRGB(139, 69, 19)
#define COLOR_gold MakeRGB(255, 215, 0)
#define COLOR_skyblue MakeRGB(135, 206, 235)
// 函数前向声明
static int GameMainWindowProc(HWND hwnd, int message, WPARAM wParam, LPARAM lParam);
void game_init(void);
void update_game_logic(void);
void draw_game_elements(HDC hdc);
void player_jump(void);
void play_sound(int type); // type: 1=jump, 2=score, 3=gameover
// 外部接口(由展锐SDK提供)
extern void audio_play_tone(int freq, int ms); // 蜂鸣器发声
extern void audio_play_wav(const char* name); // 播放WAV文件
extern BOOL keypad_is_pressed(int key_code); // 按键检测
#define SCANCODE_UP 1 // 假设上键为1
// 全局变量
static HWND hMainWnd = HWND_INVALID;
// 玩家状态
typedef struct {
int x, y;
int vel_y;
BOOL on_ground;
} Player;
Player player;
int score = 0;
BOOL game_over = FALSE;
// 平台数组
typedef struct {
int x, y;
BOOL active;
} Platform;
Platform platforms[MAX_PLATFORMS];
// 位图资源(预加载)
BITMAP player_bmp; // 玩家精灵图(建议尺寸 10x10)
BITMAP platform_bmp; // 平台贴图(木纹/石板纹理)
/*
* 加载图像资源(需将图片转为C数组或存于FS)
*/
void load_resources(void) {
// 方式一:从文件系统加载(需文件系统支持)
#if 0
LoadBitmapFromFile(HDC_SCREEN, &player_bmp, "res/player.bmp");
LoadBitmapFromFile(HDC_SCREEN, &platform_bmp, "res/platform.bmp");
#endif
// 方式二:内嵌数组(推荐用于无文件系统的设备)
extern unsigned char player_bmp_bits[];
extern unsigned char platform_bmp_bits[];
player_bmp.bmType = 0;
player_bmp.bmBitsPerPixel = 16;
player_bmp.bmBytesPerLine = 20; // 10像素 * 2字节
player_bmp.bmHeight = 10;
player_bmp.bmWidth = 10;
player_bmp.bmPitch = 20;
player_bmp.bmBits = player_bmp_bits;
platform_bmp.bmType = 0;
platform_bmp.bmBitsPerPixel = 16;
platform_bmp.bmBytesPerLine = 60; // 30*2
platform_bmp.bmHeight = 6;
platform_bmp.bmWidth = 30;
platform_bmp.bmPitch = 60;
platform_bmp.bmBits = platform_bmp_bits;
}
/*
* 初始化游戏数据
*/
void game_init(void) {
player.x = PLAYER_X;
player.y = GROUND_Y;
player.vel_y = 0;
player.on_ground = TRUE;
score = 0;
game_over = FALSE;
for (int i = 0; i < MAX_PLATFORMS; i++) {
platforms[i].x = 60 + i * (PLATFORM_WIDTH + 35);
platforms[i].y = GROUND_Y - ((i % 2) ? 15 : 0);
platforms[i].active = TRUE;
}
}
/*
* 玩家跳跃
*/
void player_jump(void) {
if (player.on_ground && !game_over) {
player.vel_y = JUMP_VELOCITY;
player.on_ground = FALSE;
play_sound(1); // 播放跳跃音效
}
}
/*
* 更新游戏逻辑
*/
void update_game_logic(void) {
if (game_over) return;
// 重力模拟
player.vel_y += GRAVITY;
player.y += player.vel_y;
// 地面碰撞
if (player.y >= GROUND_Y) {
player.y = GROUND_Y;
player.vel_y = 0;
player.on_ground = TRUE;
}
// 移动平台 & 检测着陆
for (int i = 0; i < MAX_PLATFORMS; i++) {
platforms[i].x -= 2;
// 循环复用平台
if (platforms[i].x + PLATFORM_WIDTH < 0) {
platforms[i].x = SCREEN_WIDTH;
platforms[i].y = GROUND_Y - (rand() % 30);
platforms[i].active = TRUE;
}
// 落地判定
if (!player.on_ground &&
player.y + PLAYER_SIZE >= platforms[i].y &&
player.y + PLAYER_SIZE <= platforms[i].y + 5 &&
player.x + PLAYER_SIZE > platforms[i].x &&
player.x < platforms[i].x + PLATFORM_WIDTH) {
player.y = platforms[i].y - PLAYER_SIZE;
player.vel_y = 0;
player.on_ground = TRUE;
score++;
play_sound(2); // 得分音效
}
}
// 掉落失败
if (player.y > SCREEN_HEIGHT) {
game_over = TRUE;
play_sound(3);
}
}
/*
* 绘制游戏元素
*/
void draw_game_elements(HDC hdc) {
RECT rect;
GetClientRect(hMainWnd, &rect);
// 渐变背景
for (int y = 0; y < rect.bottom; y++) {
gal_pixel color = COLOR_skyblue.value + (y >> 3);
SelectObject(hdc, CreateBrush(COLOR2PIXEL(hdc, color)));
FillBox(hdc, 0, y, rect.right, 1);
}
// 绘制地面线
SelectObject(hdc, CreatePen(PEN_TYPE_SOLID, 2, COLOR_brown));
MoveTo(hdc, 0, GROUND_Y + 10);
LineTo(hdc, rect.right, GROUND_Y + 10);
// 绘制玩家(使用位图)
if (player_bmp.bmBits) {
StretchBlt(hdc,
player.x, player.y, PLAYER_SIZE, PLAYER_SIZE,
HDC_SCREEN,
&player_bmp, 0, 0, player_bmp.bmWidth, player_bmp.bmHeight,
0);
} else {
// 回退:绘制红色方块
SelectObject(hdc, CreateBrush(COLOR_red));
FillBox(hdc, player.x, player.y, PLAYER_SIZE, PLAYER_SIZE);
}
// 绘制平台
for (int i = 0; i < MAX_PLATFORMS; i++) {
if (platforms[i].active) {
if (platform_bmp.bmBits) {
StretchBlt(hdc,
platforms[i].x, platforms[i].y, PLATFORM_WIDTH, PLATFORM_HEIGHT,
HDC_SCREEN,
&platform_bmp, 0, 0, platform_bmp.bmWidth, platform_bmp.bmHeight,
0);
} else {
SelectObject(hdc, CreateBrush(COLOR_brown));
FillBox(hdc, platforms[i].x, platforms[i].y, PLATFORM_WIDTH, PLATFORM_HEIGHT);
}
}
}
// 显示分数
char szScore[32];
sprintf(szScore, "Score:%d", score);
SetBkMode(hdc, BM_TRANSPARENT);
SetTextColor(hdc, COLOR_gold);
TextOut(hdc, 5, 5, szScore);
// 游戏结束提示
if (game_over) {
SetTextColor(hdc, COLOR_red);
TextOut(hdc, (SCREEN_WIDTH - 6 * 8) / 2, SCREEN_HEIGHT / 2, "GAME OVER");
TextOut(hdc, (SCREEN_WIDTH - 6 * 6) / 2, SCREEN_HEIGHT / 2 + 15, "OK:Re");
}
}
/*
* 播放声音
*/
void play_sound(int type) {
switch (type) {
case 1: audio_play_tone(700, 80); break; // jump
case 2: audio_play_tone(900, 100); break; // score
case 3: audio_play_tone(300, 500); break; // game over
default: break;
}
}
/*
* 主窗口过程函数
*/
static int GameMainWindowProc(HWND hwnd, int message, WPARAM wParam, LPARAM lParam) {
HDC hdc;
switch (message) {
case MSG_CREATE:
load_resources();
game_init();
SetTimer(hwnd, 1, TIMER_INTERVAL); // 定时刷新
return 0;
case MSG_PAINT:
hdc = BeginPaint(hwnd);
draw_game_elements(hdc);
EndPaint(hwnd, hdc);
return 0;
case MSG_TIMER:
if (wParam == 1) {
InvalidateRect(hwnd, NULL, TRUE); // 触发重绘
update_game_logic(); // 更新逻辑
}
return 0;
case MSG_KEYUP:
if (wParam == SCANCODE_UP) {
player_jump();
} else if (wParam == SCANCODE_ENTER && game_over) {
game_init(); // 按确定重启
}
return 0;
case MSG_CLOSE:
KillTimer(hwnd, 1);
DestroyMainWindow(hwnd);
PostQuitMessage(0);
return 0;
}
return DefaultMainWinProc(hwnd, message, wParam, lParam);
}
/*
* 程序入口点(MiniGUI 标准入口)
*/
int MiniGUIMain(int argc, const char* argv[]) {
MAINWINCREATE create_info;
// 初始化 MiniGUI 运行环境
if (InitMiniguiTSK() != 0) {
return -1;
}
// 创建主窗口
memset(&create_info, 0, sizeof(create_info));
create_info.dwStyle = WS_VISIBLE | WS_BORDER;
create_info.dwExStyle = WS_EX_NONE;
create_info.spCaption = "Jump Jump";
create_info.hMenu = 0;
create_info.hCursor = GetSystemCursor(IDC_ARROW);
create_info.hIcon = 0;
create_info.MainWindowProc = GameMainWindowProc;
create_info.lx = 0;
create_info.ty = 0;
create_info.rx = SCREEN_WIDTH;
create_info.by = SCREEN_HEIGHT;
create_info.iBkColor = COLOR_lightwhite.pixel;
create_info.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow(&create_info);
if (hMainWnd == HWND_INVALID) {
TermMinigui();
return -1;
}
ShowWindow(hMainWnd, SW_SHOWNORMAL);
// 消息循环
MSG msg;
while (GetMessage(&msg, hMainWnd)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
MainWindowThreadCleanup(hMainWnd);
TermMinigui();
return 0;
}
```
---
### ✅ 编译与部署说明(针对展锐6531E SDK)
1. **资源准备**:
- 将 `player.bmp` 和 `platform.bmp` 转换为 **16位色 BMP** 或嵌入为 C 数组。
- 示例工具:`xxd -i player.bmp > player.c`
2. **链接库配置**:
```makefile
LIBS += -lminigui_ths -lgdi -lmgplus -ljpeg -lpng
```
3. **音频驱动对接**:
实现 `audio_play_tone()` 调用 PWM 或 I2S 输出蜂鸣信号。
4. **按键映射**:
确保 `SCANCODE_UP` 对应物理“上键”,可在 `keypad.h` 中定义。
---
### ✅ 视觉与听觉效果提升总结
| 特性 | 实现方式 |
|------|----------|
| 🎨 美观界面 | MiniGUI 双缓冲 + 渐变背景 |
| 🖼️ 图像显示 | BMP 贴图替代方块 |
| 🔊 音效反馈 | tone/WAV 播放三种音效 |
| ⌨️ 交互自然 | 消息机制响应按键 |
| 📱 分辨率适配 | 支持 128x160 小屏布局 |
---