本章已经是《Canvas 动画系列》动画的第七篇了,我保证这一章不会再有难的数学公式和物理概念。鉴于有的同学并不是从第一章开始看这个系列,我将会把本文所用到的一些 类文件 和 工具函数 文件放到文章的开头。关于前面每一章中的示例demo都可以在 canvas动画代码文件 中找到。下面是前面章节中我们用到的类文件和工具函数文件,有兴趣的同学可去下载。
ok!回归正题,本章的主要内容有:
- 环境边界检测
- 摩擦力的应用
同样我们在本章的第一部分介绍前一节环境边界检测的主要内容:
- 边界设置
- 超出边界移除
- 超出边界重新形成
- 边界环绕
- 边界反弹
- 摩擦力的两种使用方法
- 摩擦力的应用
- 本章总结
设置边界
在上一章 速度与加速度中我们在重力加速度那一部分时其实就用到了边界检测的内容,在本章中我们将系统的讲述关于边界检测方面的内容!在大多数的情况下,一个简单的矩形就是一个物体运动的环境边界。比如我们的canvas画布,我们做的所有动画都是在canvas中运行的,那么canvas画布就是我们动画的运行环境。那么,如何设置canvas的边界呢?答案很简单:
var left = 0,
top = 0,
right = canvas.width,
bottom = canvas.height;
这里我们假设你使用整个canvas画布。当然,如果你只是用一部分也可以设定: top = 120, bottom = 300, left = 50, right = 300
这些数值的大小都随你设置,只要最终的效果是一个矩形的环境就是ok的(这里我们先讲最简单的矩形环境).
设置完边界我们就可以开始做些事情了,假设我们的canvas里有个小球:
if(ball.x > right){
// do something
}else if(ball.x < left){
//do something
}
if(ball.y > bottom){
//do something
}else if(ball.y < top){
//do something
}
我们通过判断小球的位置是否超出canvas的边界来做一些事情,那能做哪些事情呢?
超出边界移除
我们第一个要做的效果是超出边界移除,用到的是 ball.js
文件,先看看效果图吧!
上代码:
<canvas id="canvas" width="400" height="400" style="background:#000;">
your browser not support canvas
</canvas>
<p id="log"></p>
<script src="../js/utils.js"></script>
<script src="../js/ball.js"></script>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
log = document.getElementById("log");
var balls = [],
numBall = 10,
canWid = canvas.width,
canHei = canvas.height;
//定义10个小球
for(var i=0; i<numBall; i++){
var size = Math.random()*20 + 5, //颜色
color = Math.random()*(0xffffff), //大小
ball = new Ball(size, color);
ball.id = "ball_" + i; //给每个小球一个id
ball.radius = Math.random()*30+10;
ball.x = Math.random()*canWid;
ball.y = Math.random()*canHei;
ball.vx = Math.random()*2 - 1;
ball.vy = Math.random()*2 - 1;
balls.push(ball); //push进数组
}
//定义draw函数
function draw(ball, pos){
//让小球加上它的速度值
ball.x += ball.vx;
ball.y += ball.vy;
//判断是否超出边界,不论超出哪一边
if(ball.x - ball.radius > canvas.width || ball.radius+ball.x <0 || ball.y - ball.radius > canvas.height || ball.y+ball.radius < 0){
//将超出边界的小球从数组中删除
balls.splice(pos, 1);
if(balls.length > 0){
//将超出的小球的id值输出
log.innerHTML += "移除" + ball.id + "<br/>";
}else{
log.innerHTML = "全部移除";
}
}
ball.draw(context);
}
//动画循环
(function drawFrame(){
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
//定义初始变量 i 为小球的数量, 并且会随着小球的移除减小
var i = balls.length;
while(i--){
draw(balls[i], i);
}
}());
}
</script>
我们定义了十个小球,并把它们push进一个数组balls中,每当有小球超出边界(即完全消失在canvas中),就从 balls
中删除小球,并且打印出小球的id值。以横坐标为例,这里我们并没有使用 ball.x > canvas.width
,而是使用 ball.x - ball.radius > canvas.width
,这里做一点小的解释。因为 ball.x
是小球的球心,所以要让小球完全移除必须是小球的最左边(或最右边)完全超出canvas的边界,这才叫完全移除。图示如下:
图示以canvas左侧边界为例,小球最右边的坐标为 ball.x + ball.raidus
。所以,只要它超过了canvas的左侧边界,小球就被判定为完全移除。
超出边界重新形成
这个效果的思想比较简单:当小球超出了边界,重置他的位置就ok了。效果图:
这部分代码与上一部分代码区别不是很大,这里我只列出改变的部分:
function draw(ball, pos){
...
if(边界超出判定){
//超出了重置速度与坐标
ball.x = canvas.width/2;
ball.y = canvas.height;
ball.vx = Math.random()*(2) - 1;
ball.vy = Math.random()*(-2) - 1;
}
...
}
(function drawFrame(){
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
balls.forEach(draw);
}());
当小球超出了边界,我们就重置它的坐标与速度。注意,这里在重置速度代码中,我们将小球的竖直方向的速度 (ball.vy)
设置为负值,就达到了小球从下到上喷涌的效果。
边界环绕
边界环绕其实是上一部分的一个子类型。它想要实现的效果是: 当物体从一个边界消失,会从对立的边界出现 。简单来说,就是如果物体从左边界消失,就会从右边界出现。
如图所示,我们将canvas画布左右边界对接。当物体从左边界消失,立即又从右边界出现,形成一种环绕效果。其实,没有想象中的那么神秘,我们也不会真的把canvas画布的左右边界对接。先上上效果图:
上代码:
<canvas id="canvas" width="500" height="500" style="background:#000;">
your browser not support canvas!
</canvas>
<script src="../js/utils.js"></script>
<script src="../js/spaceship.js"></script>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d");
var ship = new SpaceShip();
ship.x = canvas.width/2;
ship.y = canvas.height/2;
var vr = 0,
vx = 0,
vy = 0,
ax = 0,
ay = 0,
angle = 0,
thrust = 0;
window.addEventListener("keydown", function(event){
switch (event.keyCode){
case 37:
vr = -3;
break;
case 39:
vr = 3;
break;
case 38:
ship.showFlame = true;
thrust = 0.05;
break;
case 40:
thrust -= 0.02;
break;
}
}, false);
window.addEventListener("keyup", function(event){
vr = 0;
thrust = 0;
ship.showFlame = false;
}, false);
(function drawFrame(){
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
angle += vr * Math.PI/180
ship.rotation = angle;
ax = Math.cos(angle)*thrust;
ay = Math.sin(angle)*thrust;
vx += ax;
vy += ay;
ship.x += vx;
ship.y += vy
//核心部分
if(ship.x - ship.width/2 > canvas.width){
ship.x = 0;
}
if(ship.x < 0){
ship.x = canvas.width;
}
if(ship.y - ship.height/2> canvas.height){
ship.y = 0;
}
if(ship.y <0){
ship.y = canvas.height;
}
ship.draw(context);
}())
}
</script>
为了方便演示,在动态图中我只让飞船沿着水平方向移动,当飞船消失在右边界的时候,立即会出现在左边界。这段代码中,你只需要关注核心部分,其他部分使用的是上一章速度与加速度结尾太空船的代码!相信理解起来很容易,在这我就不做过多解释了。
边界反弹
ok!本章的重头戏来了,标题的意思已经很明白了,我们要让物体触碰到边界的时候反弹回来。就像真实的世界中一样,球体撞到墙上反弹回来。有了前面的铺垫,要实现这个效果那还不是分分钟的事情!一样的套路,先上效果图:
上代码:
<canvas id="canvas" width="400" height="300" style="background:#000;">
your browser not support canvas!
</canvas>
<script src="../js/utils.js"></script>
<script src="../js/ball.js"></script>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d");
var vx = Math.random()*10 - 5;
var vy = Math.random()*10 - 5;
var ball = new Ball(20, "#ff0000");
ball.x = canvas.width/2;
ball.y = canvas.height/2;
//动画循环
(function drawFrame(){
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
ball.x += vx;
ball.y += vy;
//核心部分
if(ball.x + ball.radius > canvas.width){
ball.x = canvas.width - ball.radius;
vx *= -1;
}else if(ball.x - ball.radius < 0){
ball.x = ball.radius;
vx *= -1;
}
if(ball.y + ball.radius > canvas.height){
ball.y = canvas.height - ball.radius;
vy *= -1;
}else if(ball.y - ball.radius < 0){
ball.y = ball.radius;
vy *= -1;
}
ball.draw(context);
}())
}
</script>
代码很简洁,你只需要关注核心部分。这里同样我们判断小球是否超出边界,但不同的是,我们这次判断的是小球的最右边的位置,即坐标为 ball.x + ball.radius
的位置,当这个位置大于canvas的宽度的时候,在if执行语句中我们让小球的位置等于 canvas.width - ball.radius
即刚好球体的最右边的位置靠在canvas画布的右边界上,并且最重要的是:我们让速度乘以 -1
,相当于将速度方向旋转 180
度。
更好的写法,我们定义一个变量 bounce
,它的值除了 -1
你还可以取 -0.5, -1.5....
,不同之处在于,小球的速度会衰减或增强。如果你想要模仿现实中小球在经过几次反弹后速度逐渐减小最后停止,那么你可以将 bounce
的值设为 [-1, 0)
之间的值试一试。
var bounce = -1;
if(ball.x + ball.radius > canvas.width){
ball.x = canvas.width - ball.radius;
vx *= bounce;
}else if(ball.x - ball.radius < 0){
ball.x = ball.radius;
vx *= bounce;
}
if(ball.y + ball.radius > canvas.height){
ball.y = canvas.height - ball.radius;
vy *= bounce;
}else if(ball.y - ball.radius < 0){
ball.y = ball.radius;
vy *= bounce;
}
摩擦力的两种使用方法
关于摩擦力的两种使用方法,其实都大同小异。现在我们来介绍第一种方法,况且叫它 正规的方法 。摩擦力的作用是减小物体的运动速度,那么就意味着你需要定义一个变量 f
(代表摩擦力),让速度在每一帧都减去它,直至为零。假设现在我们有一个物体,它的速度分量分别为 vx
,和 vy
。那么我们首先要做的是 计算物体运动的角度(angle) ,其次是 物体运动的总的速度(speed) ,通过前面章节学习,我们很容易的就可以计算出来:
var angle = Math.atan(vy, vx);
var speed = Math.sqrt(vx*vx + vy*vy);
然后,在动画循环中这样设置:
if(speed > f){
speed -= f;
}else{
speed = 0;
}
最后你需要把每一帧得到的新的速度再重新分解到水平方向和竖直方向
vx = Math.cos(angle) * speed;
vy = Math.sin(angle) * speed;
ok,让我们来看看运行效果吧!
具体代码,如下:
<canvas id="canvas" width="500" height="500" style="background:#000;">
your browser not support canvas!
</canvas>
<script src="../js/utils.js"></script>
<script src="../js/ball.js"></script>
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
var ball = new Ball(20, "red");
ball.x = canvas.width/4;
ball.y = canvas.height/4;
var f = 0.05, speed = 0, angle = 0;//设定摩擦力
var vx = Math.random()*10 -5;
var vy = Math.random()*10 -5;
(function drawFrame(){
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
speed = Math.sqrt(vx*vx + vy*vy);
angle = Math.atan2(vy, vx);
if(speed > f){
speed -= f; //通过摩擦力减小速度
}else{
speed = 0;
}
vx = Math.cos(angle) * speed;
vy = Math.sin(angle) * speed;
ball.x += vx;
ball.y += vy;
ball.draw(context);
}());
}
</script>
具体的代码前面已经有了详细的解释。在这我就不重复说明了,示例中的物体的运动角度都是恒定的,你也可以在每一帧里改变物体的运动角度,看看会出现什么不一样的效果。
另一种关于摩擦力的使用,我们况且叫它简易摩擦力的使用,这里我给出部分代码:
(function drawFrame(){
window.requestAnimationFrame(drawFrame, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
//每一帧都乘以摩擦力变量
vx *= f;
vy *= f;
ball.x += vx;
ball.y += vy;
ball.draw(context);
}());
当然,为了减少计算机的计算量,提升性能,你可以在后面加个判定条件:
if(Math.abs(vx) > 0.001){
vx *= f;
ball.x += vx
}
if(Math.abs(vy) > 0.001){
vx *= f;
ball.y += vy
}
摩擦力的应用
摩擦力的应用这一部分,我们仍旧使用之前太空船的例子(我知道很简陋,但不要吐槽了)。
具体代码如下:
<script>
window.onload = function(){
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var ship = new SpaceShip();
ship.x = canvas.width/2;
ship.y = canvas.height/2;
var f = 0.97,vr = 0, vx = 0, vy = 0, ax = 0, ay = 0, speed = 0, angle = 0;
window.addEventListener("keydown", function(event){
switch (event.keyCode){
case 37:
vr = -3;
break;
case 39:
vr = 3;
break;
case 38:
speed = 0.5;
ship.showFlame = true;
break;
}
}, false);
window.addEventListener("keyup", function(event){
vr = 0;
speed = 0;
ship.showFlame = false;
}, false);
(function drawFramw(){
window.requestAnimationFrame(drawFramw, canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
ship.rotation += vr * Math.PI/180;
angle = ship.rotation;
ax = Math.cos(angle)*speed;
ay = Math.sin(angle)*speed;
vx += ax;
vy += ay;
//摩擦力
vx *= f;
vy *= f;
ship.x += vx;
ship.y += vy;
//边界检测
if(ship.x + ship.width/2 > canvas.width){
ship.x = canvas.width - ship.width;
vx *= -1;
}else if(ship.x < ship.width/2){
ship.x = ship.width/2;
vx *= -1;
}
if(ship.y + ship.height/2 > canvas.height){
ship.y = canvas.height - ship.height/2;
vy *= -1;
}else if(ship.y < ship.height/2){
ship.y = ship.height/2;
vy *= -1;
}
ship.draw(context);
}());
}
</script>
都是老例子了,只是增加了摩擦力那部分的代码,如果有什么不明白的,欢迎评论!
本章总结
// 1.移除一个超过边界的物体
if(object.x - object.width/2 > right ||
object.x + object.width/2 < left ||
object.y - object.height/2 > bottom ||
object.y + object.height/2 < top){
//移除物体代码
}
// 2.重现一个超出边界的物体
if(object.x - object.width/2 > right ||
object.x + object.width/2 < left ||
object.y - object.height/2 > bottom ||
object.y + object.height/2 < top){
//重新设置对象的位置和速度
}
//3. 边界环绕
if(object.x - object.width/2 > right){
object.x = left - object.width/2;
}else if(object.x + object.width/2 < left){
object.x = object.width/2 + right;
}
if(object.y - object.height/2 > bottom){
object.y = top - object.height/2;
}else if(object.y + object.height/2 < top){
object.y = object.height/2 + bottom;
}
//4.摩擦力(正规军)
speed = Math.sqrt(vx*vx + vy*vy);
angle = Math.atan2(vy, vx);
if(speed > f){
speed -= f;
}else{
speed = 0;
}
vx = Math.cos(angle)*speed;
vy = Math.sin(angle)*speed;
//4.摩擦力(野战军)
vx *= f;
vy *= f;
好了,关于边界检测和摩擦力这一章的主要内容都在上面了,方法都已经告诉你了,能做出什么样的效果,就看各位的想象力了。基本上关键的物理概念和数学公式都已经讲完了,在后面的几章我们主要讲解一些常见的运动形式,比如缓动动画,弹性动画等。敬请期待下一章——就是扔着玩。
最后,啰嗦一句,讲过的代码文件,以后我会在每一章的开头附上地址!
</div>