Canvas做游戏实践分享(十一)
7??? 缓冲和弹跳
????? 下面我们来介绍一下很常用的缓冲和弹跳的处理。在我们实现复杂运动时,这两种技术会处理大多种的情况。缓冲与弹跳非常均是用来处理将一个对象从已知起点移动到给定终点的技术——缓冲是指对象滑动到目标位置并停止,而弹跳是指对象做有摩擦力的弹簧振子运动。它们有一些共同点:
已知初始位置与目标位置运动参数与距离成正相关具体原理是缓冲的速度大小与距离成正相关,而弹跳的加速度大小与距离成正相关。
7.1??? 缓冲处理????? 缓冲有两大类,缓冲进入到某一种与缓冲离开某一点;同时还有一些具体的运动属性,如以正弦形式缓冲,以弹跳形式缓冲等。我们主要讨论缓冲离开某点的实现,其它的后面会做简单介绍。
7.1.1??? 简单缓冲
?????? 简单缓冲很容易理解,下面来简单描述其实现。假设我们想把一个对象在多帧时间之内从A移动到B。我们可以计算A,B两点间的角度与距离,对速度进行正交分解,之后在每一帧的动画中重新绘制当前对象到新位置,等对象到达B点时,停止移动对象。
?????? 上面的方案很容易实现,但如果我们想让对象移动得更自然一些,这个方案就不对了。因为它指定了对象以给定的速度在运动,到达终点时立即停止,这只适合于前面我们介绍的碰撞。我们想要的是带缓冲的实现,在这种情况下,开始的运动速度非常快,随着逐渐接近目标,速度线性地减小,当到达目标位置时,速度变为0。这种简单缓冲有一种非常容易实现的逼近方案:
??? 1. 设定一个数值(缓冲系统)用来模拟逼近我们的缓冲运动,此值与摩擦系数类似,在0和1之间。
??? 2. 决定目标位置
??? 3. 计算起点到终点的距离
??? 4. 用上面设定的缓冲系数值乘以我们得到的距离,此即当前运动对象的速度
??? 5. 在每一帧以上面得到的速度来确定物体的速度
??? 6. 重复上述3~5步。
上面我们指定的缓冲系数可以视为距离的摩擦系数,缓冲系数越接近1,瞬时速度也越大。按上面的描述,我们仍然以小球模型来实现一个简单缓冲的例子。
<!doctype html><html><head><meta charset="utf-8"><title>缓冲处理</title><style type="text/css">body {margin: 0px;padding: 0px;}canvas {margin: 0px;padding: 0px;border: 1px solid #000;}</style></head><body><canvas id="canvas" width="600" height="400"></canvas><script type="text/javascript" src="utils.js"></script><script type="text/javascript" src="ball.js"></script><script type="text/javascript">window.onload=function(){//初始化环境参数var canvas=document.getElementById("canvas");var context=canvas.getContext("2d");//设置缓冲系数var EASING=0.1;//创建小球实例var ball=new Ball(40);ball.x=0;ball.y=0;(function animationLoop(){window.requestAnimFrame(animationLoop,canvas);context.clearRect(0,0,canvas.width,canvas.height);//利用缓冲公式,得到正交分解后的瞬时速度ball.vx=(canvas.width/2-ball.x)*EASING;ball.vy=(canvas.height/2-ball.y)*EASING;//更新当前帧小球位置并绘制ball.x+=ball.vx;ball.y+=ball.vy;ball.paint(context);})();};</script></body></html></html>很简单吧?那结合之前我们介绍过的拖动对象,我们如果把对象拖到一个位置,让对象缓冲运动来原始位置。实现也非常简单,如下:
window.onload=function(){//初始化环境参数var canvas=document.getElementById("canvas");var context=canvas.getContext("2d");//设置缓冲系数var EASING=0.1;var mouse=utils.getMousePosition(canvas);//创建小球实例var ball=new Ball(40);ball.x=canvas.width/2;ball.y=canvas.height/2;//注册鼠标按下事件,检测鼠标是否位于小球内部canvas.addEventListener("mousedown",function(event){if(utils.containsPoint(mouse.x,mouse.y,ball.getBounds())){console.log("inner");ball.isDragged=true;}},false);//注册鼠标移动事件,在鼠标位于小球内时拖动小球canvas.addEventListener("mousemove",function(event){if(ball.isDragged){ball.x=mouse.x;ball.y=mouse.y;}},false);//注册鼠标抬起事件canvas.addEventListener("mouseup",function(event){ball.isDragged=false;},false);(function animationLoop(){window.requestAnimFrame(animationLoop,canvas);context.clearRect(0,0,canvas.width,canvas.height);//在小球未拖动时处理缓冲移动if(!ball.isDragged){//利用缓冲公式,得到正交分解后的瞬时速度ball.vx=(canvas.width/2-ball.x)*EASING;ball.vy=(canvas.height/2-ball.y)*EASING;//更新当前帧小球位置并绘制ball.x+=ball.vx;ball.y+=ball.vy;}ball.paint(context);})();};?
上面的实现其实有很大的性能优化余地。当运动对象已经到达目标位置后,我们的程序还在重复计算对象的速度,重绘界面。只需要添加以下约束即可:
if (ball.x === targetX && ball.y === targetY) {//具体运动代码}?但其实上面的要求相当苛刻,在芝诺悖论中就描述了相关的问题(http://zh.wikipedia.org/wiki/芝诺悖论) 。在工程上我们可以找到简单的逼近方案,下面的代码很清晰地给出了结论:
var position = 0,target = 100;for (var i = 0; i < 20; i++) { console.log(i + ": " + position); position += (target - position) * 0.5;}可以看到,在最后一次输出时,position的值已经是99.99990463256836了。这与我们的结果已经非常接近了。我们可以根据缓冲系数得到一个次数,来进行性能优化。当然,在canvas游戏开发中,由于我们的最小单位是像素,所以在工程实现上可以有更简单的逼近方案,如下:
var animationFlag=null;(function drawFrame () { animationFlag = window.requestAnim(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); var dx = targetX – ball.x; //距离一像素以内时,停止处理? if (Math.abs(dx) < 1) { ball.x = targetX; window.cancelRequestAnimationFrame(animRequest); } else { var vx = dx * EASING; ball.x += vx; } ball.draw(context);}());?
对于终点是动态确定的情形,也很容易实现,结合之前我们实现的鼠标位置获取,我们用鼠标位置来当作终点。如下代码:
window.onload = function () { var canvas = document.getElementById('canvas'), var context = canvas.getContext('2d'), var mouse = utils.getMousePosition(canvas), var ball = new Ball(), var EASING = 0.05; (function animationLoop () { window.requestAnimFrame(animationLoop, canvas); context.clearRect(0, 0, canvas.width, canvas.height); //将鼠标位置设动态设定为终点 var vx = (mouse.x - ball.x) * EASING, vy = (mouse.y - ball.y) * EASING; ?ball.x += vx; ball.y += vy; ball.draw(context); }());};?7.1.2? 缓冲的使用场景
?????? 说了这么多,你可能以为缓冲只能使用到运动动画中。其实不然,它的使用场景非常丰富,我们可以对指定对象的多个属性变化来使用缓冲技术。
?
角度变化的缓冲
?????? 很容易理解,起始位置只是换成了角度而已。如下实现:
rotation += (targetRotation - rotation) * easing;arrow.rotation = rotation * Math.PI / 180;
颜色变化的缓冲(渐变)
?????? 使用缓冲方式可以更加逼真地模拟色彩变化中的色值大小变化,透明度的变化。如果是处理色彩变化,最好是先把色值分为r,g,b后独立地对rgb三个值进行修正。如下实现:
//设置初始化值var red = 255,green = 0,blue = 0,redTarget = 0,greenTarget = 0,blueTarget = 255;//对R,G,B三角分别做处理red += (redTarget - red) * easing;green += (greenTarget - green) * easing;blue += (blueTarget - blue) * easing;//将R,G,B形式的色彩转换为16进制表示法var color = red << 16 | green << 8 | blue;?
透明度的处理也可以按此模式:
alpha += (targetAlpha - alpha) * easing;ball.color = "rgba(255, 0, 0," + alpha + ")";
7.1.3? 复杂缓冲
? ?? 对于缓冲的处理,我们介绍了最简单的处理方式,复杂的方式通常是以一条或多条曲线来模拟整个速度的变化,参见【http://flashblog.robertpenner.com/(flash版本)】及javascript版本【https://github.com/lamberta/html5-animation/tree/master/xtras/easing-equations】
?