Pico-8 精彩代码--植物生长效果解析

引言

pico-8 的社区是一个满是热情与分享精神的社区,无数玩家与开发者贡献着代码。本系列日志将选取其中比较精彩的代码片段进行解析,学习其中的算法思路。

效果

效果截图:

代码分析

作者:2DArray

来源:twitter

一个有趣的植物沿着物体生长的 gfx,作者在 twitter 上贴了代码

代码比较简单,我格式化了一下,只有 19 行:

cls()print("grow")
line(0,5,15,5,11)
memcpy(0,24576,384)
cls()
sspr(0,0,15,6,11,70,105,35)::_::
x=rnd(128)
c=0
y=rnd(64)+64
u=x+rnd(2)-1
v=y-1for a=u-1,u+1 do
for b=v-1,v+1 do
if(pget(a,b)%6>0)c+=1
endendif(pget(x,y)%6>0 and(pget(u,v)==6 or rnd()<.1) and c<6) pset(u,v,3+c%2*8)goto _

前 6 行是在屏幕下半部分中打印“GROW”,后面再解释。先来看看最精彩的循环部分 7-19 行。

8-12 行定义了 5 个变量:

  • c 用来计数
  • x 在横坐标随机取值,y64-128 范围内随机取值;很明显这是在下半个屏幕中随机取坐标点,即“GROW”所在区域
  • u 随机取 [x-1,x,x+1]x 值左中右 3 个值中随机, v =y-1y 值上方的点

这样算法的意图就比较明显了,在显示范围内随机取点,在该点上方左中右方向上延伸,以模拟植物向上分叉生长的感觉。我们可以把 u 改成 u=x 使得植物只向上方生长。

也可以令 v=y+1 使像素点向下延伸。

第 13-17 行的两个嵌套 for 循环,将 (u, v) 所在的附近九宫格内的像素点遍历一遍;if (pget(a,b) %6 > 0)c += 1,取其颜色值 (0-15) 与 6 取余,大于 0c 加一。其实就是计算 (u, v) 附近的颜色非 06 的像素的数量 c,这里背景色是黑色 0, “GROW” 是灰色 6

第 18 行,根据 3 个判断将点 (u, v) 着色;

  • pget(x, y) % 6 > 0 (u, v)下方的点 (x, y) 不能是背景色 0 或者物体的颜色 6,也就是草不能从背景或者物体上长出,这里只能从底部绿色土地长出。如下图改变了字体的颜色,草就从字体上长出来了。
  • pget(u, v) == 6 or rnd() < 0.1(u, v) 的像素如果是字体颜色 6,则可以着色,否则只有 0.1 的概率会着色。这里模拟植物优先在字体表面增长,空白部位只有很少的草在增长。如下图将字体颜色改成粉色 14,草就无法在字体上生长。
  • c < 6(u, v) 周围九宫格范围内的草的数量,如果多余 6 个则不在生长,用于控制草的密度。

最后根据 pset(u, v, 3 + c % 2 * 8) 密度用两种绿色进行着色。简单明了的实现了植物生长的特效。

最后解释一下 1-6 行在屏幕上打印“GROW”的代码。2-3 行在屏幕的左上角打印 GROW 并在字体下方加上绿色的下划线。第 4 行 memcpy(0, 24576, 384) 将屏幕内容所在的内存地址 24576 后的 384 bytes 复制到精灵内存地址 0。pico-8 中 24576 开始的内存存放的是屏幕中的像素颜色信息,一个 bytes(8 位)可以存放两个点,低 4 位和高 4 位分别储存一个点的颜色信息,这也是 pico-8 只能有 2 的 4 次方,16 种颜色的原因。那 384 = 128 * 6 / 2 正好是屏幕中打印的 GROW 所在的前 6 行,将打印的 GROW 拷贝到存放的精灵的地址中,再使用 sspr 函数将精灵放大显示到屏幕的中心。

这样复杂的操作仅仅是为了打印一个字号比较大的 GROW 而已,因为自带的 print 函数不能设置字号。这样做会把 pico-8 中精灵编辑器中前 6 行的内容都覆盖掉,所以直接在精灵编辑器中编辑再显示在屏幕中是个更好的选择。这里作者肯定是为了简便才使用这种直接打字的方式。

总结

这种随机遍历每个像素点来实现的特效会消耗大量的 cpu,用在游戏中的话还需要做相应的简化。虽然这种奇技淫巧看起来很难用到游戏里,但是这也是可以逐像素操作的 pico-8 魅力所在。当然在其他游戏引擎中,就可以用像素着色器(Shader)更有效率的实现这种算法,使其变得可用。作者使用 u, v 做变量名,应该也是 Shader 写法的影响。