用STM32做一个游戏机
游戏视频地址:https://www.bilibili.com/video/BV1i84y1t7ro
制作出来的实物图如下:
游戏运行图片如下:
单片机主函数如下:
- int main()
- {
-
- OLED_Init();
- Key_Init();
- InitAllVariable();
- OLED_ShowString(1,1,"Press any key to");
- OLED_ShowString(2,1,"start game");
- while(1)
- {
- Game();
- }
- }
复制代码
主函数中的 “Game()”用于开始游戏、暂停游戏以及游戏重新开始,内容如下:
- void Game(void)
- {
- i=KEY_ValueReturn();
- if(i)
- {
- i=0;
- OLED_Clear();
- while(1)
- {
- GameBody();
- i=KEY_ValueReturn();
- if(ReturnGameOverFlag()==1)//检测到游戏结束标志
- {
- OLED_Clear();
- while(1)
- {
- if(Counter_1_FullFlagReturn()){j=!j;}
- if(j)
- {
- OLED_Clear();
- }else
- {
- OLED_ShowString(1,1,"SCORE:");
- OLED_ShowNum(1,7,GameScore,6);
- OLED_ShowString(3,1,"Press key 3 to");
- OLED_ShowString(4,1,"restart");
- }
- i=KEY_ValueReturn();
- if(i==3){ OLED_Clear();Restart=1; break;}
- }
- }
- if(ReturnGameOverFlag()==0 && Restart==0)
- {
- if(i==3 )
- {
- OLED_Clear();
- while(1)
- {
- if(Counter_1_FullFlagReturn()){j=!j;}
- if(j){
- OLED_Clear();
- }else
- {
- OLED_ShowString(1,1," PAUSED ");
- }
- i=KEY_ValueReturn();
- if(i==3){OLED_Clear(); break;}
- }
- }
- }
- }
- }
- }
-
复制代码
游戏主体Gamebody用于实现整个游戏的运行,代码如下:
- #include "stm32f10x.h" // Device header
- #include "KEY.h"
- #include "OLED.h"
-
-
- int8_t First_Enemy[9]={16,16,16,16,16,16,16,16,16};//每队第一个敌人的坐标
- int8_t Bullet_X[15],Bullet_Y[15];//储存子弹坐标
- uint32_t GameScore=0,LastScore;
- int8_t Restart=0;//重新开始标志位
- typedef struct {
- uint8_t FireFlag;
- int8_t ShowBullet;
- uint8_t ShowEnemy;
- uint8_t GameOver;//
- uint8_t BulletSpeedUp;
- }FlagDef;
-
- typedef struct {
- int8_t X;
- int8_t Y;
- }PlayerDef;
-
- typedef struct {
- int8_t X;
- int8_t Y;
- }BulletDef;
-
- typedef struct {
- uint8_t X;
- uint8_t Y;
-
- }EnemyDef;
-
- PlayerDef My_loaction;
- FlagDef Flag;
- BulletDef Bullet_Location;
- BulletDef BulletNum;
- EnemyDef EnemyTarget;
- /****
- *@brief 初始化所有结构体变量
- *@parameter 无
- *@ReturnValue 无
- */
- void InitAllVariable(void)
- {
- My_loaction.X=4;//初始化玩家X坐标
- My_loaction.Y=1;//初始化玩家Y坐标
- Flag.FireFlag=0;//初始化开火标志位
- Flag.ShowBullet=0;//子弹显示
- Flag.GameOver=0;//游戏结束标志
- Flag.BulletSpeedUp=0;//子弹加速标志位
- Bullet_Location.X=My_loaction.X;//初始化子弹位置
- Bullet_Location.Y=My_loaction.Y;//初始化子弹位置
- EnemyTarget.X=8;//首位敌军的位置
- EnemyTarget.Y=16;//首位敌军的位置
- }
-
- /****
- *@brief 按键返回值处理
- *@parameter 无
- *@ReturnValue 无
- */
- void KeyNumReturn(void)
- {
- int8_t i,temp=0;
- temp=KEY_ValueReturn();
- if(temp){i=temp;}
- if(i==1){ My_loaction.X--; i=0; if(My_loaction.X<=1)My_loaction.X=1;}//1号键按下,玩//家上移一格
- if(i==2){ My_loaction.X++; i=0; if(My_loaction.X>=8)My_loaction.X=8; }//2号键按下,玩//家下移一格
- if(i==3) { i=0; }
- if(i==4) { Flag.FireFlag=1; i=0; } //四号键按下,开火标志位置一
- }
-
-
- /****
- *@brief 显示玩家
- *@parameter
- *@ReturnValue
- */
- void ShowPlayer(void)
- {
- uint8_t i;
- OLED_ShowBlock(My_loaction.X,My_loaction.Y);//根据坐标显示玩家
- for(i=1;i<=8;i++)//清除玩家移动后,上一次留下的影子(oled不会自动清屏,玩家移动后,上一次的方块//依然会显示)
- {
- if(i!=My_loaction.X)
- {
- OLED_DeleteBlock(i,My_loaction.Y);
- }
- }
-
- }
-
- /****
- *@brief 子弹处理
- *@parameter 无
- *@ReturnValue 无
- */
- void Bullet_process(void)
- {
- if(Flag.FireFlag==1)//开火标志位
- {
- if(Flag.ShowBullet==0)
- {
- Bullet_Location.X=My_loaction.X;
- Bullet_Location.Y=My_loaction.Y+1;
- Flag.ShowBullet=1;//子弹显示标志位
- }else if(Flag.ShowBullet==1)
- {
- Flag.BulletSpeedUp=1;
- }
- }
- Flag.FireFlag=0;
-
- LastScore=GameScore;
- if(Flag.ShowBullet)
- {
- if(Bullet_Location.Y >=1 && Bullet_Location.Y <17)
- {
- if(Counter_0_FullFlagReturn() || Flag.BulletSpeedUp==1)
- {
- Bullet_Location.Y++;
-
- Flag.BulletSpeedUp=0;
- }
- if(Bullet_Location.Y>2)//手动清除上一次子弹所在位置,OLED不会自动清屏。
- {
- OLED_DeleteBlock(Bullet_Location.X,Bullet_Location.Y-1);
- }
- }
- EnemyTarget.Y=First_Enemy[Bullet_Location.X];//
- if(Bullet_Location.Y<EnemyTarget.Y+1)//子弹没有碰到敌人或墙壁则根据坐标显示子弹
- {
- OLED_ShowBullet(Bullet_Location.X,Bullet_Location.Y);
- }else //子弹碰到敌人或墙壁则清除子弹
- {
- if(Bullet_Location.X<16){First_Enemy[Bullet_Location.X]++;}
- Flag.ShowBullet=0;
- GameScore++;//增加游戏分数
- }
- if(Bullet_Location.Y>=17)Flag.ShowBullet=0;
- }
-
- }
- /****
- *@brief 返回游戏结束标志位
- *@parameter 无
- *@ReturnValue 无
- */
- uint8_t ReturnGameOverFlag(void)
- {
- return Flag.GameOver;
- }
-
- /****
- *@brief 根据关卡显示敌人
- *@parameter 无
- *@ReturnValue 无
- */
- void Enemy_Process(void)
- {
- uint8_t i,j;
- if(Counter_EnemyMovingFlag()==1 )//时间到,让所有敌人往前走一步
- {
- for(i=1;i<=8;i++)
- {
- First_Enemy[i]--;
- }
- for(i=1;i<=8;i++)
- {
- for(j=First_Enemy[i];j<17;j++)
- {
- OLED_ShowAnemy(i,j);
- }
- }
- }else if(Counter_EnemyMovingFlag()==0)//时间没到,所有敌人原地不动
- {
- for(i=1;i<=8;i++)
- {
- for(j=First_Enemy[i];j<17;j++)
- {
- OLED_ShowAnemy(i,j);
- }
- }
- }
- for(i=1;i<=8;i++)//判断是否有敌人走到了玩家面前
- {
- if(First_Enemy[i]<=2)
- {
- Flag.GameOver=1;//将游戏结束位置一;
- for(j=1;j<=8;j++)
- {
- First_Enemy[j]=16;
-
- }
- }
- }
- }
-
- /****
- *@brief 根据分数控制蜂鸣器以及灯光
- *@parameter 无
- *@ReturnValue 无
- */
- void Score_Process(void)
- {
- if(GameScore>LastScore)
- {
- GPIO_ResetBits(GPIOC,GPIO_Pin_13);
- }else
- {
- GPIO_SetBits(GPIOC,GPIO_Pin_13);
- }
-
- if(Counter_0_FullFlagReturn())
- {
- GPIO_SetBits(GPIOC,GPIO_Pin_13);
- }
- }
-
- /****
- *@brief 游戏重新开始处理函数
- *@parameter 无
- *@ReturnValue 无
- */
- void GameRestart(void)
- {
- if(Restart==1 && Flag.GameOver==1)
- {
- OLED_Clear();
- GameScore=0;
- Flag.GameOver=0;
- Restart=0;
- }
-
- }
-
- /****
- *@brief //游戏主体,在主函数中循环调用该函数,运行当前游戏。
- *@parameter 无
- *@ReturnValue 无
- */
- void GameBody(void)
- {
- KeyNumReturn();
- ShowPlayer();
- Enemy_Process();
- GameRestart();
- Bullet_process();
- Score_Process();
- }
-
复制代码
按键处理Key.c用于处理按键,让按键按下后产生对应的动作,本次的按键处理在《单片机定时器中断扫描按键程序思路详解》(http://www.51hei.com/bbs/dpj-225674-1.html)内容上进行修改,修改后Key.c内容如下:
- #include "stm32f10x.h" // Device header
- #include "delay.h"
-
- uint8_t KEY_Num,KEY_Value;
- static uint8_t KEY_LastState,KEY_NowState;
- uint8_t Counter_0,Counter_0_FullFlag;
- uint8_t Counter_1,Counter_1_FullFlag;
- //uint8_t TEST_i;
- uint16_t Counter_2=0,Counter_EnemyMoving;
- /****
- *@brief 初始化按键
- *@parameter 无
- *@ReturnValue 无
- */
- void Key_Init(void)
- {
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);// 开启GPIOB时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);// 开启GPIOB时钟
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启TIM2时钟
-
- GPIO_InitTypeDef GPIO_InitStructure;//GPIO初始化
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOB,&GPIO_InitStructure);
-
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOC,&GPIO_InitStructure);
- GPIO_SetBits(GPIOC,GPIO_Pin_13);
-
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//TIM2定时器初始化
- TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//不对内部时钟分频
- TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
- TIM_TimeBaseInitStructure.TIM_Period=20-1;
- TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//设置时钟计数周期为72000000/7200=10000us=1
- TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//通用定时器没有重复计数器,设置为0
- TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
-
- TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//打开TIM2的更新中断
-
- TIM_InternalClockConfig(TIM2);//选择内部时钟源
-
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组
-
- NVIC_InitTypeDef NVIC_InitStructure;
- NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
- NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
- NVIC_Init(&NVIC_InitStructure);
-
- TIM_Cmd(TIM2,ENABLE);
- }
-
- //uint8_t Test_return(void)
- //{
- // return TEST_i;
- //}
- /****
- *@brief 判断哪个按键被按下
- *@parameter 无
- *@ReturnValue 无
- */
- uint8_t KEY_Numble(void)
- {
- uint8_t i=0;
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12)==0) { i=1; }
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13)==0) { i=2; }
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14)==0) { i=3; }
- if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)==0) { i=4; }
- // TEST_i=i;
- return i;
- }
- /****
- *@brief 返回按键的值
- *@parameter 无
- *@ReturnValue 根据对应的按键返回i(1~4)
- */
- uint8_t KEY_ValueReturn(void)
- {
- uint8_t i;
- i=KEY_Value;
- KEY_Value=0;
- return i;
- }
- /****
- *@brief 返回计数器Counter_0的状态
- *@parameter 无
- *@ReturnValue i:Counter_0计数满则返回1,否则返回0
- */
- uint8_t Counter_0_FullFlagReturn(void)
- {
- uint8_t i;
- i=Counter_0_FullFlag;
- Counter_0_FullFlag=0;
- return i;
- }
- /****
- *@brief 每间隔一定时间返回敌军前移标志位,控制敌军前移一格
- *@parameter无
- *@ReturnValue i:计数器满则返回1,否则返回0;
- */
- uint8_t Counter_EnemyMovingFlag(void)
- {
- uint8_t i;
- i=Counter_EnemyMoving;
- Counter_EnemyMoving=0;
- return i;
- }
- /****
- *@brief 返回计数器Counter_1的状态
- *@parameter 无
- *@ReturnValue i:Counter_1计满则为1,否则为0
- */
- uint8_t Counter_1_FullFlagReturn(void)
- {
- uint8_t i;
- i=Counter_1_FullFlag;
- Counter_1_FullFlag=0;
- return i;
- }
-
- /****
- *@brief //TIM2定时器中断函数
- *@parameter 无
- *@ReturnValue 无
- */
- void TIM2_IRQHandler(void)//TIM2定时器中断函数
- {
- KEY_LastState=KEY_NowState;//保存好按键上一次状态
- KEY_NowState=KEY_Numble();//刷新按键当前状态
-
- Counter_0++;
- Counter_2++;
- if(Counter_0==25){Counter_0=0;Counter_0_FullFlag=1;Counter_1++;}
- if(Counter_1==30){Counter_1=0;Counter_1_FullFlag=1;}
- if(Counter_2==2500){Counter_EnemyMoving=1;Counter_2=0;}
-
- if(KEY_LastState ==1 && KEY_NowState ==0)//根据前后两次状态来决定返回值
- {
- KEY_Value=1;
- }
- if(KEY_LastState ==2 && KEY_NowState ==0)
- {
- KEY_Value=2;
- }
- if(KEY_LastState ==3 && KEY_NowState ==0)
- {
- KEY_Value=3;
- }
- if(KEY_LastState ==4 && KEY_NowState ==0)
- {
- KEY_Value=4;
- }
- TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
- }
-
复制代码
Keil代码下载:
23-打砖块游戏.7z
(180.88 KB, 下载次数: 29)
|