Canvas动画

使用requestAnimationFrame让画面动起来

通过前面的章节,我们知晓了如何绘制很多有趣的东西,现在我们让它动起来。首先我们得明白,所谓动画,就是把同样的事物一遍遍的绘制。当你调用一个绘制函数时,它会立即把相关的事物绘制在屏幕上。如果你想让一个事物动起来,只需要略等几微米,重新绘制动后的效果。当然你不能写入了一个无尽的循环,这只会让你的浏览器进入假死状态。你应该做的是绘制一些事物,然后让浏览器在若干微秒后执行回调函数。做这件事最容易的办法是使用JavaScript提供的setIntervial()函数,这个函数会让你的绘制函数每N秒执行一次。

然而,实际上我们几乎不会使用setInterval()函数,这个函数的绘制速度一直都是一样的,而不论用户使用的是什么电脑,不管用户在做什么,也不管当前页是否在最前面。简言之,这个函数是有效的,单并不是高效的,不卖关子了,我们推荐使用的函数是requestAnimationFrame.

requestAnimationFrame这个API被创建的目的就是为了让动画平滑高效。你在你的绘制函数中调用它,在浏览器准备好之后会自动调用此函数,这个函数给了浏览器十足的控制权,可以由此降低帧速率。通过把它控制在60帧每秒,我们可以使得动画很流畅。通过使用递归就可以用requestAnimationFrame构建循环。

接下来 让我们通过这个API小试身手,让一个矩形动起来。

<body>
    <canvas id="canvas" width="500" height="500"></canvas>
</body>
<script>
let x = 0;
function drawIt(argument) {
    requestAnimationFrame(drawIt);
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    ctx.fillStyle = 'red';
    ctx.fillRect(x,100,200,100);
    x+=5;
}
requestAnimationFrame(drawIt)

清除背景

你可能已经注意到上面的代码的一个问题,我们的矩形确实横穿了整个画布,每帧增加5像素,但是我们看到的是矩形在越来越长。但是还记得吗,Canvas就是由像素构成的缓冲图像。如果你绘制了一些图像,除非你改变他们,否则他们会一直都在哪儿。我们一起来学习下如何在每次重绘前清空Canvas画布。很简单,使用clearRect即可。

<body>
    <canvas id="canvas" width="500" height="500"></canvas>
</body>
<script>
let x = 0;
function drawIt(argument) {
    requestAnimationFrame(drawIt);
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle = 'red';
    ctx.fillRect(x,100,200,100);
    x+=5;
}
requestAnimationFrame(drawIt)
</script>

粒子模仿器

上文就是动画的核心了,重复的一次次绘制一些东西。下面我们尝试绘制一些复杂的事物,一个粒子模拟器。我们希望粒子从上往下落,像雪花一样。为了达成这种效果,我们使用经典的例子模拟算法。 粒子模拟算法

粒子模拟装置由一系列席子循环运动构成。每一帧,它们都依据上一帧的位置变化,依据之前的粒子的位置,创建或者清除相关粒子。以下是一篇简单的雪花的例子。

let canvas= document.getElementById('canvas');
let particles = [];
let tick = 0;
function loop(){
        requestAnimationFrame(loop);
        createParticles();
        updateParticles();
        killParticles();
        drawParticles();
    }
requestAnimationFrame(loop);

首先,我们创建了粒子模拟器的使用场景,这是一个没30ms执行一次的循环函数。我们需要的数据结构是一个由粒子组成的空的数组和一个计时器。每次循环都将执行以下四个函数:

function createParticles() {
    if(tick%10==0){
        if(particles.length<100){
            particles.push({
                x:Math.random()*canvas.width,
                y:0,
                speed:2+Math.random()*3,
                radius:5+Math.random()*5,
                color:'white'
            });
        }
    }
}

createParticles函数的作用在于检查当前的粒子个数是否小于100.如果小于,则会创建一个新的粒子。这个函数每十次计时才会执行一次。这使得屏幕由空白开始然后逐步的填满,而不是立即产生100个粒子。我在这里使用了Math.random()函数和一些算法以确保雪花位于不同的位置并且看起来不会是一样的,这样会让雪花看起来更加自然。你也可以采取其它的办法产生你想要的效果。

function updateParticles(){
    for(let i in particles){
        let part = particles[i];
        part.y + = part.speed;
    }
}

updatePartciles函数非常简单,它的作用是通过每次给粒子的y值添加其速度用以改变其y值。这样会让雪花往下落。

function killParticles() {
    for(let i in particles){
        let part = particles[i];
        if(part.y>canvas.height){
            part.y = 0;
        }
    }
}

killPartciles函数如上。它用来检测当前粒子是否低于Canvas的底部,在一些模拟器中,会清除这样的粒子,并把它们从列表中移除。但是我希望我所做的动画持续下去,所以我设置这样的粒子的y坐标为0.

function drawParticles() {
    ctx.fillStyle = 'black';
    c.fillRect(0,0,canvas.width,canvas.height);
    for(let i in particles){
        let part = particles[i];
        ctx.beginPath();
        ctx.arc(part.x,part.y,part.radius,0,Math.PI*2);
        ctx.closePath();
        ctx.fillStyle = part.color;
        ctx.fill();
    }
}

最后我们绘制这些小点,这个函数也非常简单。清除背景,然后依据当前Particle的x,y,radius和颜色绘制一个个的小粒子。

好啦,观看我们的成果吧。 粒子动画

哇,壮观,我喜欢粒子动画的原因之一就在于你可以通过简单的数据知识配合一些随机的效果创建出复杂的有机的自然的效果。

精灵动画

什么是精灵?

我们的最后一个动画示例是精灵动画,那么什么是精灵呢?

一个精灵是一个你可以快速的绘制在你的屏幕上的图片。通常来说,一个精灵图片是从一个叫做sprite sheet,master image 上截取下来的一小块。就像一个游戏中的不同角色一样,这张大的表格有很多表示不同事物的精灵组成,一个大的精灵图也可能包含同一角色的不同pose。这就会给你不同的动画轮廓。这就是经典的连环画似的动画,在画面中一遍一遍的绘制不同的事物。

为什么以及该在何时使用精灵图

精灵图有以下优点:

  1. 一个sprite是一张小的图片,相对由向量尤其是复杂向量构成的图片,它的绘制要快得多;
  2. 在需要重复绘制时,精灵图非常有用,比如在一个简单的射击游戏中,屏幕中充满了子弹,使用精灵图能使得很多子弹快速的绘制;
  3. 精灵图下载速度快,并且能绘制为一张表的一部分,这使得你可以每次只下载一系列精灵图中的一张,下载速度比你下载独立的图片可是快多了。通常,他们也被更好的压缩着,通常加载一个大的图片也比加载多张小图片占用内存小。
  4. 配合Photoshop等绘图工具,精灵图可以很好的用于动画。这意味着,设计师能够轻松的不涉及代码就能调整动画;

绘制精灵图

使用drawImage方法,我们可以很容易的绘制精灵图。这个函数可以绘制,变换一张图片的部分位置。比如说我们的目标大图如下: sheet

我们可以用以下方法绘制需要的部分:

context.drawImage(
    img,
    65,0,13,13,
    0,0,13,13
)

让精灵动起来

正如你在全图中所看到的,这是同一个物体在不同帧中的不同动作。现在我们让它动起来。我们使用一个计数器记录当前的帧的位置。

let frame = tick%10;
let x = frame*13;
context.drawImage(
    img,
    x,0,13,13,
    0,0,13,13
);
tick++;

每一次当我们用计数器计算当前的帧的位置时就会执行动画。使用对10取余的意义在于这个动画会在第一帧和第九帧之间循环。我们依据这个计算x的位置。随后我们绘制图片并更新计数器。当然动画可能太快,所以你可以再对tick除以2或者3使其慢下来。

在下一章中,我们将一起构建一个简单的游戏,这个游戏将展示如何使用简单的精灵动画,键盘事件和一个简单的爆炸粒子模拟器。

results matching ""

    No results matching ""