前言:
随着科技的不断进步,嵌入式系统已经渗透到我们生活的方方面面,从家用电器到工业自动化,无处不在。在众多嵌入式应用中,游戏作为一种娱乐形式,不仅能够丰富人们的业余生活,还能有效锻炼逻辑思维和反应能力。本文将详细介绍一款基于ARM开发板GEC6818和嵌入式Linux操作系统开发的俄罗斯方块游戏。这款游戏以其经典的玩法、简洁的界面设计和流畅的运行性能,为用户带来了既富有挑战性又充满乐趣的游戏体验。文章将从设计思路、功能描述、程序流程、各模块实现等方面,全面解析这款游戏的制作过程和关键技术。
gitee:https://gitee.com/q-haodong/test_-arm/tree/master/20240619_test_tetris2
效果演示:https://live.csdn.net/v/405152?spm=1001.2014.3001.5501
基于嵌入式Linux俄罗斯方块
1. 简介
随着嵌入式技术的快速发展,嵌入式系统在各个领域的应用日益广泛。本项目以ARM开发板GEC6818为平台,基于嵌入式Linux操作系统,实现了一款具有基本功能的俄罗斯方块游戏。游戏设计遵循模块化思想,将系统分解为图形显示、触摸事件处理、游戏控制、界面显示、链表管理、移动逻辑以及主控等多个模块,以提高代码的可维护性和扩展性。通过C语言编程,利用多线程技术,实现了方块的移动、变形、随机生成、触屏控制、暂停恢复、嵌套消行和计分等功能。游戏界面简洁直观,提供了分数和等级显示,确保玩家能够轻松跟踪游戏进度。在性能方面,游戏运行流畅,代码规范,附有详细注释和文档,便于理解和维护。此外,通过全面测试,确保了游戏的稳定性和可靠性。最终,本项目不仅锻炼了嵌入式系统开发能力,也提供了一个既具有挑战性又富有趣味性的游戏体验。
2. 总体设计思路及功能描述
2.1 设计思路
本俄罗斯方块游戏的设计采用模块化的编程思想,将游戏分解为多个功能模块,每个模块负责不同的任务。主要模块包括图形显示模块、触摸事件处理模块、游戏控制模块、界面显示模块、链表管理模块、移动逻辑模块以及主控模块。程序使用C语言编写,运行在ARM平台上,利用多线程技术来提高游戏的响应速度和性能。
2.2 功能描述
- 图形显示模块:负责加载和显示BMP图片到屏幕上,支持指定区域的图片显示,用于游戏方块和背景的绘制。
- 触摸事件处理模块:监听触摸屏事件,将用户的触摸操作转换为游戏内的控制指令。
- 游戏控制模块:包含游戏的暂停和重启功能,允许玩家在任何时候暂停游戏,并在适当的时候恢复或重新开始。
- 界面显示模块:管理游戏的开始界面和结束界面,提供用户交互的界面元素。
- 链表管理模块:使用链表数据结构管理游戏中的方块布局,实现方块的动态添加和删除。
- 移动逻辑模块:控制方块的移动、变形和消行等逻辑,确保游戏规则的准确执行。
- 主控模块:作为程序的入口,初始化游戏环境,创建和管理线程,控制游戏的主循环。
2.3 程序流程图
程序流程从初始化游戏环境开始,显示欢迎界面,然后进入一个循环等待用户的触摸操作。一旦检测到触摸事件,程序将处理这些输入并更新游戏状态。随后,程序检查游戏是否结束,如果是,则显示游戏结束界面,并等待用户决定是否重启游戏或退出。如果用户选择重启,程序将重新初始化游戏环境;如果选择退出,则程序将结束运行。
3. 各部分程序功能及详细说明
3.1 游戏界面函数
3.1.1 游戏界面中的图片显示
代码中使用BMP文件格式来显示图像资源,这些图像用于游戏的图形界面,如方块、背景、按钮等元素。显示BMP图像的功能主要通过bmp_show.h头文件中声明的函数来实现。以下是与BMP显示相关的代码片段和解释:
- BMP显示函数声明 : 在bmp_show.h中,声明了两个函数bmp_show_mix和bmp_show_self,用于显示BMP图像:
int bmp_show_mix(int x0, int y0, int width, int height, char *name);
int bmp_show_self(int x0, int y0, int width, int height, char *name);
- BMP文件打开与读取 : 在bmp_show.c中,bmp_show_mix函数首先打开BMP文件,并读取文件状态,然后读取BMP图像数据:
int fd_bmp = open(name, O_RDONLY);
struct stat pst;
fstat(fd_bmp, &pst);
char *buf = (char *)malloc(pst.st_size);
lseek(fd_bmp, 54, SEEK_SET); // 跳过BMP文件头
read(fd_bmp, buf, pst.st_size - 54); // 读取BMP像素数据
- 内存映射Framebuffer : 使用mmap函数将显示设备的帧缓冲区(Framebuffer)映射到用户空间,以便于直接操作显示内存:
char *p = (char *)mmap(NULL, 800 * 480 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd_lcd, 0);
- 图像数据复制 : 将读取的BMP图像数据复制到Framebuffer的指定位置:
for (j = 0; j < height; j++) {
for (i = 0; i < width; i++) {
memcpy(p + lcd_offset, buf + bmp_offset, 3); // 从BMP缓冲区复制到Framebuffer
}
}
- 显示特定区域的BMP图像 : bmp_show_mix函数允许指定显示图像的起始位置(x0, y0)和大小(width, height),这可以用于在界面上显示图像的特定部分:
bmp_show_mix(x0, y0, width, height, name);
- 显示整个BMP图像 :bmp_show_self函数用于显示整个BMP图像,通常用于显示背景或全屏图像:
bmp_show_self(x0, y0, width, height, name);
- 释放资源 : 在图像显示完成后,需要释放分配的内存并关闭内存映射和文件描述符:
munmap(p, 800 * 480 * 4); // 关闭内存映射
close(fd_lcd); // 关闭Framebuffer文件描述符
close(fd_bmp); // 关闭BMP文件描述符
free(buf); // 释放分配的内存
3.1.2 游戏开始界面
-
效果:
-
功能: 展示游戏的初始界面,通常包含游戏的标题、开始游戏的按钮等元素。
-
实现: 使用bmp_show_mix函数加载和显示欢迎屏幕的背景图片
-
代码:
void show_interface_welcome()
{
bmp_show_mix(0, 0, 800, 480, "./tetris_pic/welcom_bk1.bmp"); // 显示欢迎界面背景
int x, y, event_type;
int button_down = 0; // 用于记录按钮是否被按下
while (1)
{
if (capture_touch_events(&x, &y, &event_type) == -1)
{
// 触摸事件捕获失败,可能需要处理错误或退出
break;
}
if (event_type == 1)
{
// 触摸按下事件
if (x > 440 && x < 620 && y > 360 && y < 460)
{
// 用户按下了按钮区域
bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/welcom_bk1_push.bmp"); // 显示按钮按下的图片
button_down = 1;
}
printf("Touch down at (%d, %d)\n", y, y);
}
else if (event_type == 0 && button_down)
{
// 触摸离开事件且按钮之前被按下
bmp_show_self(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, "./tetris_pic/welcom_bk1.bmp"); // 恢复按钮正常状态
button_down = 0;
printf("Touch up at (%d, %d)\n", y, y);
if (x > 440 && x < 620 && y > 360 && y < 460)
{
break; // 离开循环,进入游戏
}
}
}
}
3.1.3 游戏主界面
-
效果:
-
功能: 展示游戏进行中的界面,包括方块下落区域、下一个方块的预览区、分数和等级显示等。
-
实现: 在主循环中持续更新界面,显示当前活动方块、分数和等级。
-
代码:
int main(int argc, char *argv[])
{
show_interface_welcome();
struct ls_all *head;
// 显示背景图片
bmp_show_mix(0, 0, 800, 480, "./tetris_pic/bck.bmp");
int rt;
pthread_t idt, idr;
// 获取两种随机形状并初始化,得到初始化结构体
srand((unsigned int)time(NULL));
shp = ((unsigned int)rand()) % 7 + 1;
shp_next = ((unsigned int)rand()) % 7 + 1;
bk = bk_init(shp);
bk_next = bk_init(shp_next);
// 初始化掉落方块结构体
head = ls_init();
// 初始化分数和速度
score = 0;
speed = 0;
// 显示移动方块 及 提示方块
the_show(bk);
the_show_next(bk_next);
score_show(0); // 显示成绩
// 创建控制方块移动线程
pthread_create(&idt, NULL, auto_down, (void *)head);
// 时间更新线程,时间到且无操作自动更新dir为下落状态
pthread_create(&idr, NULL, time_out, NULL);
while (1)
{
// 锁定互斥锁以安全地读取 dir
pthread_mutex_lock(&dir_mutex);
int current_dir = dir; // 假设这是在循环中读取 dir 变量的地方
pthread_mutex_unlock(&dir_mutex);
if (paused == 1 || gameover == 1 || current_dir == -2)
{
usleep(100);
continue;
}
if (current_dir == -1)
{
// 变形
change_type(bk);
the_show_bck_type(bk);
}
else
{
// 移动
change_dir(bk->p, current_dir);
the_show_bck_dir(bk->p, current_dir);
}
// 移动检查是否越界及掉落到底部
bk = move_check(head, current_dir);
if (bk == NULL