算法的艺术(附算法特效demo)
不管在web端还是在客户端,图形引擎总是非常多,有些引擎会提供底层的api,有些会直接提供现成的特效api,由与各个引擎实现不同,如果你什么时候要换引擎,很可能某种特效就做不出来了。但是引擎的底部实现归根结底也就是各种算法,如果你掌握了算法,不管什么语言什么引擎,你都能作出很漂亮的特效,而且当你对算法了如指掌的时候,枯燥的算法就会升华为漂亮的艺术,令人叹为观止。好了,那今天,我就来讲一下简单特效算法(如果大家对算法有兴趣,可以告诉我,我之后会讲一些更加有意思的算法)。
因为createjs可以和animateCC配合简化特效的制作流程,那我先讲配合animateCC制作特效,如果不会animateCC也不想用animateCC怎么办?别着急,我也有不用animateCC的方法,不耐烦的可以直接看最后面。
大家先来看这个特效:
http://www.ajexoop.com/demo/effects/index1.html
这个是个下雨的特效,如果改变素材,可以变成漫天星星,花瓣等等,是一个比较常用的特效,那这个特效是怎么做的呢,我先放出代码。
var canvas,stage
function init() {
canvas = document.getElementById("mainView");
stage = new createjs.Stage(canvas);
stageBreakHandler();
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED;
createjs.Ticker.setFPS(30);
createjs.Ticker.addEventListener("tick", stageBreakHandler);
createRain()
}
function createRain()
{
var scount = 600;
for(var i = 0;i < scount;i++)
{
var yudian = new lib.b();
yudian.x = - 200 + 1920*Math.random();
yudian.y = -20;
yudian.gotoAndPlay(Math.ceil(Math.random()*yudian.totalFrames));//随机跳帧 防止所有雨点一起下来
yudian.mouseEnabled = false;//一般特效都需要取消鼠标事件 已防止阻碍正常鼠标事件
yudian.mouseChildren = false;
stage.addChild(yudian);
var scale = 0.2 + Math.random() * 0.8;
yudian.scaleX = -scale;
yudian.scaleY = scale;
var a = 0.4 + Math.random()*0.3;
yudian.alpha = a;
}
}
function stageBreakHandler(event)
{
stage.update();
}
大家可以看到,其实很简单,就是把元件用for循环生成多个,然后随机了大小和不透明度。那很多人又会问了,那下落怎么做到的呢,刚才本人不是提到了配合animateCC,这时候animateCC的作用就出来了。

下落的动画直接在animateCC里做好了,这样就可以省去很多事,而且还可以做到一些代码做不到的特效。
不过看起来不够真实,我再放一个改进版:
http://www.ajexoop.com/demo/effects/index2.html
这个看起来就真实多了,其实代码差不多,就是在animateCC里放了模糊滤镜,当然直接代码加也是可以的,在animateCC里只是更加直观。
第二个demo的代码:
var canvas,stage
function init() {
canvas = document.getElementById("mainView");
stage = new createjs.Stage(canvas);
stageBreakHandler();
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED;
createjs.Ticker.setFPS(30);
createjs.Ticker.addEventListener("tick", stageBreakHandler);
createRain()
}
function createRain()
{
var scount = 600;
for(var i = 0;i < scount;i++)
{
var yudian = new lib.d();
yudian.x = - 100 + 1920*Math.random();
yudian.y = -20;
yudian.gotoAndPlay(Math.ceil(Math.random()*yudian.totalFrames));//随机跳帧 防止所有雨点一起下来
yudian.mouseEnabled = false;//一般特效都需要取消鼠标事件 已防止阻碍正常鼠标事件
yudian.mouseChildren = false;
stage.addChild(yudian);
var scale = Math.random() * 1;
yudian.scaleX = -scale;
yudian.scaleY = scale;
var a = 0.4 + Math.random()*0.3;
yudian.alpha = a;
}
var bcount = 200;
for(var j = 0;j < bcount;j++)
{
var byudian = new lib.c();
byudian.x = 1920*Math.random();
byudian.y = -20;
byudian.mouseEnabled = false;//一般特效都需要取消鼠标事件 已防止阻碍正常鼠标事件
byudian.mouseChildren = false;
byudian.gotoAndPlay(Math.ceil(Math.random()*byudian.totalFrames));//随机跳帧 防止所有雨点一起下来
stage.addChild(byudian);
var bscale = 2 + Math.random() * 0.5;
byudian.scaleX = -bscale;
byudian.scaleY = bscale;
var ba = Math.random()*0.2;
byudian.alpha = ba;
}
}
function stageBreakHandler(event)
{
stage.update();
}
用了2种雨和模糊,使其更加真实。
刚才说了,在animateCC里可以做到代码做不到的特效,其中之一就是复杂的运动路径,不过这个也不是说代码做不到,就是如果用代码做非常复杂。
看图例的路径:

这个路径一点都不平滑吧,用代码就真的很难画了,用animateCC还可以画出更夸张的路径,然后大家看一下效果:
http://www.ajexoop.com/demo/effects/index3.html
是不是一个非常好看的发光特效?照旧发出代码:
var canvas,stage
function init() {
createjs.MotionGuidePlugin.install();
canvas = document.getElementById("mainView");
stage = new createjs.Stage(canvas);
stageBreakHandler();
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED;
createjs.Ticker.setFPS(30);
createjs.Ticker.addEventListener("tick", stageBreakHandler);
createLight()
}
function createLight()
{
var scount = 300;
for(var i = 0;i < scount;i++)
{
var light = new lib.lightY();
light.x = 1920*Math.random();
light.y = 600;
light.gotoAndPlay(Math.ceil(Math.random()*light.totalFrames));
light.mouseEnabled = false;
light.mouseChildren = false;
stage.addChild(light);
var scale = 0.3 + Math.random() * 0.7;
var arrow = Math.random() > 0.5?1:-1;//左右2个方向随机
light.scaleX = scale*arrow;
light.scaleY = scale;
var a = 0.4 + Math.random()*0.3;
light.alpha = a;
}
}
function stageBreakHandler(event)
{
stage.update();
}
还是基本一模一样的代码。当然其中的素材我都是用矢量做的,用位图性能会好很多。
那如果不用animateCC怎么做特效?我刚才不是也说了可以支持各种语言引擎,但是很多语言引擎是没有动画设计软件的。
最后就是重点来了,不用设计软件,直接用代码写出一个好看的动画。
大家看这个特效:
http://www.ajexoop.com/demo/effects/index4.html
var canvas,stage,container1,container2,images = {};
function init() {
createjs.MotionGuidePlugin.install();
canvas = document.getElementById("mainView");
stage = new createjs.Stage(canvas);
container1 = new createjs.Container();//后雪景容器
stage.addChild(container1);
container2 = new createjs.Container();//前雪景容器
stage.addChild(container2);
var loader = new createjs.LoadQueue(false);
loader.addEventListener("fileload", handleFileLoad);
loader.addEventListener("complete",completeHandler);
loader.loadManifest([
{src:"images/xue1.png", id:"snow1"},
{src:"images/xue2.png", id:"snow2"},
{src:"images/xue3.png", id:"snow3"},
{src:"images/xue4.png", id:"snow4"},
{src:"images/xue5.jpg", id:"snow5"},
{src:"images/xue6.png", id:"snow6"},
{src:"images/xue7.png", id:"snow7"},
{src:"images/xue8.png", id:"snow8"}
]);
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED;
createjs.Ticker.setFPS(60);
createjs.Ticker.addEventListener("tick", stageBreakHandler);
}
function handleFileLoad(evt) {
if (evt.item.type == "image") { images[evt.item.id] = evt.result; }
}
function completeHandler(event)
{
event.currentTarget.removeEventListener("fileload",handleFileLoad);
event.currentTarget.removeEventListener("complete",completeHandler);
stageBreakHandler();
createSnow();
}
//雪花数组 用作回收(下面那一长串代码实际上是可以用几行代码对象池解决的,不过为了方便理解我写的长了点,大家可以试试搜索对象池的做法)
var _snow1List = [];
var _snow2List = [];
var _snow3List = [];
var _snow4List = [];
var _snow5List = [];
var _snow6List = [];
var _snow7List = [];
var _snow8List = [];
var _frameIndex = 0;
function createSnow()
{
var i;
var snow;
var scale;
for(i = 0;i < 100;i++)
{
//后景雪
snow = getSnow(1);
scale = 0.2 + Math.random() * 0.3;
snow.scaleX = scale;
snow.scaleY = scale;
snow.x = Math.random() * 1920;
snow.y = Math.random() * 800;
snow.speed = 0.5 + Math.random() * 1;
snow.rotation = 360 * Math.random();
container1.addChild(snow);
//前景雪
snow = getSnow(0);
scale = 0.6 + Math.random() * 0.4;
snow.scaleX = scale;
snow.scaleY = scale;
snow.x = Math.random() * 1920;
snow.y = Math.random() * 800;
snow.speed = 0.7 + Math.random() * 1.3;
snow.rotation = 360 * Math.random();
container2.addChild(snow);
}
}
function lightFrameHandler()
{
var i;
var snow;
var scale;
if(_frameIndex % 10 == 0)
{
//后景雪
snow = getSnow(1);
scale = 0.2 + Math.random() * 0.3;
snow.scaleX = scale;
snow.scaleY = scale;
snow.x = Math.random() * 1920;
snow.y = - 20;
snow.speed = 0.5 + Math.random() * 1;
container1.addChild(snow);
//前景雪
snow = getSnow(0);
scale = 0.6 + Math.random() * 0.4;
snow.scaleX = scale;
snow.scaleY = scale;
snow.x = Math.random() * 1920;
snow.y = - 20;
snow.speed = 0.7 + Math.random() * 1.3;
container2.addChild(snow);
}
var mc
for(i = 0; i < container1.numChildren;i++)
{
mc = container1.getChildAt(i);
if(mc.y > 850)
{
if(mc.parent) mc.parent.removeChild(mc);
this["_snow" + mc.type + "List"].push(mc);
}
mc.x -= 0.1;
mc.y += mc.speed;
}
for(i = 0; i < container2.numChildren;i++)
{
mc = container2.getChildAt(i);
if(mc.y > 850)
{
if(mc.parent) mc.parent.removeChild(mc);
this["_snow" + mc.type + "List"].push(mc);
}
mc.x -= 0.2;
mc.y += mc.speed;
mc.rotation +=1;
}
_frameIndex++;
}
//为什么不直接做成一个对象,通过参数改变子对象呢?
// 因为如果这样做new一个对象,相当于new所有子对象,
// 而且这个对象本身又new的多 这种情况生成多个对象时很浪费性能的
function getSnow(type)
{
var snow;
switch(type)
{
case 0://0时随机
{
var random = Math.random();
if(random >= 0 && random < 0.2 )
{
snow = getSnow(1);
}
else if(random >= 0.2 && random< 0.4)
{
snow = getSnow(2);
}
else if(random >=0.4 && random < 0.6)
{
snow = getSnow(3);
}
else if(random >= 0.6 && random < 0.8)
{
snow = getSnow(4);
}
else if(random >=0.8 && random < 0.85)
{
snow = getSnow(5);
}
else if(random >= 0.85 && random < 0.9)
{
snow = getSnow(6);
}
else if(random >=0.9 && random < 0.95)
{
snow = getSnow(7);
}
else if(random >= 0.95)
{
snow = getSnow(8);
}
break;
}
case 1:
{
if(_snow1List.length > 0)
{
snow = _snow1List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow1);
}
snow.type = type;
break;
}
case 2:
{
if(_snow2List.length > 0)
{
snow = _snow2List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow2);
}
snow.type = type;
break;
}
case 3:
{
if(_snow3List.length > 0)
{
snow = _snow3List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow3);
}
snow.type = type;
break;
}
case 4:
{
if(_snow4List.length > 0)
{
snow = _snow4List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow4);
}
snow.type = type;
break;
}
case 5:
{
if(_snow5List.length > 0)
{
snow = _snow5List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow5);
}
snow.type = type;
break;
}
case 6:
{
if(_snow6List.length > 0)
{
snow = _snow6List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow6);
}
snow.type = type;
break;
}
case 7:
{
if(_snow7List.length > 0)
{
snow = _snow7List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow7);
}
snow.type = type;
break;
}
case 8:
{
if(_snow8List.length > 0)
{
snow = _snow8List.shift();
}
else
{
snow = new createjs.Bitmap(images.snow8);
}
snow.type = type;
break;
}
default:
{
break;
}
}
return snow;
}
function stageBreakHandler(event)
{
lightFrameHandler();
stage.update();
}
代码很长哈,主要是为了做对象复用,对象复用是一个防止内存溢出的很好办法(如果不停的new会导致对象回收不了而造成内存溢出),当雪花超过屏幕的时候被回收,当雪花要被生成时,对象池又有对象,那对象直接被拿出来,既被复用(其实还有更简单的办法,就是超过屏幕时,直接放回顶端,但是这样复用做的比较死,不好在程序的运行中调节数量)。
这个动画完全没用animateCC,下落的动画也用算法直接写出来,那有些人又问了,我为什么不用tween来做呢,用tween明明更加简单,原因是这样的:tween采用的是真实时间,而不是帧,真实时间会有什么问题呢,如果你浏览器最小化一会再还原,你会发现雪花都变成了一堆,因为真实时间的运算导致它还原的瞬间把原来漏运行的动画给全部一起运行了。
有些人可能注意到了,大雪花片,会有漂浮效果,这是怎么做到的呢?其实也很简单,别设置中心点就好了,在0,0点旋转的时候,它会有个向上的过程,图片本身又是向下落的,这样上下一中和,就有种漂浮的感觉了。
以上的算法都是用js搭配createjs的api写的,其实这里api的成分并不多,只要大家掌握了核心思想,换哪种语言都可以写出来。
好了,今天的文章就写到这里,还是我在文章开头的那句话,如果大家对特效算法有兴趣,可以直接告诉我,我会再写相关教程与博文,谢谢大家支持。
匿名
111