|
作品名称:掌上游戏机
作者:nemon
https://bbs.eeworld.com.cn/home.php?mod=space&uid=424743
以前有人统计说大众最热衷3个G,其中为首的就是GAME。看到核心为平头哥CH2601芯片的RVB2601开发板的片上资源、板载资源都很丰富。于是基于这个平台,制作了掌上游戏机的原型。
本项目完全基于RVB2601开发板本身资源,以用户按键K1、K2作为输入,OLED作为输出,自造了位图、字体、抽象屏幕、游戏应用框架4个车轮,优化了oled的刷新算法,手工绘制了全部所需位图,编制出主菜单和flappy bird共2个应用。
操作过程:开机后显示菜单。
根据屏幕提示,按右键(B)可以更换选项,按左键(A)运行游戏。
根据屏幕提示,按右键(B)退出,按左键(A)开始游戏。
在游戏中,按左键(A)提升小鸟,钻过缺口得一分,撞倒水管游戏结束。
|
|
|
通过GPIO得到按键操作,经mpu处理,通过SPI接口,更新oled屏 |
|
|
|
main函数调用csi_gpio_pin进行初始化等。Menu和FlappyBird实现Game结构体,main函数调用Menu并传递事件给它;menu调用FlappyBird并传递事件给它。各种Game都使用Screen更新oled。Font使用Image存储点阵,Screen及各种Game都依赖Image、Font。 |
|
|
|
父过程调用game的init来获取单例并初始化,game自己控制状态,在收到父过程通过event_handle传过来事件后,决定如何处理,并返回状态。当game将自己状态置为退出后,父进程接管控制流。 |
1、源码文件清单
以上文件,以文件名对应第二部分“组件关系图”中的各组件。
结构体IMAGE包含3个子元素:位图宽,位图高,数据数组。此处为了简化多维数组处理,数据用了一维数组,于是就定义了一个宏 IMAGE_POT,用于将二维的表示法转为一维的定义。image_create_function 用数据创建位图,如果只提供了大小,可以创建空白图片。 image_destory_function 销毁位图,主要作用是释放位图占用的内存。另有5个工具函数,作用分别是:水平翻转、垂直翻转、旋转180度、右转90度、左转90度。
3、字体(Font.h、Font.c):
存储每个字符的位图,用函数font_get_char_function从“字库”里检索。
实现动画和游戏逻辑,包括按键处理、评分、各阶段屏幕绘制。
screen_init_function(int b_flag_inversen);用于初始化屏幕,并设置屏幕是否为反色输出。就是这个反色输出功能,使我能用习惯的黑白来画图。如果需要,也可以在OLEd和LCD间方便地切换。
screen_set_dirty_flag_function(int b_flag) 和 screen_set_auto_refresh_flag_function(int b_flag) 用于设置是否发生更改和是否自动向设备层刷新。这两个函数是联合使用,我一般先关掉自动刷新,然后绘图的时候,绘图函数自动根据颜色来设置是否有更改。所有的画图都完成后,再打开自动刷新,这时就会根据之前的更改标记来决定是否调用OLED驱动函数进行刷新。
void screen_refresh_function();如名字所示,执行它会进行一次强制刷新。
void screen_clean_function();如名字所示,执行它会清理缓存。
另外的几个功能函数,分别是screen_draw_bitmap_function(画位图)、screen_draw_string_function(调用字库画字符串)、screen_draw_Int32_function(数字转字符串画出)、screen_draw_horizontal_line_function(画水平线)、screen_draw_vertical_line_function(画垂直线)、screen_draw_rectangular_function(用边框色和填充色画矩形)、screen_draw_dot_function(画一个点)。
增加一级缓存,用于记录上一次更新后的屏幕数据:
uint8_t g_oled_ram_cache[8][128]={0};
修改oled_reflesh(),增加输出前判断是否一致(由于硬件特性,连续修改不需要设置列的位置)。
7、抽象游戏结构体(Game.h)
在Game.c中仅是被注释起来的示例。game_event预定义了按键A、B、1、2、3、4按下,以及定时器触发和请求刷新显示事件。父过程通过结构体_GAME中的(*game_event_handle)(game_event event)向子过程传递事件,并获取最新的GAME_STAT。面向对象的角度看,每个游戏都是一个单例对象。启动一个游戏时,首先执行该游戏的init函数,获得指向该游戏的结构体指针game。之后有2种处理方法:用中断或是while(1):如果用中断,则(1)执行game->game_loop_handle直到退出;(2)在事件对应的处理函数中调用game->game_event_handle,将事件传递给game;(3)维护一个game*的栈,把当前的game压进去,并在game退出时,恢复最新的顶部game。如果使用while(1),则(1)while(1)要写在在main函数一级,也就是整个系统的最高层;(2)通过PIO、延时等方法判断是否触发事件,然后用game->game_event_handle,将事件传递给game,同时根据返回的GAME_STAT值,判断是否游戏已经退出。(3)由于这种情况下,需要父级game管理子级game,并在子级game退出时,接管输入输出。
8、主菜单(game_MainMenu.h、game_MainMenu.c):
用于显示菜单和启动游戏。因为现在只做了1个Flappy Bird,所以10个菜单挂的都是它。然后把菜单画出来,响应按钮事件,更换选择或执行选中游戏的操作。当选中时,将gameMenu状态设置成游戏中,同时初始化对应的game,这时所有的事件都应该传给当前的game。传事件之后顺便检查一下,要是已经退出了,那就把gameMenu的状态改回来,也就是接管控制。
初步统计,代码超过1600行。核心代码在发帖和博客中详细分析(见第七部分的链接)。工程文件压缩后约35MB,链接: https://pan.baidu.com/s/18FNGYZJgrcPjjpHOT-gd0A 提取码: uxyn 。
链接地址:javascript:;
本项目验证了RVB2601的资源和处理能力,在YoC的组件基础上,比较快速的搭建出了掌上游戏机平台。受时间约束,完成了主控和一个游戏,可以作为原型验证CH2601的运算速度和片上存储空间。
后期努力方向包括:
2、“【平头哥RVB2601创意应用开发】掌上游戏机”系列文章:
标题 |
电子工程世界-论坛-平头哥RISC-V RVB2601活动专区 |
平头哥芯片开放社区(OCC)博客 |
(0)开箱,吓一跳 |
|
|
(1)对眼儿画图记 |
||
(2)飞翔的小鸟 |
||
(3)一按就蹦 |
八、其他
感谢电子工程世界和平头哥芯片开放社区的支持!