资讯专栏INFORMATION COLUMN

【30分钟学完】canvas动画|游戏基础(4):边界与碰撞

宠来也 / 3359人阅读

摘要:越界是常见的场景,一般会有两种场景的越界一是整个物体移出区域,二是物体接触到区域边界。基本思路检查物体是否越过任意边界如果发生越界,立即将物体置回边界反转物体的速度向量的方向。核心代码如下完整示例两圆基于距离的碰撞演示

前言

本系列前几篇中常出现物体跑到画布外的情况,本篇就是为了解决这个问题。
阅读本篇前请先打好前面的基础。
本人能力有限,欢迎牛人共同讨论,批评指正。

越界检测

假定物体是个圆形,如图其圆心坐标即是物体的x轴和y轴坐标。
越界是常见的场景,一般会有两种场景的越界:一是整个物体移出区域,二是物体接触到区域边界。我们以画布边界为例进行讨论,示例中矩形边界即是:

</>复制代码

  1. let top = 0;
  2. let bottom = canvas.height;
  3. let left = 0;
  4. let right = canvas.width;

整个物体移出区域

要整个物体离开范围才算越界,则可得越界条件如下,以下任何一项为true即可判定越界。

</>复制代码

  1. // 右侧越界
  2. object.x - object.width/2 > right
  3. // 左侧越界
  4. object.x + object.width/2 < left
  5. // 上部越界
  6. object.y + object.height/2 < top
  7. // 下部越界
  8. object.y - object.height/2 > bottom
物体接触到区域边界

物体接触到区域边界就算越界,则可得越界条件如下,以下任何一项为true即可判定越界。

</>复制代码

  1. // 右侧越界
  2. object.x + object.width/2 > right
  3. // 左侧越界
  4. object.x - object.width/2 < left
  5. // 上部越界
  6. object.y - object.height/2 < top
  7. // 下部越界
  8. object.y + object.height/2 > bottom
越界了该怎么办

搞明白越界条件后,接下来讨论越界之后的处理办法,一般是一下四种。

将物体移除

这是最简单的处理办法,属于整个物体移出区域才算越界的情况。
下面的例子会先批量创建ball,保存在balls数组里,每次动画循环都会遍历这个数组,依次输入draw()函数,改变ball的位置并检测是否越界。下面只列出draw()函数的代码。
完整示例:清除越界圆

</>复制代码

  1. function draw(ball, pos) {
  2. // 依据球的速度改变球的位置
  3. ball.x += ball.vx;
  4. ball.y += ball.vy;
  5. // 检查是否越界
  6. if (ball.x - ball.radius > canvas.width || ball.x + ball.radius < 0 || ball.y - ball.radius > canvas.height || ball.y + ball.radius < 0) {
  7. // 在数组中清除越界的球
  8. balls.splice(pos, 1);
  9. // 打印提示
  10. if (balls.length > 0) {
  11. log.value += `Removed ${ball.id}
  12. `;
  13. log.scrollTop = log.scrollHeight;
  14. } else {
  15. log.value += "All gone!
  16. ";
  17. }
  18. }
  19. // 画球
  20. ball.draw(context);
  21. }
将其物体置回边界内

属于整个物体移出区域才算越界的情况。
下面的例子也是把创建的ball保存在balls数组里,但ball的初始位置都是画布中间的下部,如果检测到有ball越界,则会重置ball的位置。下面只列出draw()函数的代码。
完整示例:彩色喷泉

</>复制代码

  1. function draw(ball) {
  2. // 依据球的速度改变球的位置,这里包含了伪重力
  3. ball.vy += gravity;
  4. ball.x += ball.vx;
  5. ball.y += ball.vy;
  6. // 检测是否越界
  7. if (ball.x - ball.radius > canvas.width || ball.x + ball.radius < 0 || ball.y - ball.radius > canvas.height || ball.y + ball.radius < 0) {
  8. // 重置ball的位置
  9. ball.x = canvas.width / 2;
  10. ball.y = canvas.height;
  11. // 重置ball的速度
  12. ball.vx = Math.random() * 6 - 3;
  13. ball.vy = Math.random() * -10 - 10;
  14. // 打印提示
  15. log.value = `Reset ${ball.id}
  16. `;
  17. }
  18. // 画球
  19. ball.draw(context);
  20. }
屏幕环绕

属于整个物体移出区域才算越界的情况。
屏幕环绕就是让同一个物体出现在边界内的另一个位置,如果一个物体从屏幕左侧移出,它就会在屏幕右侧再次出现,反之亦然,上下也是同理。
这里比前面的要稍微复杂的判断物体跃的是那边的界,伪代码如下:

</>复制代码

  1. if(object.x - object.width/2 > right){
  2. object.x = left - object.widht/2;
  3. }else if(object.x + object.width/2 < left){
  4. object.x = right + object.width/2;
  5. }
  6. if(object.y - object.height/2 > bottom){
  7. object.y = top - object.height/2;
  8. }else if(object.y + object.height/2 < top){
  9. obejct.y = bottom + object.height/2;
  10. }
反弹(粗略版)

这是较复杂的一种情况,属于物体接触到区域边界就算越界的情况。基本思路:

检查物体是否越过任意边界;

如果发生越界, 立即将物体置回边界;

反转物体的速度向量的方向。

下面的示例是一个ball在画布内移动,撞到边界就反弹,反弹核心代码如下。
完整示例:反弹球(粗略版)

</>复制代码

  1. if (ball.x + ball.radius > right) {
  2. ball.x = right - ball.radius;
  3. vx *= -1;
  4. } else if (ball.x - ball.radius < left) {
  5. ball.x = left + ball.radius;
  6. vx *= -1;
  7. }
  8. if (ball.y + ball.radius > bottom) {
  9. ball.y = bottom - ball.radius;
  10. vy *= -1;
  11. } else if (ball.y - ball.radius < top) {
  12. ball.y = top + ball.radius;
  13. vy *= -1;
  14. }
反弹(完美版)

咋看似乎效果不错,但仔细想想,我们这样将物体置回边界的做法是准确的吗?
答案是否定的,理想反弹与实际反弹是不同的,如下图:

从图中我们可以清除的知道,ball实际上是不太可能会在理想反弹点反弹的,因为如果速度过大,计算位置时ball已经越过“理想反弹点”到达“实际反弹点”,而我们如果只是将ball的x轴坐标简单粗暴移到边界上,那还是不可能是“理想反弹点”,也就是说这种反弹方法不准确。
那么,完美反弹的思路就明确了,我们需要找到“理想反弹点”,并将ball置到该点。如果是左右边越界,则算出"理想反弹点"与“实际反弹点”在y轴上的距离;如果是上下边越界,则算出"理想反弹点"与“实际反弹点”在x轴上的距离。如图,思路以左右边越界为例:

由速度可求得物体的方向弧度angle;

算出"实际反弹点"和“理想反弹点”在x轴上的距离;

依据正切求"实际反弹点"和“理想反弹点”在y轴上的距离;

“理想反弹点”的y轴坐标即是"实际反弹点"加上这段距离。

改造后的核心代码如下,至于有没有必要多做这么多运算,这就要权衡性能和精密性了。
完整示例:反弹球(完美版)

</>复制代码

  1. if (ball.x + ball.radius > right) {
  2. const dx = ball.x - (right - ball.radius);
  3. const dy = Math.tan(angle) * dx;
  4. ball.x = right - ball.radius;
  5. ball.y += dy;
  6. vx *= bounce;
  7. } else if (ball.x - ball.radius < left) {
  8. const dx = ball.x - (left + ball.radius);
  9. const dy = Math.tan(angle) * dx;
  10. ball.x = left + ball.radius;
  11. ball.y += dy;
  12. vx *= bounce;
  13. }
  14. if (ball.y + ball.radius > bottom) {
  15. const dy = ball.y - (bottom - ball.radius);
  16. const dx = dy / Math.tan(angle);
  17. ball.y = bottom - ball.radius;
  18. ball.x += dx;
  19. vy *= bounce;
  20. } else if (ball.y - ball.radius < top) {
  21. const dy = ball.y - (top + ball.radius);
  22. const dx = dy / Math.tan(angle);
  23. ball.y = top + ball.radius;
  24. ball.x += dx;
  25. vy *= bounce;
  26. }
碰撞检测

和越界检查很像,我们扩展到两个物体间的碰撞检测,一般常用的有如下两种办法。

基于几何图形的碰撞检测

一般是用在检测矩形的碰撞,原理就是判断一个物体是否和另一个物体有重叠。
下面直接给出两个检测的工具函数。完整示例:

两个矩形碰撞检测演示

矩形与点碰撞检测演示

</>复制代码

  1. // 两个矩形碰撞检测
  2. function intersects(rectA, rectB) {
  3. return !(rectA.x + rectA.width < rectB.x ||
  4. rectB.x + rectB.width < rectA.x ||
  5. rectA.y + rectA.height < rectB.y ||
  6. rectB.y + rectB.height < rectA.y);
  7. };

</>复制代码

  1. // 矩形与点碰撞检测
  2. function containsPoint(rect, x, y) {
  3. return !(x < rect.x || x > rect.x + rect.width || y < rect.y || y > rect.y + rect.height);
  4. };
基于距离的碰撞检测

一般是用在检测圆形的碰撞,原理就是判断两个物体是否足够近到发生碰撞。
对于圆来说,只要两个圆心距离小于两圆半径之和,那我们就可判定为碰撞。圆心距离可通过勾股定理求得。核心代码如下:
完整示例:两圆基于距离的碰撞演示

</>复制代码

  1. const dx = ballB.x - ballA.x;
  2. const dy = ballB.y - ballA.y;
  3. const dist = Math.sqrt(dx ** 2 + dy ** 2);
  4. if (dist < ballA.radius + ballB.radius) {
  5. log.value = "Hit!";
  6. } else {
  7. log.value = "";
  8. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/54528.html

相关文章

  • 30分钟学完canvas动画|游戏基础(4):边界碰撞

    摘要:越界是常见的场景,一般会有两种场景的越界一是整个物体移出区域,二是物体接触到区域边界。基本思路检查物体是否越过任意边界如果发生越界,立即将物体置回边界反转物体的速度向量的方向。核心代码如下完整示例两圆基于距离的碰撞演示 前言 本系列前几篇中常出现物体跑到画布外的情况,本篇就是为了解决这个问题。 阅读本篇前请先打好前面的基础。 本人能力有限,欢迎牛人共同讨论,批评指正。 越界检测 假...

    岳光 评论0 收藏0
  • 30分钟学完canvas动画|游戏基础(7):动量守恒多物体碰撞

    摘要:科普动量是守恒量。动量守恒定律表示为一个系统不受外力或者所受外力之和为零,这个系统中所有物体的总动量保持不变。动量守恒定律可由机械能对空间平移对称性推出。在可以忽略碰撞以外的因素时,动量是守恒的。 前言 一路沿着本系列教程学习的朋友可能会发现,前面教程中都尽量避免提及质量的概念,很多运动概念也时刻提醒大家这不是真实的物体运动。因为真实的物体运动其实跟质量都是密不可分的,而且质量的引入自...

    Dr_Noooo 评论0 收藏0
  • 30分钟学完canvas动画|游戏基础(7):动量守恒多物体碰撞

    摘要:科普动量是守恒量。动量守恒定律表示为一个系统不受外力或者所受外力之和为零,这个系统中所有物体的总动量保持不变。动量守恒定律可由机械能对空间平移对称性推出。在可以忽略碰撞以外的因素时,动量是守恒的。 前言 一路沿着本系列教程学习的朋友可能会发现,前面教程中都尽量避免提及质量的概念,很多运动概念也时刻提醒大家这不是真实的物体运动。因为真实的物体运动其实跟质量都是密不可分的,而且质量的引入自...

    scq000 评论0 收藏0

发表评论

0条评论

宠来也

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<