傻孩子(GorgonMeducer) · 2022年03月21日

【玩转Arm-2D】如何制作有景深效果的横版过关游戏

以下文章来源于嵌入式小书虫 ,作者FledgingSu 支离苏

上一篇我们用Arm-2D简简单单制作了一款Flappy Bird小游戏,背景弄了一个渐变的大满月,今天我们在给他改造一下,让背景带有景深的效果,视频演示如下:

image.png

(注:图中硬件不含音频发声电路,背景音乐为视频配音,像素资源来自于2021年原神拜年祭视频,版权归原作者所有)

怎么样,效果还可以吧,这个可是用一个M0内核的pico单片机实现的效果哦。

image.png

【景深原理篇】


我们借助Arm-2D可以很容易地加入多层背景,造出景深。每层背景都是独立的层次,且这个层次独立滚动(即滚动速度不同),越远滚动越慢,就会产生景深感。

从视频中不难发现,我们的背景一共4层,最上面是蓝天白云,第2层为树木草坪,第3层为小花、小草和石头,最下面一层为草地。 他们左移的速度从上到下依次加快(第3层和第4层的速度一样),这样就看到了 视频中的景深感。

好,下面我们讲一下怎么实现一个独立的滚动层。

如下图所示第2层树木草坪的滚动:

image.png

  • 制作一个循环的素材(宽度可以大于屏幕宽度),然后调用Arm-2D提供的图片填充功能就可以了,就是之前进度条的填充功能,忘记的同学可以看下面这篇文章(提示:坐标可以是负数

    【玩转Arm-2D】二、制作一个酷炫的进度条

这个素材是连续出现的,那上面的云是一朵一朵出来的,两朵云之间还有间隙,这个是怎么实现的呢?

这个其实也简单,原理如下图:

image.png

  • 当云朵移出屏幕后,可以给(x,y)重新赋值,可以是随机值,但要确保x的值大于240,这样云朵就可以循环滚动了。

好了,到这里我们就可以制作连续的树木背景和随机出现云朵的背景了,背景下面的花、草、石头也是和云朵一样随机出现的,最下面的草地是连续出现的。接下来讲一下程序是怎么实现的。

【景深程序篇】


首先讲一下我封装的景深数据结构,如下:

typedef struct {    
  • ptTiles就是指向素材Tile的数组
  • ptLocation就是素材在屏幕中显示的位置坐标
  • offect_iX就是素材向左移动的距离
  • speed_iX就是向左移动的速度
  • Align_Bottom_iY为底部对齐,如下图所示:

image.png

  • 由于素材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为循环的素材填充的区域,如下图所示:

image.png

  • 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的关系如下图所示:
image.png

资源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,他们的差值不会变,即在屏幕中显示的位置不变。

image.png

【再谈碰撞检测】


上一篇我们自己简单实现了碰撞检测的函数,这次我们讲一下Arm-2D自带的函数进行碰撞检测,函数原型如下:

bool arm_2d_is_point_inside_region( 
  • 这个函数直接就可以判断一个点是不是在这个区域中,也很简单。不过,这次我们要使用一个更高级的碰撞检测,
    image.png

哈哈哈,其实就是检测一个区域,只有这个区域超过一定的面积才算碰到障碍物,原理如下图:

image.png

用到的函数原型如下:

bool arm_2d_region_intersect(   
  • 返回值为true,则有相交区域,第3个参数ptRegionOut指向这个相交区域,相交区域的面积就是ptRegionOut->tSize.iWidth与ptRegionOut->tSize.iHeight的乘积。

修改后的碰撞检测函数如下:

uint8_t ys_hit_against_inspect(){

还是很简单的。

【说在后面的话】


我们的原神小游戏虽然可以玩了,但是障碍物的运动还比较机械,人物的运动也不是很自然,所以还有很大的改进空间。下一篇我们就针对这个问题,封装成运动轨迹函数,使我们的小游戏更好玩。也期待大家能实现自己的运动轨迹函数,大家一起玩才更好玩。

下一篇精彩继续(* ̄︶ ̄)。。。。。。

原文:裸机思维
作者:FledgingSu 支离苏

专栏推荐文章

如果你喜欢我的思维,欢迎订阅裸机思维
推荐阅读
关注数
1466
内容数
108
探讨嵌入式系统开发的相关思维、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息