pico-8 精彩代码解析 1
pico-8 的社区是一个满是热情与分享精神的社区,无数玩家与开发者贡献着代码。本系列日志将选取其中比较精彩的代码片段进行解析,学习其中的算法思路。
效果截图:
作者:2DArray
来源:twiiter
一个有趣的植物沿着物体生长的gfx,作者在twitter上贴了代码(由于404的原因我就不发原文连接了)
代码比较简单,我格式化了一下,只有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-1 for a=u-1,u+1 do for b=v-1,v+1 do if(pget(a,b)%6>0)c+=1 end end if(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 在横坐标随机取值,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写法的影响。