继上篇日志分享了第一次使用PICO-8开发游戏之后,我已经沉迷在PICO8无限魅力之中。这次准备提高点难度开发一款动作游戏,尝试下如何处理物体移动 碰撞等。估计要分多篇日志才可以分享完,第一篇先把主要的游戏机制完成。好了先上游戏截图,介绍下游戏玩法。
在线版本地址:https://www.lexaloffle.com/bbs/?tid=31557
点击上面的链接试玩。
简单玩法
- 按方向键中的右键进行蓄力,松开后跳跃
- 在空中碰到墙壁转为攀爬状态,可以继续蓄力跳跃
- 在空中碰到最下方岩浆则死亡,等待重生
- 在空中碰到对手(2P)如果此时你的高度高于对手,则将对手踩下,我方得分;反之则被对手踩下,对手得分
角色控制
我们要控制角色在游戏中位移,首先就要纪录角色位置 速度等信息。我使用了一个table用来纪录player1的所有信息。
gravity = 0.25 player1 = {} player1.x = 16 //角色坐标 x player1.y = 64 //角色坐标 y player1.vx = 0 //角色水平方向速度 player1.vy = 0 //角色垂直方向速度 function _update() player1.x = player1.x + player1.vx player1.y = player1.y + player1.vy player1.vy = player1.vy + gravity end
这里的速度其实为下一帧坐标的变化量,我们在游戏循环函数_update()中每次执行都将vx vy加到x y中,这样就可以改变角色位置。vx vy为0时角色静止不动,向右方跳跃只需要赋值 vx = 5 vy=-7这样角色就会以每帧右移5像素,上移7像素的速度运动了。当然这里还得考虑重力才可以模拟出跳跃的抛物线,我使用了一个变量gravity = 0.25纪录重力值,在_update()中每帧执行 vy = vy - gravity改变vy的大小,这里的gravity相当于角色在y轴的加速度。
关于蓄力(按键时间越长跳跃越高),其实就是根据按键时间长度调整松开按键后player1.vy的值,按的时间越长垂直方向向上的速度越大。
角色动画
动画在pico中似乎比较麻烦,不像其他游戏引擎提供了良好的帧动画编辑功能。简陋的pico什么都没有提供,看来只能自己实现动画帧的切换了。
首先我们先把需要的sprites在编辑器中都画出来
- 前两帧为角色攀爬在墙上时的动画,即1,2两帧循环切换。
- 2,3,4,5帧为蓄力的动画,身体靠近墙边的距离表示蓄力的程度。
- 第6帧为角色死亡掉落时的sprite。
首先是角色攀爬在墙上时1,2两帧循环切换的方法。为了做动画首先我们得用一个变量纪录时间的变化,我使用player1.time 来纪录,在_update()中执行player1.time = player1.time + 1。
实际上time纪录了角色初始化以来的帧数,我们就可以根据这个time计算出当前该显示哪个sprite。
if flr(flr(player1.time/10) % 2) == 0 then player1.frame = 1 else player1.frame = 2 end
这里的player1.frame 纪录的就是sprite的索引。关键代码是flr((player1.time/10) % 2),这里的flr是向下取整函数。
这里flr(player1.time/10)是对time取商,然后又对商取余。取商的操作实际上控制了切换的速度,我们的update是以30fps的速度运行的,如果每帧都替换sprite那动画1秒将运行15次,显然太快了。我们time/10取商相当于慢了10倍,time的值1秒钟增加30变成了增加3,每次增加的时候切换sprite,就相当于1秒钟执行了1.5次动画。后面%2取2的余数,是为了获得0 ,1;当为0的时候显示第一帧1的时候显示第二帧。这里是只有两个sprites就完成动画的情况,如果需要n张sprites完成动画循环,就%n就可以了;再根据返回的0到n-1顺序显示sprite。
总结下flr((time/m) % n),先取商 flr(time/m);m越大播放速度越慢。再取余数%n,有几帧n就等于几。
蓄力动画,我们设定蓄力时间最长是2s,也就是按下按键后蓄力动画要在2s之内以均匀的速度播放完毕。在2s内执行4帧动画,2s中会执行60帧,60/4=15 也就是说,m=15,n=4。flr((time/15) % 4),剩下的就是根据值更换sprite了。
角色碰撞
上一篇日志说到了根据fget mget来获取当前坐标值下地图中的flag,使用这种方法可以方便的判断角色是否碰到墙壁,或者下方的岩浆。但是游戏中另外一个角色2P并不是在地图编辑器中绘制出来的,所以此方法已无法获取到。我们需要新的方法判断是否于2P产生来碰撞。
当然这个新方法也十分简单,就是判断两个角色的xy坐标之差的绝对值是否同时小于8(角色sprite的宽高都是8)。当然也可以减小阈值8,使碰撞更难发生。
if abs(player1.x - player2.x) < 8 and abs(player1.y - player2.y) < 8 then end
其实就是简单的aabb盒模型判断碰撞,pico里几乎所有东西都是矩形,简单的矩形碰撞检测就可以通吃,可以不用复杂的碰撞检测算法。
当检测到角色碰撞后,在下方(y比较小)的角色会被踩停下落,把下方的角色的vx vy都赋为0,角色就会在gravity的加速度下自由落体。
2P颜色处理
为了跟1P区分,2P至少在颜色上应该有所不同。但是仅仅是颜色不同就在sprite sheet上再画一遍2P是不是太麻烦了,而且又浪费了6个sprite,要是知道pico只有最多255个sprites。
当然pico提供了更好的解决方法,就是在执行2P绘制之前替换色板的颜色:
pal(8,12) pal(2,3) spr(player2.frame,player2.x,player2.y,1,1) pal()
这里在spr绘制之前调用了pal(x,y)方法,这个方法就是将调色板的x位置用y位置的颜色替换,这里是用12号蓝色替换了8号红色,3号绿色替换2号紫色。最后在执行完spr绘制后,调用不带参数的pal()将调色板还原回去。
2P AI
这里的AI只做了简单的处理,就是随机取0-2s,作为蓄力的时间,后面加入更多元素之后应该还会增加AI的复杂度。
目前游戏还是只是简单的模型,下面将会加入空中可以释放的道具(飞镖之类),移动的墙壁之类的丰富玩法的内容;还将加入更多的动画,爆炸,火焰等;当然还有音乐,虽然这对我来说实在是太困难了。
如果你对此游戏有任何好的建议请给我留言,当然还有文章中的错误,欢迎指正。
暂无关于此日志的评论。