前言

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

越界检测

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

let top = 0;
let bottom = canvas.height;
let left = 0;
let right = canvas.width;

整个物体移出区域

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

// 右侧越界
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即可判定越界。

// 右侧越界
object.x + object.width/2 > right
// 左侧越界
object.x - object.width/2 < left
// 上部越界
object.y - object.height/2 < top
// 下部越界
object.y + object.height/2 > bottom

越界了该怎么办

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

将物体移除

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

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);
}

屏幕环绕

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

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;
}

反弹(粗略版)

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

  1. 检查物体是否越过任意边界;
  2. 如果发生越界, 立即将物体置回边界;
  3. 反转物体的速度向量的方向。

下面的示例是一个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轴上的距离。如图,思路以左右边越界为例:

  1. 由速度可求得物体的方向弧度angle;
  2. 算出"实际反弹点"和“理想反弹点”在x轴上的距离;
  3. 依据正切求"实际反弹点"和“理想反弹点”在y轴上的距离;
  4. “理想反弹点”的y轴坐标即是"实际反弹点"加上这段距离。

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

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 = '';
}

【30分钟学完】canvas动画|游戏基础(4):边界与碰撞的更多相关文章

  1. 【30分钟学完】canvas动画|游戏基础(2):从零开始画画

    前言 上篇主要是理论的概述,本篇会多些实践,来讲讲canvas的基础用法,并包含一些基础三角函数的应用,推荐没有canvas基础的朋友阅读,熟悉的朋友可以跳过. 本人能力有限,欢迎牛人共同讨论,批评指 ...

  2. 【30分钟学完】canvas动画|游戏基础(1):理论先行

    前言 本文虽说是基础教程,但这是相对动画/游戏领域来说,在前端领域算是中级教程了,不适合前端小白或萌新.阅读前请确保自己对前端三大件(JavaScript+CSS+HTML)的基础已经十分熟悉,而且有 ...

  3. 【30分钟学完】canvas动画|游戏基础(7):动量守恒与多物体碰撞

    前言 一路沿着本系列教程学习的朋友可能会发现,前面教程中都尽量避免提及质量的概念,很多运动概念也时刻提醒大家这不是真实的物体运动.因为真实的物体运动其实跟质量都是密不可分的,而且质量的引入自然必须提及 ...

  4. 【30分钟学完】canvas动画|游戏基础(6):坐标旋转探究

    前言 本篇主要讲坐标旋转及其应用,这是编程动画必不可少的技术. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 坐标旋转 模拟场景:已知一个中心点(centerX,cent ...

  5. 【30分钟学完】canvas动画|游戏基础(extra1):颜色那些事

    前言 本篇主要讲解关于计算机颜色系统的概念,后续结合一些canvas的应用.因为是"你不知道也没关系"的边缘知识,所以作为本系列教程的扩展,没有兴趣的同学可以跳过. 开始我们万紫千 ...

  6. 【30分钟学完】canvas动画|游戏基础(extra1-1):美图我也行

    前言 本文是接续系列教程的extra1,主要是介绍颜色系统在canvas中的应用. 本来是与extra1一起成文的,因为segmentfault莫名其妙的字数限制bug只能分割放送了. canvas操 ...

  7. 【30分钟学完】canvas动画|游戏基础(5):重力加速度与模拟摩擦力

    前言 解决运动和碰撞问题后,我们为了让运动环境更加自然,需要加入一些环境因子,比如常见的重力加速度和模拟摩擦力. 阅读本篇前请先打好前面的基础. 本人能力有限,欢迎牛人共同讨论,批评指正. 重力加速度 ...

  8. 30分钟学玩转RabbitMQ

    最近在学习RabbitMQ,在网上找了不少资料发现都特高端.动辄集群部署,分布式架构什么的,对于一个初学者实在不够友好.心想求人不如求自己,为什么不自己整理一套资料呢?于是<30分钟学玩转Rab ...

  9. 3分钟学完Python,直接从入门到精通

    作为帅气小编,我已经把python一些模块的甩在这儿了qwq,只要你拿到这些干货,包你玩转python,直接冲向"大佬"的段位,如果已经学了C或者C++或者说如果你需要你的一段关键 ...

随机推荐

  1. 吴裕雄--天生自然 JAVA开发学习: 泛型

    public class GenericMethodTest { // 泛型方法 printArray public static < E > void printArray( E[] i ...

  2. logback日志大量写磁盘导致微服务不能正常响应的解决方案

    最近几天,遇到一个莫名其妙的问题,每天几乎同一时段微服务自己跑着跑着就假死了,过几个小时就又自动恢复了. 通过对定时任务.网卡.内存.磁盘.业务日志的排查分析,只有磁盘的IO在假死前一段时间偏高,经查 ...

  3. layui从url中取值 ajax获取当前链接中的变量

    在使用layui(javascript)的时候,  需要从当前页面的url地址中取值, 例如: http://localhost:8081/html/fund-purchase.html?fundID ...

  4. xml字符串转json字符串

    XML字符串转JSON字符串网上的方法很多,这里主要推荐两种: 1.使用org.json包 jar地址:http://mvnrepository.com/artifact/org.json/json ...

  5. Smarty使用-模版中编写js

      在smarty模版中编写js使用literal标签, Literal 标签区域内的数据将被当作文本处理,此时模板将忽略其内部的所有字符信息. 该特性用于显示有可能包含大括号等字符信息的 javas ...

  6. 把本地的jar包导入到本地的maven仓库,Eclipse可以使用

    mvn install:install-file -Dfile=F:/SprintDirectory/ToolsDirectory/libary/fastdfs_client_v1.20.jar -D ...

  7. centos 中文乱码解决办法2

    特别注意:vm10自带虚拟机共享工具,自动识别utf-8格式的中文文件内容 下面步骤摘自网络: 两种方案其实差不多,这里提供两个文件的下载地址,免得去网上搜这两个文件了: fonts-chinese- ...

  8. python中编码判断

    https://www.cnblogs.com/lc-D-a/p/6074878.html python3 用isinstance()检查unicode编码报错

  9. 反编译.net下的exe程序

    1. 什么叫.net平台 .NET框架是一个多语言组件开发和执行环境,它提供了一个跨语言的统一编程环境..NET框架的目的是便于开发人员更容易地建立Web应用程序和Web服务,使得Internet上的 ...

  10. SpringMVC基本使用步骤

    使用Spring MVC,第一步就是使用Spring提供的前置控制器,即Servlet的实现类DispatcherServlet拦截url:org.springframework.web.servle ...