引言
pico-8 的社区是一个满是热情与分享精神的社区,无数玩家与开发者贡献着代码。本系列日志将选取其中比较精彩的代码片段进行解析,学习其中的算法思路。
效果
效果截图:
代码分析
作者:2DArray
来源:twitter
一个有趣的植物沿着物体生长的 gfx,作者在 twitter 上贴了代码
代码比较简单,我格式化了一下,只有 19 行:
cls()print("grow") |
前 6 行是在屏幕下半部分中打印“GROW”,后面再解释。先来看看最精彩的循环部分 7-19 行。
8-12 行定义了 5 个变量:
c
用来计数x
在横坐标随机取值,y
在64-128
范围内随机取值;很明显这是在下半个屏幕中随机取坐标点,即“GROW”所在区域u
随机取[x-1,x,x+1]
在x
值左中右3
个值中随机,v =y-1
是y
值上方的点
这样算法的意图就比较明显了,在显示范围内随机取点,在该点上方左中右方向上延伸,以模拟植物向上分叉生长的感觉。我们可以把 u
改成 u=x
使得植物只向上方生长。
也可以令 v=y+1
使像素点向下延伸。
第 13-17 行的两个嵌套 for
循环,将 (u, v)
所在的附近九宫格内的像素点遍历一遍;if (pget(a,b) %6 > 0)c += 1
,取其颜色值 (0-15
) 与 6
取余,大于 0
则 c
加一。其实就是计算 (u, v)
附近的颜色非 0
,6
的像素的数量 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 写法的影响。