话不多说,先上测试视频:
这一章节用的硬件是PWM移相测试卡+OLED12864 ( SSD1306_IIC)
序言、
最近刷抖音看到了个贪吃蛇,这不一下就激起了童年的回忆,准备小试牛刀做个贪吃蛇耍耍。既然也做出来了个初版,那就给广大网友一起耍耍了,想自己借鉴或者升级的也可以仔细看下文章,想玩的直接群文件下载代码即可。这个帖子的代码用的最最好理解的方法写的,适合新手练习,老鸟略过哈哈。
一、知识储备
1.去下面这个帖子学习IIC的知识:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=236
2.去下面这个帖子学习OLED显示汉字的知识:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=30
3.去下面的帖子学习单片机驱动G驱动OLED12864,I2C方式:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=2592
4.去下面的帖子学习菜单:https://www.stcaimcu.com/forum.php?mod=viewthread&tid=44
二、效果分析
按键,oled初始化什么的比较简单就略过了,有需要的可以去看上面的帖子。这里主要讲绘图和蛇的动作。
1.界面功能切换的结构体
typedef struct Menu
{
uint8_t Sub;
//进入下一级
uint8_t Enter;
//返回上一级
uint8_t Back;
// 对应的函数指针
void (*Current_Operation)(void);
} Menu_Table;
程序里基于这个结构体定义了一个结构体数组,
Menu_Table Table[3]= { /* 菜单及界面 */
/*SUB E B F */
{2, 1, 0, Menu},
{0, 1, 0, Game},
{0, 0, 0, GameMode}
};
这个结构体数组讲用到的三个界面都做进去了,分别是主菜单界面(Menu),游戏界面(Game),游戏模式设置界面(GameMode)。
2.界面和功能切换的函数指针
在代码里定义了一个 void (*Operation)(void); 的函数指针,Operation指向要执行的函数。Operation=Table[Cur].Current_Operation指向当前界面的函数,然后再主循环里不断的循环执行这个函数。这部分不懂得可以去参考上面的菜单的帖子。
3.主菜单界面(Menu)介绍
(1)如果切换过界面,先清除当前界面的显示
(2)初始化所有的运行变量
(3)显示汉字
(4)判断当前的模式并在指定位置显示 *
具体的实现代码如下:
- void Menu(void)
- {
- if(modeFlag)
- {
- OLED_Clear();
- }
- /* 不在游戏中 */
- playing = 0;
- modeFlag = 0;
- replay = 1;
- Get_Command();
- /* 关闭定时器 */
- TR0 = 0;
- /* 显示贪吃蛇 */
- OLED_ShowCHinese(44,0,0);
- OLED_ShowCHinese(60,0,1);
- OLED_ShowCHinese(76,0,2);
- /* 开始游戏 */
- OLED_ShowCHinese(34,3,3);
- OLED_ShowCHinese(50,3,4);
- OLED_ShowCHinese(66,3,5);
- OLED_ShowCHinese(82,3,6);
- /* 难度选择 */
- OLED_ShowCHinese(34,5,7);
- OLED_ShowCHinese(50,5,8);
- OLED_ShowCHinese(66,5,9);
- OLED_ShowCHinese(82,5,10);
-
- /* 菜单选择 */
- switch(menuSelect)
- {
- case MAINMENU:
- OLED_ShowChar(14,3,'*',16);
- OLED_ShowChar(14,5,' ',16);
- break;
- case GAMEMODE:
- OLED_ShowChar(14,5,'*',16);
- OLED_ShowChar(14,3,' ',16);
- break;
- }
-
-
- }
复制代码
显示效果图如下图所示:
4.游戏模式设置界面(GameMode)
(1)如果切换过界面,先清除当前界面的显示
(2)初始化所有的运行变量
(3)显示汉字
(4)判断当前的模式并在指定位置显示 * ,表示当前选中
具体的实现代码如下:
- void GameMode(void)
- {
- if(!modeFlag)
- {
- OLED_Clear();
- }
- Get_Command();
- playing = 0;
- modeFlag = 1;
- OLED_ShowCHinese(34,1,15);
- OLED_ShowCHinese(50,1,16);
- OLED_ShowCHinese(34,4,17);
- OLED_ShowCHinese(50,4,18);
-
- switch(CuMode)
- {
- case 0:
- OLED_ShowChar(14,1,'*',16);
- OLED_ShowChar(14,4,' ',16);
- break;
- case 1:
- OLED_ShowChar(14,1,' ',16);
- OLED_ShowChar(14,4,'*',16);
- break;
- }
- }
复制代码
显示效果图如下图所示:
5.游戏界面(Game)
(1)是否重新开界面,先清除当前界面的显示和之前的游戏记录
(2)定时时间到了蛇移动,如果移动后吃到了食物在重新生成,画新的蛇
(3)如果游戏结束了展示成绩
具体的实现代码如下:
- void Game(void)
- {
-
- /* 重开游戏 */
- if(replay)
- {
- /* 贪吃蛇初始化 */
- clearSnake();
- GUI_Init();
- replay = 0;
- }
- /* 正在游戏中 */
- playing = 1;
- /* 开启定时器 */
- TR0 = 1;
- if(timerFlag)
- {
- Move();
- Eat_Food();
- drawSnake();
- timerFlag = 0;
- }
- //定时器0开始计时
- Get_Command();
- GUI_Refresh(map);
- if(GameOver())
- {
- OLED_Clear();
- /* 展示成绩 */
- Show_Score();
- Delay2000ms();
- OLED_Clear();
- Cur = Table[Cur].Back;
-
- }
- }
复制代码
显示效果图如下图所示:
三、蛇体部分分析
1.如何实现游戏界面的显示: 本项目使用的OLED屏是0.96寸的,像素为128*64。贪吃蛇少不了蛇,如果采用一个像素点来画蛇的身体,那这个蛇小到离谱,不够明显。 所以本项目使用的4*4像素的正方形(只点亮外框像素)来绘制蛇的身体。实现的思路大致如下: 使用map [32][16]放大四倍变为128*64。 map [32][16] : 对应的是上图中的左边。用于存储数据,然后调用GUI_Refresh判断相应的值,做出对应的操作,然后放大四倍成为上图中的右边,最后再调用OLED里面的相关代码,就会把数据写入OLED_GRAM[128][8]。 例如: - void Paint_Body(uint8_t x,uint8_t y )//绘制身体
- {
- uint8_t i,j;
- for(i=4*y; i<4*y+4; i++)
- {
- for(j=4*x; j<4*x+4; j++)
- {
- if(i==4*y||i==4*y+3)
- {
-
- OLED_DrawPoint(j,i);
-
- }
- if(j==4*x||j==4*x+3)
- {
-
- OLED_DrawPoint(j,i);
-
- }
- }
- }
- }
- void Paint
复制代码
这里OLED_GRAM[128][8]:作为OLED的显存数组,通过这个数据就可以让OLED上对应的像素亮起来。• snake_Grid[32][2]: 用来存储蛇的坐标,后面会有讲解。其他的代码也可以这样实现,比如画外框,食物等。之后就只需要操作map数组,就可以完成我们的贪吃蛇啦。
2.游戏规则设计 上面有提到snake_Grid[32][2]是用于存储蛇的坐标的,同时snake_Grid[0][0]和snake_Grid[0][1]是来存储蛇头的坐标,我们只需要判断蛇的其他坐标是不是等于蛇头的坐标,相等说明吃到自己了,结束游戏;另一个规则是撞到墙游戏结束,只需要判断蛇头的坐标是不是等于0xfe(0xfe对应的是边框值)。 - // 撞到墙结束游戏
- if(map[snake_Grid[0][0]][snake_Grid[0][1]] == 0xfe && GameDifficulty==1)
- {
- // crashWall
- crashWall = 1;
- }
复制代码
- bit GameOver(void)
- {
- //蛇头坐标
- uint8_t sx=snake_Grid[0][0],sy=snake_Grid[0][1],i;
- //判断有没有吃到自己
- for(i=1; i<length; i++)
- {
- if(snake_Grid[i][0]==sx && snake_Grid[i][1]==sy)
- return 1;
- }
- //如果撞到墙
- if(crashWall==1 && GameDifficulty==1)
- {
- crashWall = 0;
- return 1;
- }
- return 0;
- }
复制代码
具体的代码都在上面讲解了,有需要源码的小伙伴可以在群文件下载,有任何问题可以在下面留言!
|