引题
最近忙完实验,在慢慢开始的学习HTML5 的canvas,很早就听过动态canvas的威名,一直没有机会和时间学习。这次系统学习HTML5和css3,自然不会放过这次机会。
本文主要是总结学习canvas动画的一些基本知识,并不会介绍canvas,关于入门读者朋友可以回去翻翻书本。另外本文还会试着写几个实例,加深理解。
canvas动画基本思想
思想一 刷新重绘
我们平时看到的canvas游戏 里,人物运动、物体变化都是基于这个思想。比如绘制动画的圆,我们通过鼠标触动或者重力等外界因素改变圆的圆心坐标。下一步,通过:
context.clearRect(0, 0, canvas.width, canvas.height)
把当前canvas的画面清空。然后,再通过:
context.art(circle.x, circle.y, circle.radius, 0, Math.PI*2)
利用新的圆心坐标绘制新的圆。
刷新重绘的思想是canvas游戏的核心,任何需要移动的物体都是通过这种方式。
不过在即时战略游戏中,很多东西不需要更新,需要的只是人物,这样我们全部清楚再重绘就有点得不偿失。这里就需要用到一种区域适配更新的技术,是John Carmack发明的,叫做ATR技术。有兴趣的朋友可以去google一下。
思想二 碰撞检测
碰撞检测用的比较比较频繁。比如和canvas上的圆交互的时候,需要检测点击的是哪个圆,只需循环判断触点坐标到圆心的距离与半径的关系即可。再比如迷宫游戏里面,移动人物的时候需要判断前方是否是墙壁,是墙壁的话就不能移动。这个碰撞检测的方法不太常见,是通过像素检测的方式。如果前方的像素是我们预定的墙壁的颜色,就不能移动了。
一些需要知道的东西
动画对于重绘速度有要求,通常每秒30次以上才不会导致出现间断的现象。由于绘制速度很快,可能你会觉得我们的画布会反应迟钝。但是,由于很多现代浏览器都使用了硬件加速等性能增强技术,把图像处理工作转移给显卡,从而节省了CPU。由此引申出一个css hack技术。开启硬件加速,css如下:
transform: translateZ(0); transform: translate3d(0, 0, 0);
圆圈绘图程序每次刷新画布,都会先使用clearRect清除所有,再绘制所有(比上次多). 这样不会出现闪烁,因为Canvas针对这个问题进行了优化,它实际上会在绘图逻辑执行完成后才清除或绘制所有内容。
canvas动画实例
实例一 自由落体交互的小球
要不断的移动小球,正如上面所说,就需要不断重绘canvas
setTimeOut(“drawFrame()”, 20) 接下来看看drawFrame函数:
function drawFrame() {
//
清空
context.clearRect(
0,
0, canvas.width, canvas.height);
context.beginPath();
//
balls是一个存放我们小球的数组。
for(
var i=
0; i<balls.length; i++) {
var ball = balls[i];
ball.x += ball.dx;
ball.y += ball.dy;
//
添加重力效果
if ((ball.y) < canvas.height) ball.dy +=
0.22;
//
添加摩擦力效果
ball.dx = ball.dx *
0.998;
//
碰到墙反弹
if ((ball.x + ball.radius > canvas.width) || (ball.x - ball.radius <
0)) {
ball.dx = -ball.dx;
}
//
碰到地反弹注意0.96哦!
if ((ball.y + ball.radius > canvas.height) || (ball.y - ball.radius <
0)) {
ball.dy = -ball.dy0.
96;
}
context.fillStyle =
"
white
";
//
Draw 开始绘制球
context.arc(ball.x, ball.y, ball.radius,
0, Math.PI2);
context.lineWidth =
1;
context.fill();
context.stroke();
}
setTimeout(
"
drawFrame()
",
20);}
drawFrame函数是动画的核心,处理外力对小球的影响。考虑了重力摩擦力等。然后不断调整小球的坐标,刷新重绘。
下面是我们处理鼠标交互的函数:
function canvasClick(e) {
//
触点位置
var clickX = e.pageX - canvas.offsetLeft;
var clickY = e.pageY - canvas.offsetTop;
//
边界检测,看点击哪个球。
for(
var i
in balls)
{
var ball = balls[i];
if ((clickX > (ball.x-ball.radius)) && (clickX < (ball.x+ball.radius)))
{
if ((clickY > (ball.y-ball.radius)) && (clickY < (ball.y+ball.radius)))
{
//
改变小球的速度。
ball.dx -=
2;
ball.dy -=
3;
return;
}
}
}}
最后是增加小球和清空画布:
function addBall() {
var radius = parseFloat(document.getElementById(
"
ballSize
").value);
var ball =
new Ball(
50,
50,
1,
1,radius);
balls.push(ball);
}
function clearBalls() {
//
清空数组,下次重绘的就是空得了。
balls = [];
}
最终结果:
实例二 散落的粒子
这次,写一个无数粒子构成的名字,鼠标接触马上散落各处的例子。
无数粒子是用canvas画的圆,不过半径我写成了1px。这里就不展示了。
下面看核心函数,处理小球运动的轨迹:
function updateBalls(canvas, balls, timeDiff, mousePos) {
var context = canvas.getContext(
'
2d
');
var collisionDamper =
0.3;
//
碰撞减缓
var floorFriction =
0.0005 * timeDiff;
//
摩擦力
var mouseForceMultiplier =
1 * timeDiff;
//
Multiplier 乘数
var restoreForce =
0.002 * timeDiff;
//
和上个例子一样,循环小球数组,改变每个小球的圆心坐标。
for(
var n =
0; n < balls.length; n++) {
var ball = balls[n];
//
改变圆心坐标
ball.y += ball.vy;
ball.x += ball.vx;
//
恢复力,最后要静止。
if(ball.x > ball.origX) {
ball.vx -= restoreForce;
}
else {
ball.vx += restoreForce;
}
if(ball.y > ball.origY) {
ball.vy -= restoreForce;
}
else {
ball.vy += restoreForce;
}
//
鼠标给的力
var mouseX = mousePos.x;
var mouseY = mousePos.y;
var distX = ball.x - mouseX;
//
判断左右的。
var distY = ball.y - mouseY;
var radius = Math.sqrt(Math.pow(distX,
2) + Math.pow(distY,
2));
var totalDist = Math.abs(distX) + Math.abs(distY);
var forceX = (Math.abs(distX) / totalDist) * (
1 / radius) * mouseForceMultiplier;
var forceY = (Math.abs(distY) / totalDist) * (
1 / radius) * mouseForceMultiplier;
if(distX >
0) {
//
如果从左边触动小球
ball.vx += forceX;
}
else {
ball.vx -= forceX;
}
if(distY >
0) {
//
从上方触动小球
ball.vy += forceY;
}
else {
ball.vy -= forceY;
}
//
考虑地板摩擦力
if(ball.vx >
0) {
ball.vx -= floorFriction;
}
else
if(ball.vx <
0) {
ball.vx += floorFriction;
}
if(ball.vy >
0) {
ball.vy -= floorFriction;
}
else
if(ball.vy <
0) {
ball.vy += floorFriction;
}
//
碰到地板反弹
if(ball.y > (canvas.height - ball.radius)) {
ball.y = canvas.height - ball.radius -
2;
ball.vy *= -
1;
ball.vy *= (
1 - collisionDamper);
}
//
碰到上顶反弹
if(ball.y < (ball.radius)) {
ball.y = ball.radius +
2;
ball.vy *= -
1;
ball.vy *= (
1 - collisionDamper);
}
//
碰到右边反弹
if(ball.x > (canvas.width - ball.radius)) {
ball.x = canvas.width - ball.radius -
2;
ball.vx *= -
1;
ball.vx *= (
1 - collisionDamper);
}
//
碰到左边反弹
if(ball.x < (ball.radius)) {
ball.x = ball.radius +
2;
ball.vx *= -
1;
ball.vx *= (
1 - collisionDamper);
}
}
}
就像注释的一样,考虑了反弹、地板摩擦力、鼠标力、恢复力等。通过这个函数改变小球的位置。
剩下其他几个不太重要的函数就不写了,主要是循环小球数组重绘,寻找鼠标触点位置等,和第一个差不多。
效果(本来想写freestyle的,太多了,懒得去复制后面的。):