pico-8 精彩代码解析 4
来源:twitter
整理了一下作者的代码,共计15行:
for i=0,11 do pal(i,({135,10,9,137,8,136,2,130,128})[i+1],1) end function _draw() srand(3) rectfill(0,0,127,40,0) for y=52,170,2 do k=y/19-2 x=y*44%138-4 h=40+rnd(20) for i=-20,20 do line(x+i*h/70,y,x+sin(t()/4+x/500+y/300)/7*h,y-h,i>0 and k+2 or k) end end end
1-3行, 将调色板的颜色映射,这里用到了第二块调色板的颜色,更好的表示渐变。关于第二块调色板的内容,在解析 2里有详细说明。
5行, 初始随机数种子,在计算树木高度的时候详细解释为什么每次绘制要初始随机数
6行, 画背景,我们用cls()代替手动绘制背景,可以更好的测试
7行, 循环得到y,y值是树的y轴位置,从52-170,每两个树之间相差2
8行, k用来将树按照y值的顺序,映射到调色板的色值,实现近景到远景的颜色渐变
9行, 计算树的x坐标,根据y轴坐标的倍数与138取余,就把树木限制在0-138的范围内一排排的码好。
10行, 计算树木的高度h,这里每棵树的高度是随机的,但是每帧绘制的时候同一棵树的高度是不变的,也就是每一帧绘制的时候rnd(20)会得到同一个随机值,这就得益于在第5行,绘制开始的时候重新初始化随机数种子,使得每帧获得随机数是一致的。
11-13行,绘制一棵树。pico8没有直接绘制三角形的函数,这里用40根线段拼成了一个三角形。我们先看下简化的代码,line(x+i,y,x,y-h,i>0 and k+2 or k) 。i值(-20 -> 20)是三角形底边的x轴偏移量,从线段 起点(x-20,y) 终点 (x,y-h) 到 起点 (x+20,y) 终点 (x,y-h)] 共计画了40跟线段组成了三角形,三角形的底边宽40。简化的效果图如下。
line(x+i*h/70,y,x+sin(t()/4+x/500+y/300)/7*h,y-h,i>0 and k+2 or k) 原代码计算起点时将偏移量i乘以树木的高度h/70用来表示,矮的树木拥有更窄的底边。计算终点时,树尖的x坐标根据时间,取正弦值,用来模拟来回摆动的效果,且越矮的树木摆动幅度越低。最后根据偏移量i来觉得颜色,i<0为树木左半边,i>0为树木右半边。
pico-8 精彩代码解析 3
作者: Luca@lucatron_
来源:twitter
整理过后14行代码:
c={10,9,8,2,1,0} fillp(0xa5a5) function _draw() for i=24576,32767 do poke(i,peek(i)/2) end for z=6,1,-.1 do for b=0,7 do b=b/8+sin(z/8+t()/4)/30 k=19-sin((6-z)/20)*40*(.5+sin(t()/2+z/9+b*2)/2) circfill(64+cos(b)*k,64+sin(b)*k,z*2.3,c[flr(z)]+c[flr(z+.5)]*16) end end end
1-2行,c是触手的颜色,顶部到底部由明止暗。fillp是填充样式,点阵图样填充,有种渐变的效果(在最后详细解释)。
4-6行,是残影效果,这里没有用cls清空画布,直接将屏幕内存中的数值每帧减半,最终降为0透明,来实现透明效果
10-11行,两个嵌套循环,外层的z表示,每个触手的层数,内层b表示触手的数量(8个)。单个触手是由从下至上,大小递减,颜色逐渐变亮的圆组成,z表示圆的位置,相当于z轴的坐标,从6-1共有60层。
9行,b/8 将八只触手映射到 0-1,pico8中三角函数的参数取值0-1代表着0-360°。在第11行的circlefill 圆的绘制函数中,圆B的位置 x,y = 64+cos(b)*k,64+sin(b)*k 。 其中原点是屏幕的中心点64,64;角度b为每个触手映射的角度,k为圆至屏幕中心的距离。
10行 计算每个圆至屏幕中心的距离k,我们先把圆的角度b从中去掉看下结果。k=19-sin((6-z)/20)*40*(.5+sin(t()/2+z/9)/2)
将圆的z轴坐标z映射到至中心的距离k,通过sin的映射使得,距离k根据z呈现正弦波的形状。
最后把角度b加回去,k=19-sin((6-z)/20)*40*(.5+sin(t()/2+z/9+b*2)/2) 使得不同的触手在波形上拥有不同的位置。
第11行 circlefill 画圆,其中圆的半径 根据z轴距离变化,z*2.3。圆的颜色c[flr(z)]+c[flr(z+.5)]*16 是两个颜色相加,利用高低位的不同颜色,再配合第2行的fillp实现两种颜色的填充图样。
最后补充下fillp的用法。pico8中用一个4*4的图来填充 circ() circfill() rect() rectfill() pset() line()等函数画的图形。
16个像素点用上图表格里的数字代表,如果想让某个点不着色就将点对应的数值累加,将最终得到的值传给fillp,比如只让对角线
32768,1024,32,1没有颜色,fillp(32768+1024+32+1)
也可以7+8*16 指定第二个颜色。
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写法的影响。
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写法的影响。
pico-8 精彩代码解析 2
作者:2DArray
来源:twiiter
神奇的漩涡效果,代码发布在作者的twitter上,整理下只有11行:
p={130,141,2,136,8,137,142,9} for i=1,8 do pal(i-1,p[i],1) end ::_:: x=rnd(128) y=rnd(128) d=sqrt((x-64)^2+(y-64)^2)-t()*4 a=atan2(y-64,x-64)*16 pset(x,y,(a+d/4)%8) goto _
1-4行代码是调色板映射代码,用来实现渐变的颜色。我们知道pico8只有16种颜色,16种颜色很难配合出渐变的过渡效果。这里作者用到pico8隐藏的调色板,变量p中的130,141,136,137,142都是隐藏调色板的色号。这个隐藏的调色板在官方文档里也没有介绍,在论坛翻了了好久才找到,新旧两个调色板的对比,我做了个对比图:
6-9行定义了4个参数:
- x,y 在屏幕范围内随机取值
- d 是取的点(x,y)相对于(64,64)即中心点的距离,后面的t()*4 是为了使距离根据运行时间改变
- a 是点(x,y)相对于中心点的角度,pico8中atan2的返回值是0-1,对应这0°-360°。后面的*16将a的取值变成了0-16
第10行 根据d 和 a的值将对应颜色填充到x,y。我们可以分开来看下
pset(x,y,a%8)
如果只用a来计算,会将屏幕按照点的到中间点的角度分割成16份,这个比较好理解,因为a的取值是0-16,相当于把屏幕中的点映射到16个区域。
pset(x,y,(d/4)%8)
d是点到中心的距离,相同距离的圆形区域被填充同一个颜色。
pset(x,y,(a+d/4)%8)
当把角度和距离加起来的时候神奇的图像就出现了,而且角度a必须于8的倍数相乘才可以对齐,不知道原理是什么,真的很神奇。