以下文章来源于嵌入式小书虫 ,作者FledgingSu 支离苏
上一篇我们用Arm-2D简简单单制作了一款Flappy Bird小游戏,背景弄了一个渐变的大满月,今天我们在给他改造一下,让背景带有景深的效果,视频演示如下:
(注:图中硬件不含音频发声电路,背景音乐为视频配音,像素资源来自于2021年原神拜年祭视频,版权归原作者所有)
怎么样,效果还可以吧,这个可是用一个M0内核的pico单片机实现的效果哦。
【景深原理篇】
我们借助Arm-2D可以很容易地加入多层背景,造出景深。每层背景都是独立的层次,且这个层次独立滚动(即滚动速度不同),越远滚动越慢,就会产生景深感。
从视频中不难发现,我们的背景一共4层,最上面是蓝天白云,第2层为树木草坪,第3层为小花、小草和石头,最下面一层为草地。 他们左移的速度从上到下依次加快(第3层和第4层的速度一样),这样就看到了 视频中的景深感。
好,下面我们讲一下怎么实现一个独立的滚动层。
如下图所示第2层树木草坪的滚动:
制作一个循环的素材(宽度可以大于屏幕宽度),然后调用Arm-2D提供的图片填充功能就可以了,就是之前进度条的填充功能,忘记的同学可以看下面这篇文章(提示:坐标可以是负数)
这个素材是连续出现的,那上面的云是一朵一朵出来的,两朵云之间还有间隙,这个是怎么实现的呢?
这个其实也简单,原理如下图:
- 当云朵移出屏幕后,可以给(x,y)重新赋值,可以是随机值,但要确保x的值大于240,这样云朵就可以循环滚动了。
好了,到这里我们就可以制作连续的树木背景和随机出现云朵的背景了,背景下面的花、草、石头也是和云朵一样随机出现的,最下面的草地是连续出现的。接下来讲一下程序是怎么实现的。
【景深程序篇】
首先讲一下我封装的景深数据结构,如下:
typedef struct {
- ptTiles就是指向素材Tile的数组
- ptLocation就是素材在屏幕中显示的位置坐标
- offect_iX就是素材向左移动的距离
- speed_iX就是向左移动的速度
- Align_Bottom_iY为底部对齐,如下图所示:
- 由于素材Tile的iY是随机的,默认是不对齐的,所以需要底部对齐就要设置Align_Bottom_iY的值,同时bIsAlign_Bottom置为1。
- layer_level为第几层
- tiles_nums为素材Tile数组的个数
- for_num为显示Tile的个数,比如素材Tile(花、草、石头)有4个(即tiles_nums=4),但我们需要随机显示8个,即for_num=8
- ptRandom_tile_nums为随机显示资源Tile的下标数组。
- ptTiles_iX_offect为资源Tile坐标iX方向的初始偏移量数组。
- ptTiles_iY_offect为资源Tile坐标iY方向的初始偏移量数组。
- bIsAlign_Bottom这个就是是否底部对齐。
- FillRegion为循环的素材填充的区域,如下图所示:
- bIsFill_Tile为是否是用一个周期图片填充,(即bIsFill_Tile为1,填充区域FillRegion有效)。
接下来我们就定义一个layer_t的数组,初始化云朵为例,如下
layer_t layers[4]={
- 注意tiles_nums 和for_num 可以不相等,(即有3朵云资源Tile,但是每次显示2朵则for_num =2,tiles_nums =3,此时需要设置ptRandom_tile_nums指向的数组个数也为2)。
数组ptRandom_tile_nums、ptTiles_iY_offect和ptTiles_iX_offect的关系如下图所示:
资源Tile云朵的数组初始化(3朵云资源):
const arm_2d_tile_t c_tile33[3] = {
其他数组初始化如下:
static arm_2d_location_t layer_3_location[3]={{0,30},{98,50},{156+98,70}};
有了layers[4]这个数组,景深效果的显示函数就简单了,如下:
void paly_game_gui_refresh(const arm_2d_tile_t *ptTile, bool bIsNewFrame){
- 这个函数就实现了上面视频中的景深效果,是不是很简单。
注意:
这里有一个问题不知道大家发现没?就是我们的位移ptItem->offect_iX一直在增加,而他是int16_t类型(ptItem->offect_iX += ptItem->speed_iX),这样一直加下去肯定会超出范围,为了解决这个问题,我们在31~34行和64~67行对ptItem->offect_iX进行了清零。这个原理也简单,因为我们在计算tBox.tLocation.iX 用的是相对坐标(即ptItem->ptLocation[i].iX - ptItem->offect_iX),所以只要同时减去ptItem->offect_iX,他们的差值不会变,即在屏幕中显示的位置不变。
【再谈碰撞检测】
上一篇我们自己简单实现了碰撞检测的函数,这次我们讲一下Arm-2D自带的函数进行碰撞检测,函数原型如下:
bool arm_2d_is_point_inside_region(
- 这个函数直接就可以判断一个点是不是在这个区域中,也很简单。不过,这次我们要使用一个更高级的碰撞检测,
哈哈哈,其实就是检测一个区域,只有这个区域超过一定的面积才算碰到障碍物,原理如下图:
用到的函数原型如下:
bool arm_2d_region_intersect(
- 返回值为true,则有相交区域,第3个参数ptRegionOut指向这个相交区域,相交区域的面积就是ptRegionOut->tSize.iWidth与ptRegionOut->tSize.iHeight的乘积。
修改后的碰撞检测函数如下:
uint8_t ys_hit_against_inspect(){
还是很简单的。
【说在后面的话】
我们的原神小游戏虽然可以玩了,但是障碍物的运动还比较机械,人物的运动也不是很自然,所以还有很大的改进空间。下一篇我们就针对这个问题,封装成运动轨迹函数,使我们的小游戏更好玩。也期待大家能实现自己的运动轨迹函数,大家一起玩才更好玩。
下一篇精彩继续(* ̄︶ ̄)。。。。。。
原文:裸机思维
作者:FledgingSu 支离苏
专栏推荐文章
如果你喜欢我的思维,欢迎订阅裸机思维