1.硬件信息2.界面设计3.程序流程
3.1 游戏初始化3.2 生成食物坐标3.3 获取前进方向3.4 蛇的坐标更新3.5 碰撞检测3.6 输出界面3.7 主函数 4 结果展示
1.硬件信息本文使用的是STC15W408AS型号的51单片机,自己做的一个带按键的小系统,显示屏使用的是某宝上买的1.54英寸的墨水屏,一共有200 * 200个像素点。所以整个游戏界面的坐标按照200 * 200进行设计。
2.界面设计既然是简洁版的贪吃蛇,那么显示界面就一切从简了,如下图所示。只要搞懂了基本的设计思路在此基础上还是可以搞复杂的。(0_0)
首先是游戏场地,在四周是宽度为8个像素的墙壁,如果蛇头碰撞到墙壁那么游戏结束,也就是说蛇头的坐标超出规定范围就游戏结束。
为方便计算,在原本200 * 200像素的基础上,我把8 * 8作为一个游戏像素,也就是映射为25 * 25个像素。把8 * 8像素的左上角作为坐标的话,蛇的活动范围就在(1,1)到(23,23)。
贪吃蛇游戏涉及到蛇的移动、食物生成、碰撞检测、方向控制和界面输出。所以整个程序的设计可以按照以下步骤:
1.游戏初始化
2.生成食物坐标
3.获取前进方向
4.蛇的坐标更新
5.碰撞检测(墙壁,蛇身和实物)
6.输出界面
大致的程序流程:
游戏一开始我设计的是蛇的长度为3,初始方向向右。由于51单片机不好实现链表,这里直接用数值存储蛇的每个游戏像素坐标。本文设计最大长度为20,超过20游戏结束。以下是相关的变量初始化。
u8 snake_len=3; //蛇的长度
u8 snake_xy[20][2]={{5,3},{4,3},{3,3}}; //初始蛇坐标
u8 sanke_tempxy[2]={5,3}; //保存尾坐标
char direction_xy[2]={1,0}; //前进方向
u8 food_xy[2]={4,5}; //食物坐标
u8 gemeover=0; //0位继续 1死亡结束 2通过结束
bit eat=0; //是否吃到食物
其中前进方向用在后面的蛇头坐标计算中。
3.2 生成食物坐标使用rand()函数生成1到23的随机数作为食物的生成坐标,且生成的坐标不能和蛇身重合。
需要包含头文件 #include
void Food_GetXY(void)
{
u8 ture=0; //生成的值不能和蛇身坐标重合
u8 i=0;
if(eat)
{
while(!ture)
{
ture = 1;
food_xy[0] = (rand()%22)+1; //1-23
for(i=0;i
3.3 获取前进方向
在主函数中一直循环获取按键键值,更新前进方向:
key_get = Key_Scan2();
if(key_get)
{
switch(key_get){
case 1:{
direction_xy[0] = 0;
direction_xy[1] = 1;
break;}
case 2:{
direction_xy[0] = 1;
direction_xy[1] = 0;
break;}
case 4:{
direction_xy[0] = -1;
direction_xy[1] = 0;
break;}
case 8:{
direction_xy[0] = 0;
direction_xy[1] = -1;
break;}
}
key_get = 0;
}
其中的按键获取函数是无去抖等待的,需要的可以参考:
STM32定时器实现不加延时的三种独立按键获取函数——返回1次,多次和长短按键
3.4 蛇的坐标更新
先保存下蛇尾的坐标,后面碰撞检测的时候如果吃到食物需要用于增加蛇身长度。
然后,从后往前更新蛇身坐标。
最后,按照前进方向更新蛇头坐标。
void Snake_UpdateXY(void)
{
u8 i=0;
sanke_tempxy[0] = snake_xy[snake_len-1][0]; //保存尾坐标
sanke_tempxy[1] = snake_xy[snake_len-1][1];
for(i=snake_len;i>0;i--) //从后往前更新蛇身坐标
{
snake_xy[i][0] = snake_xy[i-1][0];
snake_xy[i][1] = snake_xy[i-1][1];
}
snake_xy[0][0] += direction_xy[0]; //按照前进方向更新蛇头坐标
snake_xy[0][1] += direction_xy[1];
}
3.5 碰撞检测
超出游戏界面范围的话游戏结束,蛇身超过20游戏通过。
void Snake_Crash(void) //碰撞判断
{
u8 i=0;
if((snake_xy[0][0]>23 || snake_xy[0][0]<1)||(snake_xy[0][1]>23 || snake_xy[0][1]<1)) //蛇头碰到边框
gemeover = 1;
// for(i=1;i
3.6 输出界面
通过游戏坐标,这边调用的是墨水屏的显示函数,游戏用在其他地方的话可以忽略这节。
void Show_Updaet(void)
{
u8 i=0;
EPD_Dis_Part(0,0,gImage_main_1,200,8,OFF); //边框
EPD_Dis_Part(192,8,gImage_main_1,8,192,OFF);
EPD_Dis_Part(0,8,gImage_main_1,8,192,OFF);
EPD_Dis_Part(8,192,gImage_main_1,184,8,OFF);
if(gemeover==1)
{
EPD_Dis_Part(110,72,gImage_num[4],35,56,NEG); //失败界面
EPD_Dis_Part(150,72,gImage_num[4],35,56,NEG);
}
else if(gemeover==2)
{
EPD_Dis_Part(110,72,gImage_num[6],35,56,NEG); //通关界面
EPD_Dis_Part(150,72,gImage_num[6],35,56,NEG);
}
else
{
for(i=0;i
3.7 主函数
int main()
{
// uchar i;
u8 key_get=0;
system_init();
while(1)
{
if(gemeover) //游戏结束
{
return 0;
}
key_get = Key_Scan2(); //获取按键
if(key_get)
{
switch(key_get){ //获取前进方向
case 1:{
direction_xy[0] = 0;
direction_xy[1] = 1;
break;}
case 2:{
direction_xy[0] = 1;
direction_xy[1] = 0;
break;}
case 4:{
direction_xy[0] = -1;
direction_xy[1] = 0;
break;}
case 8:{
direction_xy[0] = 0;
direction_xy[1] = -1;
break;}
}
key_get = 0;
}
if(system_ctr&0x01 && !isEPD_W21_BUSY) //每隔500ms进一次进行游戏更新
{
Show_clear_snake(); //清屏
Food_GetXY(); //生成食物坐标
Snake_UpdateXY();//蛇的坐标更新
Snake_Crash(); //碰撞检测
Show_Updaet(); //输出界面
system_ctr &= ~0x01;
}
}
return 0;
}
4 结果展示
最后来看下效果吧,这个墨水屏更新显示太慢了,导致游戏不能快点刷新,体验极差(0-0),显示的动图这是快了4倍的速度。本文如果对你有帮助的话就点个赞吧。



