本系列前几篇中常出现物体跑到画布外的状况,本篇就是为了解决这个问题。
阅读本篇前请先打好前面的基础。
本人能力有限,欢迎牛人共同讨论,批评指正。javascript
假定物体是个圆形,如图其圆心坐标便是物体的x轴和y轴坐标。
越界是常见的场景,通常会有两种场景的越界:一是整个物体移出区域,二是物体接触到区域边界。咱们以画布边界为例进行讨论,示例中矩形边界便是:html
let top = 0; let bottom = canvas.height; let left = 0; let right = canvas.width;
要整个物体离开范围才算越界,则可得越界条件以下,如下任何一项为true便可断定越界。java
// 右侧越界 object.x - object.width/2 > right // 左侧越界 object.x + object.width/2 < left // 上部越界 object.y + object.height/2 < top // 下部越界 object.y - object.height/2 > bottom
物体接触到区域边界就算越界,则可得越界条件以下,如下任何一项为true便可断定越界。git
// 右侧越界 object.x + object.width/2 > right // 左侧越界 object.x - object.width/2 < left // 上部越界 object.y - object.height/2 < top // 下部越界 object.y + object.height/2 > bottom
搞明白越界条件后,接下来讨论越界以后的处理办法,通常是一下四种。github
这是最简单的处理办法,属于整个物体移出区域才算越界的状况。
下面的例子会先批量建立ball,保存在balls数组里,每次动画循环都会遍历这个数组,依次输入draw()函数,改变ball的位置并检测是否越界。下面只列出draw()函数的代码。
完整示例:清除越界圆canvas
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 > canvas.height || ball.y + ball.radius < 0) { // 在数组中清除越界的球 balls.splice(pos, 1); // 打印提示 if (balls.length > 0) { log.value += `Removed ${ball.id}\n`; log.scrollTop = log.scrollHeight; } else { log.value += 'All gone!\n'; } } // 画球 ball.draw(context); }
属于整个物体移出区域才算越界的状况。
下面的例子也是把建立的ball保存在balls数组里,但ball的初始位置都是画布中间的下部,若是检测到有ball越界,则会重置ball的位置。下面只列出draw()函数的代码。
完整示例:彩色喷泉数组
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 > canvas.height || ball.y + ball.radius < 0) { // 重置ball的位置 ball.x = canvas.width / 2; ball.y = canvas.height; // 重置ball的速度 ball.vx = Math.random() * 6 - 3; ball.vy = Math.random() * -10 - 10; // 打印提示 log.value = `Reset ${ball.id}\n`; } // 画球 ball.draw(context); }
属于整个物体移出区域才算越界的状况。
屏幕环绕就是让同一个物体出如今边界内的另外一个位置,若是一个物体从屏幕左侧移出,它就会在屏幕右侧再次出现,反之亦然,上下也是同理。
这里比前面的要稍微复杂的判断物体跃的是那边的界,伪代码以下:dom
if(object.x - object.width/2 > right){ object.x = left - object.widht/2; }else if(object.x + object.width/2 < left){ object.x = right + object.width/2; } if(object.y - object.height/2 > bottom){ object.y = top - object.height/2; }else if(object.y + object.height/2 < top){ obejct.y = bottom + object.height/2; }
这是较复杂的一种状况,属于物体接触到区域边界就算越界的状况。基本思路:函数
下面的示例是一个ball在画布内移动,撞到边界就反弹,反弹核心代码以下。
完整示例:反弹球(粗略版)工具
if (ball.x + ball.radius > right) { ball.x = right - ball.radius; vx *= -1; } else if (ball.x - ball.radius < left) { ball.x = left + ball.radius; vx *= -1; } if (ball.y + ball.radius > bottom) { ball.y = bottom - ball.radius; vy *= -1; } else if (ball.y - ball.radius < top) { ball.y = top + ball.radius; vy *= -1; }
咋看彷佛效果不错,但仔细想一想,咱们这样将物体置回边界的作法是准确的吗?
答案是否认的,理想反弹与实际反弹是不一样的,以下图:
从图中咱们能够清除的知道,ball其实是不太可能会在理想反弹点反弹的,由于若是速度过大,计算位置时ball已经越过“理想反弹点”到达“实际反弹点”,而咱们若是只是将ball的x轴坐标简单粗暴移到边界上,那仍是不多是“理想反弹点”,也就是说这种反弹方法不许确。
那么,完美反弹的思路就明确了,咱们须要找到“理想反弹点”,并将ball置到该点。若是是左右边越界,则算出"理想反弹点"与“实际反弹点”在y轴上的距离;若是是上下边越界,则算出"理想反弹点"与“实际反弹点”在x轴上的距离。如图,思路以左右边越界为例:
改造后的核心代码以下,至于有没有必要多作这么多运算,这就要权衡性能和精密性了。
完整示例:反弹球(完美版)
if (ball.x + ball.radius > right) { const dx = ball.x - (right - ball.radius); const dy = Math.tan(angle) * dx; ball.x = right - ball.radius; ball.y += dy; vx *= bounce; } else if (ball.x - ball.radius < left) { const dx = ball.x - (left + ball.radius); const dy = Math.tan(angle) * dx; ball.x = left + ball.radius; ball.y += dy; vx *= bounce; } if (ball.y + ball.radius > bottom) { const dy = ball.y - (bottom - ball.radius); const dx = dy / Math.tan(angle); ball.y = bottom - ball.radius; ball.x += dx; vy *= bounce; } else if (ball.y - ball.radius < top) { const dy = ball.y - (top + ball.radius); const dx = dy / Math.tan(angle); ball.y = top + ball.radius; ball.x += dx; vy *= bounce; }
和越界检查很像,咱们扩展到两个物体间的碰撞检测,通常经常使用的有以下两种办法。
通常是用在检测矩形的碰撞,原理就是判断一个物体是否和另外一个物体有重叠。
下面直接给出两个检测的工具函数。完整示例:
// 两个矩形碰撞检测 function intersects(rectA, rectB) { return !(rectA.x + rectA.width < rectB.x || rectB.x + rectB.width < rectA.x || rectA.y + rectA.height < rectB.y || rectB.y + rectB.height < rectA.y); };
// 矩形与点碰撞检测 function containsPoint(rect, x, y) { return !(x < rect.x || x > rect.x + rect.width || y < rect.y || y > rect.y + rect.height); };
通常是用在检测圆形的碰撞,原理就是判断两个物体是否足够近到发生碰撞。
对于圆来讲,只要两个圆心距离小于两圆半径之和,那咱们就可断定为碰撞。圆心距离可经过勾股定理求得。核心代码以下:
完整示例:两圆基于距离的碰撞演示
const dx = ballB.x - ballA.x; const dy = ballB.y - ballA.y; const dist = Math.sqrt(dx ** 2 + dy ** 2); if (dist < ballA.radius + ballB.radius) { log.value = 'Hit!'; } else { log.value = ''; }