1. 项目任务
- 利用LCD屏,实现简易的图片展示,展示个人信息
- 利用图片显示与按键功能实现简易的游戏设计,需要包含动画连贯展示
2. 项目方案设计
本实验主要为具体游戏设计,主要包含游戏架构设计、底层硬件设计、具体代码实现四部分,设计了本项目方案的架构图如图2.1所示。
3. 游戏架构设计
本项目参照Halfbrick Studios公司出品的手机游戏——水果忍者,尝试实现嵌入式系统中的游戏设计,游戏架构设计分为了游戏基本元素、游戏运行逻辑和整体代码逻辑三部分。
3.1 游戏基本元素
本游戏将游戏内部对象分为了切割物、游戏界面、游戏引擎三部分。
- 切割物:为游戏交互的主要角色,其中具体分为了水果与炸弹,切割水果可以得分,作为本游戏主要的奖励方式,而切割炸弹则会触发惩罚,切割物图像如图3.1所示,其结构体代码对象如下:
struct Fruit
{
char type; // 切割物种类
char flag; // 对象当前状态
int Cx; // 对象中心x位置
int Cy; // 对象中心y位置
unsigned int radius; // 对象圈半径大小
int Vx; // 对象x轴速度,水平速度不变
int Vy; // 对象y轴速度,受引擎影响
};
- 游戏界面:本游戏主要包含了开始界面、游戏运行界面、结束界面三个界面,其中开始界面用于选择游戏运行的规则模式,游戏运行界面则为游戏运行时的交互界面,结束界面则是在游戏结束后的统计界面,如图3.2所示。
- 游戏引擎:用于计算游戏运行对象当前时刻下的状态,如当前速度的计算,速度方向的改变。引擎规定了游戏内的加速度(随着得分改变)、游戏内的边界,游戏内的参考坐标系等参考信息,代码对象定义如下:
#define XHIGH 480 // x轴上边界
#define YHIGH 272 // y轴上边界
int GameAcc=2;
int randsend=0;
int countsend=0;
3.2 游戏运行逻辑
由于本游戏较为简单,整体逻辑清晰,结构简单,主要分为了界面切换逻辑、游戏交互逻辑、游戏对象运行规则三个部分,图3.3绘制了整体游戏运行逻辑。
3.2.1 界面切换逻辑
界面切换较为简单,本项目采用了状态机的实现思想,通过改变界面标志位的数值,实现游戏界面的切换,该逻辑较为简单,不作详细说明。
3.2.2 游戏交互逻辑
本游戏主要人机交互为触摸屏的使用、按键的使用,其中游戏运行主要记录笔尖划过的轨迹以实现游戏交互,具体来看游戏交互逻辑如图3.4所示。
- 笔尖落下响应TS中断,此时触摸屏触发了笔尖落下中断,转换到AD自动记录坐标模式,记录了当前的坐标为起点,并且触发等待笔尖抬起中断,并通过软件中断持续记录笔尖当前坐标。
- 持续记录笔尖当前位置作为终点,直到笔尖抬起。
- 计算起点与终点的位置并连成线段,并与当前存在的所有切割物的垂线进行比较相交,换句话说,本游戏中暂时不承认竖切水果,由于大部分切割操作均为横切,所以该方案对于游戏体验影响较小;且为每个切割物添加一条横线,即可提高准确度,但意义不大。
- 如果切割操作成功,则记录被切割物的编号,检索该编号切割物的种类,实现不同的奖惩机制。
以上便是游戏主体的交互逻辑,而按键控制主要控制本游戏的游戏进程,实现暂停、退出等操作。
3.2.3 游戏运行流程
该规则规定了游戏中切割物对象如何产生、对象状态如何更新、如何删除当前对象,具体逻辑如图3.5所示,其中规定每次主循环重新开始,为游戏进程开始。
while(1)
{
if(gameflag==0)
{
StartMenu();
gameflag=1;
}
while(gameflag==1)
{
flag=0;
if(runflag==1)
{
if(BoomCount<3)
{
if(Count_GameCutting()<3)
Create_GameCutting();
Display_GameCutting(); // 显示对象
imagedelay(2000);
INTMSK |= BIT_ADC; // 关闭ADC总中断
Delete_GameCutting();
INTMSK &= ~BIT_ADC; // 开启ADC总中断
imagedelay(1);
Update_GameCutting_Status(); // 更新对象状态
}
}
else
{
Display_Background();
imagedelay(1);
Update_Score();
Display_GameCutting();
while(flag==0 && gameflag==1)
{
imagedelay(100);
flag = runflag;
}
}
randsend++;
if(randsend>=9000)
randsend=0;
}
}
3.3 整体代码逻辑
基于上述的游戏架构设计,设计了具体的代码实现逻辑,如图3.7所示。
4. 底层硬件设计
对于寄存器的使用不作为本次实验的重点,本实验运用大部分硬件开发技术,均在前三次实验中有所涉及,底层硬件设计主要描述在较大代码存储需求情况下的内存空间分配,以及触摸屏中断的处理逻辑。
4.1 内存空间分配
与之前的实验相比,由于本游戏中运用到了大量的游戏图片素材,所以整体的存储空间需求较大,为此需要合理地分配内存空间以保证代码的正常运行。
经过简单的统计,本游戏运用到的图片游戏素材共占3M,代码大约占1M左右,整体共占4M内存空间大小,但为了确保后续游戏的扩展性,本项目在SRAM中划分了16M的虚拟内存空间分配,而规定了中断栈指针的地址为0x32000000,规定系统模式栈指针地址为0x34000000,这完全可以保证32M大小的游戏设计,符合本项目的全部需求。
4.2 数模转换器与触摸屏接口
由于AD数模转换在此前的实验中未接触,为此简单讲解触摸屏的简单原理,根据游戏交互逻辑,硬件设计需要明晰如何记录笔尖位置。
- AD数模转换器:即将模拟电压信号转换为数字电平信号,从而CPU处理相应信号。
- 触摸屏:实际为电阻屏,通过触摸屏幕中某些位置,实现电路中电阻阻值改变,从而输出不同的模拟电压信号。
但由于在S3C2440中触摸屏与AD均为同一设备下的两种模式,运用触摸屏需要运用同一中断下的子中断切换,绘制了图4.1整体的中断实现流程以明晰硬件设计。
5. 具体代码实现
本项目的代码原理并不困难,也没有过多的寄存器原理知识,更多只是游戏思路的设计以及一些代码调试过程的实现,所以主要内容为具体代码的讲解,而主要调试主要是,图片显示与动画实现,触摸屏交互实现,随机数的产生。
5.1 图片显示与动画实现
5.1.1 区域图片显示
在单片机基础课程中,LED点阵的显示、数码管的显示均是点亮对应位置的LED灯,实现显示不同的数字或字符。同样地,将LCD的每个像素点假设为一个LED灯,但是不同的是该LED灯可以通过32位的字符进行控制灯的颜色与亮度,从而实现图片显示效果,所以图片显示原理即通过给予对应像素位点的控制寄存器32位颜色,从而实现彩色显示。
根据计算机图形学的相关理论,将任意颜色可以拆分为红(red),绿(green),蓝(blue)三种颜色的组合,从而实现颜色的编码,除此之外为了实现3D效果显示,对应原色中均添加了透明度的设置,从而实现透视效果,所以整体来看图片像素位点寄存器中的32位被设置为了0xAARRGGBB的形式,高十六位为透明度,低十六位为RGB编码。
清楚图片显示原理后,显示区域内的图片显示,只需要明确图片大小长宽即可,再利用putpixel函数即可,以背景图片显示为例,如图5.1所示
void Display_Background(void)
{
volatile unsigned int a = 0x00;
volatile unsigned int b = 0x00;
volatile unsigned int c = 0x0000;
unsigned int x = 0;
unsigned int y = 0;
for(y=0;y<YHIGH;y++)
for(x=0;x<XHIGH;x++)
{
a = gImage_Background[(y*XHIGH+x)*2];
b = gImage_Background[(y*XHIGH+x)*2+1];
c = (a<<8) | b;
// a = 0xbb;
// b = 0xbb;
PutPixel(x, y, c);
}
}
5.1.2 不规则图片显示
在动画制作的过程中,小区域的动画显示可以大幅减少清屏时间,提高显示效率,如何显示不规则的图片。
首先,对于三角形、矩形等规则图片,可以规定其边长、角度从而计算出各点的颜色,但是由于无法使用C语言标准库,如何获得近似的数学函数则是需要解决的。
- 绘制圆形:绘制圆形需要利用平方根函数,在计算机近似计算平方根函数时,采用了牛顿迭代法(凸函数求解局部最优解的方法),具体代码如下:
// 画圆函数
void Circle(unsigned int x, unsigned int y, int r)
{
int i, j;
unsigned int a=x, b=y;
unsigned int d=1;
for(j=0;j<2*r;j++)
{
b = y - r + j;
d = mysqrt((r*r-(r-j)*(r-j)));
for(i=0;i<2*d;i++)
{
a = x- d + i;
PutPixel(a, b, 0x000f);
}
}
}
- 不规则形状:例如切割物对象中的草莓图5.2所示,其形状完全无规则,则需要框出草莓所在区域部分矩形,将对象的背景设为与对象颜色对比度较强的颜色,如白色,在显示该区域图像时,默认白色不显示即可,具体代码如图5.3所示。
else if(type==1) // 草莓
{
for(j=0;j<50;j++)
{
for(i=0;i<47;i++)
{
a = gImage_strawberry[(j*47+i)*2];
b = gImage_strawberry[(j*47+i)*2+1];
c = (a<<8) | b;
if(b <= 0xfa && a<=0xf6 && (x-24+i>=0 && x-24+i<480))
PutPixel(x-24+i, y-25+j, c);
}
}
}
5.1.3 动画显示
动画显示原理则和走马灯原理类似,利用视觉暂留效果,将图片显示一段时间后清屏,再显示状态改变后的图片即可。
5.2 触摸屏交互实现
触摸屏交互在游戏交互逻辑中思路已经详细说明,具体实现过程中如何实现软件中断与如何判断两线段相交,是调试中的主要问题。
5.2.1 软件中断实现
软件中断实现的思路较为简单,只需要在笔尖落下中断函数结束时,重新对于ADC中断源寄存器赋值,实现软件中断即可,但是如何确定当前笔尖状态,以及同时需要等待笔尖抬起中断。
所以解决该问题,即在笔尖落下后,ADC转换完第一次坐标数据后,延时大概1ms在该时间内等待笔尖抬起中断;若笔尖尚未抬起,则进入AD数据转换模式。时序逻辑如图5.4.
static void Isr_Adc(void)
{
unsigned int y = ADCDAT0;
unsigned int x = ADCDAT1;
int cutflag=-1;
y = TcYData(y);
x = TcXData(x);
if((x > 10 && x < 470) || (y > 10 && y < 260))
{
// printf("ydata = %4d, xdata = %4d\r\n", y, x);
TcxyData[TcTail][0] = x;
TcxyData[TcTail][1] = y;
cutflag = JudgeCutting(TcxyData[Tchead][0], TcxyData[Tchead][1], TcxyData[TcTail][0], TcxyData[TcTail][1]);
TcTail++;
}
wait_up_int(); /* 进入"等待中断模式",等待触摸屏被松开 */
ADCDAT1 |= 0x8000;
// 清INT_ADC中断
SUBSRCPND |= BIT_SUB_ADC;
SRCPND |= BIT_ADC;
INTPND |= BIT_ADC;
if((ADCDAT1 & 0x8000) == 0x0000)
{
wait(10000);
mode_auto_xy(); /* 进入自动(连续) X/Y轴坐标转换模式 */
ADCCON |= ADC_START;
}
}
5.2.2 线段相交
如何判断两个线段相交,根据线性代数的理论知识,判断两直线相交,即直线不平行,只需进行两直线叉乘,即可判断直线相交,但是我们需要解决线段相交问题,需要通过快速排斥实验和跨立实验。
快速排斥实验,假设两线段均在同一直角坐标系,线段l_1的最大x坐标,与线段l_2的最小x坐标相比,如果投影不存在相交部分,那么线段则一定不相交。
跨立实验,先看一个老马饮水问题,如果点A和点B构成的线段与x轴相交,那么点A和点B的y轴坐标相乘一定为负的,同理与y轴相交,则意味着x轴坐标相乘一定为负。所以,假设线段l_1为参考线段,则线段l_2的A点和B点分别与l_1进行叉乘,后相乘,如果为负,则意味着点A和点B在参考线段的两边。
具体代码实现如下:
// 叉乘
int cross(int x1, int y1, int x2, int y2)
{
return x1*y2-x2*y1;
}
// 判断线段相交
char JudgeSect(int x1, int y1, int x2, int y2, int x, int y, int l)
{
if((x1<x&&x2<x) || (x1>x&&x2>x))
return 0;
if(cross(x-x1, y-y1, x2-x1, y2-y1)*cross(x-x1, y+l-y1, x2-x1, y2-y1) < 0)
return 1;
else
return 0;
}
5.3 随机数的产生
游戏过程中,水果的产生需要随机产生,同时需要更具当前得分而有所改变,为此需要得到合适的随机数才能得到合适的游戏效果。
一个简单的方法是根据当前时钟时间,得到一个伪随机数,所以利用RTC时钟得到一个秒时间的伪随机数,但是这存在随机数的周期太短,每隔60秒随机数即重复产生;随机数时间改变太慢,需要每隔1秒钟,随机数才会改变,RTC实现代码如下:
void RTC_Init(void)
{
RTCCON |= (0x1);
RTCALM |= (0x1<<0 | 0x1<<1 | 0x1<<6);
}
// 读RTC实时时钟秒
int ReadSecRTC(void)
{
volatile unsigned char RTCdata = BCDSEC;
unsigned char data1 = ((RTCdata & 0x70)>>4);
unsigned char data2 = RTCdata & 0x0f;
return data1*10+data2;
}
int ReadMinRTC(void)
{
volatile unsigned char RTCdata = BCDMIN;
unsigned char data1 = ((RTCdata & 0x70)>>4);
unsigned char data2 = RTCdata & 0x0f;
return data1*10+data2;
}
// 随机数函数,从0-9
int rand(void)
{
return ReadSecRTC()%10;
}
为改进上述问题,引入了一个时间进程随机数种子,每隔一个主循环改变,该种子便实现自加1,主循环大约10ms一次,则就意味着随机数会10ms改变一次,这样大致满足了当前游戏的随机数产生的需求。
但是在实际的概率事件中,相同事件发生往往会出现聚集性,而人们在实际生活中往往认为随机数会均匀分布,如当硬币重复投出10次正面后,人们更愿意相信下次硬币为反面。所以,游戏中设计的随机数,应尽量避免数字重复产生的情况,为此在产生随机数函数中添加了一个随机数计数种子,保证每次进入随机数函数,该计数种子均会加1,以保证即使在相同时间内进入随机数函数,亦会产生不同的随机数,最终随机产生代码:
// 双时间叠加随机函数 0-100
int Readsend(void)
{
countsend = countsend+(randsend%10)*rand()+1+CreateCutting;
return (countsend%100+randsend%100+rand()*11)/3;
}
以上是本游戏开发中遇到的主要问题以及解决方法,附上本次项目的全部文件如图5.6所示。
本次实验,整体较为满意,符合设计游戏的最初目标,同时深刻意识到了硬件是软件的基础与上线。嵌入式系统开发从始至终,都需要保证硬件与算法的相统一,才能开发出好的嵌入式系统产品。
游戏的最终结果利用视频呈现,实验游戏最终效果如文章来源:https://www.toymoban.com/news/detail-742178.html
嵌入式游戏设计——水果忍者文章来源地址https://www.toymoban.com/news/detail-742178.html
到了这里,关于ARM裸机开发——简易嵌入式游戏开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!