我现在介绍一下如何用ActionScript写一个Flash版本。
效果图如下:
点击查看线上效果 (键盘X清屏,S停止,按住Shift更改风向)*
要求示例文件:silkflash.zip(ZIP, 4KB)首先让我们来分析一下需要考虑的问题:波浪线的画法波浪线间的填充效果颜色的过渡光效一、波浪线的画法
这个效果中很重要的一个亮点就是一层层平滑的波浪,相信细心的朋友一定注意到了这里面的随机事件,每一条线都是一条随机路径,难点不在于如何选取随机点的位置,而是如何用一条平滑的线将它们连接起来。如图1中左图是一条随机线中的各个随机点,如果用直线连接那么就是一条条的折线,显然不是我们要的波浪线;如果如中图那样用曲线来连接,难点就是如何来确定曲线的控制点;所以我采用的方法是如右图那样,用每两个点的中点来做实际连接的锚点,而采用原来的随机点作控制点,这样既能保证平滑又可以确保曲线与随机点的方向一致,不至于最后出来的波浪线走样儿。
图1.波浪线的画法
找到了锚点和控制点后,用我在这篇文章介绍过的drawPath来画线,这里我就不多罗嗦了。下面就是随机和计算波浪线控制点的代码:
//用来存储锚点和控制点的数组 var gpd:Vector.< Number > = new Vector.< Number >(); for(var i:int=datas.length-1;i >=0;i--){ if(i < 2){ //打头的两个点运动幅度相对大一些 datas[i].x += windX*1.05 + (1 - 2 * Math.random())*headRandomStep; datas[i].y += windY*1.05 + (1 - 2 * Math.random())*headRandomStep }else{ //其余各点运动幅度相对小一些 datas[i].x += windX + (1 - 2 * Math.random()) * fadeStep; datas[i].y += windY + (1 - 2 * Math.random()) * fadeStep; } if(i > 0){ //各点都朝着前一个点运动,宏观就是整体形状的萎缩 datas[i].x += (datas[i-1].x - datas[i].x)/7; datas[i].y += (datas[i-1].y - datas[i].y)/7; } if(i < datas.length-1){ //计算中心点,并将其添加到数组中作为锚点坐标,实际随机点作为控制点坐标。 var x2:Number = (datas[i].x + datas[i+1].x)/2; var y2:Number = (datas[i].y + datas[i+1].y)/2; var ax:Number = datas[i+1].x; var ay:Number = datas[i+1].y; if(i < datas.length-2){ gpd.push(ax); gpd.push(ay); } gpd.push(x2); gpd.push(y2); } }二、波浪线间的填充效果
波浪线做好后,每两段相邻的波浪线之间应该是连续填充的,否则丝绸就变成粗线条的麻布了。我刚看到这个效果的时候就在想这个问题,仔细观察后发现波浪线之间的填充色有很强的渐变,而且在一些极端区域甚至可以看到细线。这让我确定了之前的一个猜测,那就是波浪线之间的填充是若干条细线,而不是整块填充色。
也就是说,在画两条相邻的波浪线时,同时将它们中间的若干条过渡线补充进去,这样当过渡线足够多就会形成填充。方案一旦确定,实施的代码其实很简单:
//设置过渡线的数目为20条 var number:Number =20; for(var i:int=0;i< number;i++){ var g:Vector.< Number > = new Vector.< Number >(); for(var j:int=0;j< gpd.length;j++){ //将相邻两条线对应的两个点之间平均分为20份,然后依次赋给对应过渡线的相应点 g.push(graphicsDatas[j]+i*(gpd[j] - graphicsDatas[j])/number); } //画图 }三、颜色的过渡
丝绸绚丽的色彩是整个效果最夺目的地方,它其实就是一条连续的光谱。
可见光的光谱从长波到短波会依次经历彩虹的七个颜色:红、橙、黄、绿、青、蓝、紫。如果从RGB颜色通道上来看,就是下面的这六个阶段:
0xFF0000(红) -> 0xFFFF00(黄)-> 0x00FF00(绿) -> 0x00FFFF(蓝绿) -> 0x0000FF(蓝) -> 0xFF00FF(紫)
所以RGB的变化规律是这样的,当R达到0xFF,开始增加G的值;当G增加到0xFF,开始减小R的值,减小到0后增加B的值;当B到达0xFF开始减小G的值,达到0值后增加R的值,然后减小B的值完成一个光谱循环。用程序描述就是下面的代码:
var speed:Number = 0xFF/80; if(r >0){ if(g >=0xFF){ r-=speed; }else if(b>=0xFF){ r +=speed; }else if(b==0){ g+=speed; } } if(g >0){ if(b >=0xFF){ g-=speed; }else if(r >=0xFF){ g +=speed; }else if(r==0){ b+=speed; } } if(b >0){ if(r >=0xFF){ b-=speed; }else if(g>=0xFF){ b +=speed; }else if(g==0){ r+=speed; } } r = Math.max(Math.min(0xFF,r),0); g = Math.max(Math.min(0xFF,g),0); b = Math.max(Math.min(0xFF,b),0); colorTransform.color = (r<<16)+(g<<8)+b;四、光效
为了弄清楚整个效果的点睛之笔,也就是光影效果,我确实费了好一阵功夫。我的思路是这样的,当所有的线条都画好后,由于波浪线的疏密会形成颜色的深浅,这一点无需质疑,只要将波浪线一条一条画上去自然会水到渠成。但是仔细观察后发现,原效果中线条密集的地方除了饱和的颜色之外,还有明显的白色高光。所以我坚信一定要用到BlendMode.ADD这个算法,因为只有这个方法会在两种颜色叠加时增加RGB的实际值。可是当我用BlendMode.ADD后发现效果与预期的相差甚远,像素感特别强,更别提什么平滑。我用了整整两个通宵来解决这个问题,修改透明度、颜色值、加高光层等等做过各种尝试,最后发现原因出现在一个小小的细节上。因为我用的是BitmapData位图绘制,在黑色的背景上有一层输出位图,我在初始化的时候将输出的位图定义成全透明的,这样绘制的波浪线实际上是在一张透明层上做图形叠加,所以结果很差。解决的方法就是将输出背景改为不透明黑色,这样得到的效果就和当初设想的一模一样!
至于效果中其他的一些细节比如跟随线条的小星星等因为不是核心技术瓶颈,所以我就不一一解释。我写这个代码的目的就是想告诉Flash开发者,Flash 和ActionScript都已经非常成熟,只要你在网页上见到的任何绚丽的效果,不管它是不是用Flash开发,基本上都可以用Flash作出一模一样甚至更好的结果。
补充:老刘Ryan也做了一版,我们采用了不同的方法,在颜色过渡上他用的是mx.utils.HSBColor,在曲线生成上他用的perlin,都是原生的API,在执行效率上肯定要更高。