感谢大佬Jerome Herr给我们授权使用他的作品~

 

create'webby' numbers and letters

 

这是一个流行款粒子连线的效果,相信各位在各个网站都见过很多,如果你有在关注OF,在兜兜鱼代码检查的前期实验篇:

 

代码造物| 教你用Processing做一张运动蛛网

 

中,我们早已讨论过关于粒子连线的问题,然而有趣的是,在这个例子中,粒子会随着按键的输入而改变自身的形状为对应字母,并且你可以通过这个例子了解到像素和pGraphs的妙用。

 

这次我会采用按功能分类的方式来对这个例子进行解释,(似乎一直都是这样-.-),例子中会涉及到三角函数的使用,这里假定你已经掌握了processing的基本语法并且对三角函数在生成艺术中的使用有所了解,所以不再深剖原理~

 

源代码:

 

由于整个程序都在调用Ball来形成图像,所以Ball是构成整个图像最重要的部分,接下来我把代码拆开成几个部分讲解:

 

 

 

ClassBall

 

 

首先来讨论Ball是如何定义的,就如往常我们所常见的粒子类,Ball拥有位置速度半径等基本属性,具备显示,运动,连线等方法。

 

 

这里主要要进行解释的是move函数和lineBetween函数

 

voidmove(){

sin/cos

 

这里move中的函数之所以要在位置计算后半部分增加三角函数计算,是为了让各个球体围绕着一点进行圆周运动,你可以观察其中一个球体,它的路径是一个圆形。如果去除后半部分的三角函数,将会让全部的点停止运动。

theta

 

theta是一个在增长的变量,这个变量决定了点圆周运动的速率,调大增长量后,点的运动速率会变快。

 

dir各有百分之五十的概率取到正负,这样可以错开点的运动方向。

 

offSet

 

offSet的作用是让每个Ball的在初始化的时候取到不同的三角函数值,这意味着虽然每个点都在以相同的速率做圆周运动,但是每个点在各自的圆的运动轨迹上开始的位置不同,这就进一步错开了每个点的运动。

 

下面我比较了两种不同offSet值的情况:

 

【radius= random(50, 100);】

 

Ball圆周运动的范围就会扩大。

 

}

 

voidlineBetween(){

 

这是每单个Ball都具备的方法,在这个方法中,会对ballCollection中的每一个对象进行检测,i会从0一直增长到ballCollection.size()-1,这样就可以让ballCollection中的每一个球都进行一次以下的操作。

 

被轮到的Ball会被命名为other,other会和当前这个球体的位置loc进行距离检测,如果距离在设定的范围(0,d)之间,就会对两个Ball进行连线,并且会将connection[i]改为true,表示ballCollection[i]对应的Ball对象开启连接,如果不在距离之内,该连接状态将会关闭。

 

countC的作用是计算处在连接状态的Ball的数量,在最后会对每个ballCollection[i]对应的connection[i]进行检测,如果处在连接状态,countC的计数将会+1,countC的值会影响连线的透明度值,意味着处在连接状态的Ball的数量会影响线条的透明度值。

 

球体定义成功,但这里指的是其中一个球体,接下来需要一个方法来将更多的球体调出。

 

}

voidcreateStuff(){

 

ballCollection中的Ball会被清空,由于createStuff是在初始化阶段以及按键触发后被调用的,初始化阶段并无作用,因为ballCollection在开始的时候并没有对象存放。

 

而在按键触发的时候,会很需要这一功能,如果不清空之前已加载的Ball,多个字母将会叠加:

【overload】

 

letter图层为一个白背景,黑字体的PGraphics图层,在这个图层中,写出了l,这里的l不是指字母l,而是一个命名为l的字符串。

你可以通过下面这个链接了解PGraphics,

 

https://processing.org/reference/PGraphics.html

 

简单来说它是一个额外的图层,你可以在这个图层上任意涂鸦,而不影响原本的画布,当然你也可以叠加多个图层组合出不同效果。pgraphics图层默认是不显示的,它的显示方式和图片的显示方式相同,使用image函数加载pgraphics图层。

 

下一步加载了letter图层的所有像素,在这个图层范围中,会加载所有的球体,所以num指的是球体的数量。

 

下面是球体运动会摆出字母形状的关键!

 

在letter中随机选择一个像素点获取颜色,当这个像素点的亮度低于255的时候就生成org,loc,radius,dir,offSet用于Ball的初始化,最后将这个Ball加载入ballCollection。

 

我们来观察刚刚加载的letter图层,如下图:

colorc为画布上随机选择到的像素点的颜色,黑色的亮度值为0,白色的亮度值为255。

 

它的亮度值小于255意味着它是黑色的,而黑色像素点所在的部分就是字体所在的部分,因此,当随机取到的点的位置(x,y)为字体上的黑色像素时,才会在这个位置上添加一个Ball对象,这就是最终所有的Ball会呈现出一个字母的原因。

 

}

 

voidsetup(){

 

setup函数创建了一个与画布尺寸同样大小的pGraphics对象,

初始化了一个用以存储Ball的ArrayList()数组ballCollection,

并且调用了createStuff()函数,顺着pGraphics上的黑色字母添加了多个Ball对象。

 

}

 

voiddraw(){

 

draw函数调用了ballCollection中每一个Ball对象的run函数,让每个Ball持续运动、连线、显示。

 

theta为每个Ball中move()函数会调用到的变量,它的增长会让Ball开始旋转。

 

这里声明的是一个全局变量,你也可以在类中定义theta,差别在与这会让每个类都多一个变量,会占相对大的内存,这么做可以让每个Ball中的theta取到不同的值或发生不同的变化,如果每个Ball的theta都相同,那么可以使用全局变量来节省内存。

 

}

 

voidkeyPressed(){

 

当按键发生变化的时候,l(开头定义的存储字母的全局变量)的存储值就换成按键后的字母。

 

清空上一个的字母的Ball群体,按照变化后的字母重新生成一次Ball群体。

 

}