标题:
STM32单片机打砖块游戏机制作 附源程序
[打印本页]
作者:
小白EE
时间:
2022-12-14 10:44
标题:
STM32单片机打砖块游戏机制作 附源程序
用STM32做一个游戏机
游戏视频地址:
https://www.bilibili.com/video/BV1i84y1t7ro
制作出来的实物图如下:
游戏运行图片如下:
51hei.jpg
(293.73 KB, 下载次数: 42)
下载附件
2022-12-14 16:26 上传
单片机主函数如下:
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)
2022-12-14 09:06 上传
点击文件名下载附件
下载积分: 黑币 -5
作者:
异性朋友
时间:
2023-6-5 14:31
为什么不能左右移动
欢迎光临 (http://www.51hei.com/bbs/)
Powered by Discuz! X3.1