5.边界控制与摩擦力
在大多数的游戏设计中,会有环境边界控制来帮助我们保证画布中的对象在运动的过程中不会脱离画面。同时,对象的运动环境很少会是真空的情况,所以就会存在着摩擦力阻碍物体的速度变化。
5.1 环境边界控制
如果我们关注的运动对象在移动的过程中消失在了视窗之外,我们有两大类的选择:一种是将对象重新移动到当前的视窗中或者不再关注此对象,另一种方式是使当前的视窗跟随运动对象移动。在程序实现上,这两类很相近。下面我们主要针对第一类来进行分析。
设置边界
运动对象的运动边界可以是整个Canvas视窗,也可以只是其中的一小块,在实现上没本质上的差异。我们假设边界为整个Canvas视窗。即:
var left = 0, top = 0, right = canvas.width, bottom = canvas.height;
很轻易地得到边界处理的模板代码:
if (ball.x > canvas.width) { // do something } else if (ball.x < 0) { // do something } if (ball.y > canvas.height) { // do something } else if (ball.y < 0) { // do something }
在对超出边界对象的处理时,我们可以将这个对象清理了,或者将其对象回收放入对象池中使用,或者将对象重新放置在画布的另一侧让其继续运动,或者使对象进行反弹。
清理对象
以小球系统为例,假设我们有大量小球对象,这些小球被放置在一个数组中并初始化。如果小球运动时超出边界,则将此对象从小球数组中清理。如下:
if (ball.x – ball.radius > canvas.width || ball.x + ball.radius < 0 || ball.y – ball.radius > canvas.height || ball.y + ball.radius < 0) { balls.splice(balls.indexOf(ball), 1); }
但如果我们在之前的小球系统中加入上述代码后运行就会发现问题,下面两幅图清晰地反映了这个问题及解决方式:
第一幅图我们可以看到,小球的一半已经进入了边缘,此时我们的程序才检测出小球运动超出了画布。如第二幅图所示,我们需要特殊处理,在小球的球心离边缘的距离小于其半径时,就需要进行边缘越界处理。如下:
if (ball.x – ball.radius > canvas.width || ball.x + ball.radius < 0 || ball.y – ball.radius > canvas.height || ball.y + ball.radius < 0) { balls.splice(balls.indexOf(ball), 1); }
以下为上述原理的一个完整实现的例子:
window.οnlοad=function(){ var canvas = document.getElementById('canvas'); var context = canvas.getContext("2d"); var balls=[]; var ballNumbers=10; //初始化小球 for(var ball,i=0;i<ballNumbers;i++){ ball=new Ball(20); ball.id="ball"+i; ball.x=Math.random()*canvas.width; ball.y=Math.random()*canvas.height; ball.vx=Math.random()*2-1; ball.vy=Math.random()*2-1; balls.push(ball); } //绘制小球 function draw(ball,pos){ //速度处理 ball.x+=ball.vx; ball.y+=ball.vy; //边缘检测 if(ball.x-ball.radius > canvas.width|| ball.x+ball.radius < 0|| ball.y+ball.radius < 0| ball.y-ball.radius > canvas.height){ balls.splice(pos,1); } ball.draw(context); } //帧处理 (function drawFrame(){ window.requestAnimFrame(drawFrame,canvas); context.clearRect(0,0,canvas.width,canvas.height); console.log(balls.length); var i=balls.length; while(i--){ draw(balls[i],i); } })();
对象回收
对于越界运动对象的回收处理与上面介绍的清理方式非常相似,只需要把清理的过程换成对象运动状态的初始化即可。如下我们以一个类喷泉式的示例来演示其原理:
window.οnlοad=function(){ var canvas = document.getElementById('canvas'); var context = canvas.getContext("2d"); var balls=[]; var ballNumbers=300; var gravity=0.5; //初始化小球 for(var ball,i=0;i<ballNumbers;i++){ ball=new Ball(2,Math.random()*0xffffff); ball.x=canvas.width/2; ball.y=canvas.height; ball.vx=Math.random()*2-1; ball.vy=Math.random()*-10-10; balls.push(ball); } //绘制小球 function draw(ball){ //重力及速度处理 ball.vy+=gravity; ball.x+=ball.vx; ball.y+=ball.vy; //边缘处理 if(ball.x-ball.radius > canvas.width|| ball.x+ball.radius < 0|| ball.y+ball.radius < 0| ball.y-ball.radius > canvas.height){ ball.x=canvas.width/2; ball.y=canvas.height; ball.vx=Math.random()*2-1; ball.vy=Math.random()*-10-10; } ball.draw(context); } //帧处理 (function animationLoop(){ window.requestAnimFrame(animationLoop,canvas); context.clearRect(0,0,canvas.width,canvas.height); console.log(balls.length); for(var i=0;i<ballNumbers;i++){ draw(balls[i]); } })(); };
如上代码,每一帧中,我们会对所有的小球对象进行绘制。在此定义了一个draw函数用来处理越界及绘制功能。首先对重力加速度进行了处理,接着对于两个坐标轴的速度进行了处理,然后检测小球是否越界(如果越界,则将其运动状态初始化),最后绘制小球。整体效果图如下:
画布扩展
画布扩展的实现很简单,如果运动对象超出画布的右侧,则将其移动到画面的左侧(反之亦然),如果运动对象超出画布的上方,则将其移动到画面的下侧(反之亦然)。其实现原理与上述的对象回收类似。我们对之前介绍过的键盘控制飞船应用进行改造,只需要在其帧处理函数animationLoop中ship.paint(context)前加入如下的边界处理代码即可:
if(ship.x-ship.width/2>right){
ship.x=left-ship.width/2;
}else if(ship.x+ship.width<left){
ship.x=right+ship.width/2;
}else if(ship.y+ship.height/2<top){
ship.y=bottom+ship.height/2;
}else if(ship.y-ship.height/2>bottom){
ship.y=top-ship.height/2;
}