带着canvas去流浪系列之八 碰撞【华为云技术分享】

【摘要】 canvas动画-碰撞仿真

示例代码托管在:http://www.github.com/dashnowords/blogs

经过前面章节相对枯燥的练习,相信你已经能够上手canvas的原生API了,那么从这一节开始,我们就开始接触点好玩的东西——动画。

经过前面章节相对枯燥的练习,相信你已经能够上手canvas的原生API了,那么从这一节开始,我们就开始接触点好玩的东西——动画。

一. canvas的能力

如果你以为canvas只能绘制图表那真的就图样图森破了,且不谈webgl的绘图上下文,单就2d空间的画笔就可以做很多有意思的事情,比如实现一些酷炫的动画效果,比如做一些物理仿真,图片滤镜,直播弹幕,甚至做游戏开发等等,画面的变化大多依赖于canvas提供的像素操作能力,而动效几乎都是靠canvas在短时间内逐帧绘制而形成的,和电影的原理是一样的。

我们知道javascript中和时间控制有关的函数setTimeout( ) 以及setInterval( )最终执行时的时间点并不准确,因为在事件队列中会被其他异步任务影响甚至直接阻塞,那么在不断重复的绘制中,就有可能会出现卡顿或者忽快忽慢;另一方面,假设我们使用的电脑显示屏刷新率为60帧/秒,也就是大约16.7ms重绘一次,那么即时我们在16.7ms时间内执行了很多次计算和绘制命令,实际上最终呈现出的也只是最后一次结果,就好比对一段很密集的数据进行了隔点采样,轻则浪费性能,重则会在画面呈现时出现跳帧。为了配合显示器刷新,我们可以使用另一个方法——requestAnimationFrame(fn),这是javascript中专门用来绘制逐帧动画的,它会配合显示器的刷新频率进行必要的图像更新,节省不必要的性能浪费。

二. 动画框架

canvas上实现基本的动画,可以遵循一个基本的编程框架:

1 function step(){
2    /**
3    *在每一帧中要执行的逻辑
4    *......
5    */
6    requestAnimationFrame(step);
7 }
8
9 step();//启动执行

你没看错,这就是canvas动画最核心的一段代码,step()函数会在每个绘图周期内重复执行。那么每一帧中需要做哪些工作呢?

我们将canvas想象成一个舞台stage,每一个需要绘制在画布上的元素被称为精灵,无论它们拥有怎样的属性,它们都具备update( )paint( )两个基本方法,前者用于在每一帧中计算更新精灵的参数属性,后者用于将这个精灵对象绘制在画布上。那么step函数在每一帧中所执行的逻辑就变得明朗了,对画布进行必要的擦除,接着更新每一个精灵的状态(可能是位置,颜色等等),然后将其绘制在画布上。

比如现在要在画布上表现一段太阳东升西落得动画,对应的伪代码就是下面这个样子的:

 1 let stage = [];
 2 stage.push(background, tree, cloud, sun);
 3
 4 function step(){
 5    cleanStage();//对画布进行必要擦除
 6    background.update();//更新土地的属性
 7    tree.update();//更新树的属性
 8    cloud.update();//更新云的属性
 9    sun.update();//更新太阳的属性(属性中必然包含着太阳的位置数据)
10    background.paint();//绘制土地
11    tree.paint();//绘制树
12    cloud.paint();//绘制云
13    sun.paint();//绘制太阳
14    requestAnimationFrame(step);
15 }

如果你理解了上面的过程,那么接下来我们对上述代码进行一些抽象和改写:

 1 //建立舞台及添加元素的代码
 2 let stage = [];
 3 stage.push(background, tree, cloud, sun....);
 4
 5 //逐帧动画代码
 6 function step(){
 7    cleanStage();
 8    stage.map(sprite=>{
 9        sprite,update();
10        sprite.paint(ctx);
11    });
12    requestAnimationFrame(step);
13 }

每一个精灵对象都需要实现自己的update( )paint( )方法来描述自己的参数如何变化,以及如何在每一帧中被绘制,被添加进stage数组的都是精灵的实例,一般会将canvas绘图上下文传入paint(context)方法,这样就可以将精灵绘制在指定的画布上。上面的范式只是一个简陋的核心模型,但是已经足够说明canvas动画的本质。

三. 在canvas中模拟碰撞

现在我们就通过一个碰撞仿真的例子来学习canvas动画以及基本的物理仿真分析,示例虽然精简,但包含了canvas动效最核心的精灵动画和碰撞检测主题。为了方便二维向量操作并隐藏各种数学计算的细节,我们直接使用一个已经定义好的Vector2类,其中封装了很多向量的基本操作,都是初高中数学的知识,如果你已经记不太清楚,可以找一些有关的资料复习一下。

3.1定义小球的属性

将每一个小球视为一个精灵,我们需要为它增加一些基本属性以便在每一帧中能够将其绘制出来。通过位置,半径和颜色信息,就能够绘制出小球;通过速度信息,就可以计算小球的位置变化,以便在绘制下一帧时使用。

1 class Ball{
2    constructor(x,y,id){
3        this.pos = new Vector2(x,y);//初始化小球的位置
4        this.id = id;
5        this.color = ‘‘;//绘制的颜色
6        this.r = 20;//小球半径,为方便演示,此处使用给定值
7        this.velocity = null;//小球的速度
8    }
9 }

3.2 生成新的小球

为了增加演示效果,我们使用一个定时函数来随机生成小球,每次生成时为其赋予一个颜色,并给定一个随机的初始速度。

1 //为全局balls数组增加一个新的小球,初始位置为(50,30),
2 function addBall() {
3   let ball = new Ball(50,30,balls.length);
4       ball.color = colorPalette[parseInt(steps / 100,10) % 10];
5       ball.velocity = new Vector2(5*Math.random(), 5 * Math.random());
6       balls.push(ball);
7 }

为了方便起见,我们使用一个全局自增的数值变量,在step中根据条件来执行addBall()方法:

1 if (steps % 100 === 0 && steps < 1500) {
2  addBall();
3 }

step每循环100次(大约1.5秒)就会多生成一个向随机方向发射的小球,且小球的数量不能超过15个。

3.3 帧动画绘制函数step

step函数是动画的核心,我们需要在其中完成重绘背景,添加小球,更新每个小球,绘制小球这些逻辑(由于背景是静态的,示例中并没有将其抽象为精灵动画)。

 1 function step() {
 2    steps++;
 3    //重绘背景
 4    paintBg();
 5    //每隔一定时间增加一个小球
 6    if (steps % 100 === 0 && steps < 1500) {
 7      addBall();
 8    }
 9    //更新每个小球的状态
10    balls = balls.map((ball,index,originArr)=>{
11      ball.update(index,originArr);
12      ball.paint();//描线但不在画布上绘制
13      return ball;
14    });
15    //绘制每个小球位置
16    requestAnimationFrame(step);
17 }

3.4 定义小球的update方法

精灵的绘制方法paint一般都只涉及canvas的基本绘图API,并不复杂,例如本例中,只需要在小球的pos属性记录的位置处绘制一个封闭弧线并填充它就可以了。精灵的update( )方法往往才是最难编写的部分。在这个方法中,需要完成的基本逻辑包括状态更新和碰撞检测。

  • 状态更新

    状态更新一般包括自身状态更新和相对状态更新。自身状态的更新,比如你希望小球在运动过程中颜色会有变化,就属于自身状态的变化,相对状态变化一般指小球相对公共坐标系或某个参照对象而发生的宏观位置变化,比如本例中的小球位置变化。

  • 碰撞检测

    碰撞检测一般包括精灵是否与其他精灵发生碰撞,并需要对碰撞后造成的影响进行仿真。

参考代码:

/*更新状态
由于检测碰撞需要知道其他小球的位置,故此处将小球数组的引用传入
也可以直接以面向对象的方式来定义*/
update(index,balls){
 1    let nextPos;//模拟下一次落点
 2
 3    //1.计算下一次落点
 4    nextPos = this.pos.add(this.velocity.multiply(dt));
 5
 6    //2.判断新位置是否碰触边界,如果是则边界法向的速度反向,假设碰撞过程是无能量损失
 7    if (nextPos.x + this.r > rightBorder || nextPos.x < this.r) {
 8        this.velocity.x = -1 * this.velocity.x;//速度分量反向
 9        nextPos = this.pos;//取消当前帧的位置更新
10    }
11    if (nextPos.y + this.r > bottomBorder || nextPos.y < this.r) {
12        this.velocity.y = -1 * this.velocity.y;
13        nextPos = this.pos;
14    }
15
16    //3.判断是否与其他小球产生碰撞,为避免重复,每个小球只和比自己id更大的小球做检测
17    balls.map(ball=>{
18       if (ball.id > index && this.checkCollision(ball)) {
19           this.handleCollision(ball);
20       }
21       return ball;
22    });
23
24    //4.确认更新位置
25    this.pos = nextPos;
26 }
 

3.5 碰撞检测

规则形状的碰撞检测一般有某些特殊方法,例如平面内的小球,其实只需要判断圆心的距离和两球半径和的大小,就可以知道两球是否碰撞。而当检测物体的外观并不规则时,碰撞检测是成了一个非常复杂的问题,最常用的方法包括外接盒检测,光线投射法和分离轴定理检测,感兴趣的小伙伴可以自行查资料进行学习。本例中的检测方法实际上是外接盒检测法的一种基本情况。

1 //碰撞检测
2 checkCollision(ball){
3   return this.pos.subtract(ball.pos).length() < this.r + ball.r;
4 }

3.6 碰撞仿真

碰撞仿真就是利用物理知识来计算碰撞对于物体造成的影响并修改其对应参数。本例中的碰撞可以抽象为两个质量相等的运动小球的非对心碰撞,且不计能量损失,一般情况下需要使用能量守恒定理和动量守恒定理联立方程进行求解。本例的仿真中,我们先将小球的非对心碰撞简化为对心碰撞,方法是将小球的速度向量分解为沿球心连线方向Vr以及沿圆心连线法向Vn两个分量,然后使用两个小球的Vr来进行对心碰撞的模拟(质量相等的刚体对心碰撞后会互换速度),接着再将碰撞后的速度与小球自己的法向速度Vn进行向量合成即可。

本例的代码中使用了简化的方案,只计算了沿球心连线方向的分量并进行了碰撞模拟,没有对碰撞后的速度进行合成,但对碰撞模拟的效果影响不大。参考代码如下:

1 //处理碰撞
2 handleCollision(ball){
3    let ballToThis = this.pos.subtract(ball.pos).normalize();
4    let thisToBall = ballToThis.negate();
5    this.velocity = ballToThis.multiply(Math.abs(ball.velocity.length()*(ball.velocity.dot(ballToThis) / ball.velocity.length())));
6    ball.velocity = thisToBall.multiply(Math.abs(this.velocity.length()*(this.velocity.dot(ballToThis) / this.velocity.length())));
7 }

碰撞后两个小球的速度都发生了变化,在下一帧更新位置时就会表现出来,效果已经在本节开头展示出了。

完整的示例代码可以参见附件的demo,或访问开头处我的github仓库地址。

四. 下一步

有了这样一个撞球的基本模型和示例,你能做出一个乒乓球小游戏或是撞球小游戏吗?

demo.rar

作者:大史不说话

原文地址:https://www.cnblogs.com/huaweicloud/p/12017439.html

时间: 2024-10-09 14:05:30

带着canvas去流浪系列之八 碰撞【华为云技术分享】的相关文章

【带着canvas去流浪】 (3)绘制饼图

目录 一. 任务说明 二. 重点提示 三. 示例代码 四. hover高亮的实现思路 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文目录 华为云社区地址:[你要的前端打怪升级指南] 一. 任务说明 使用原生canvasAPI绘制饼图(南丁格尔玫瑰).(截图以及数据来自于百度Echarts官方示例库[查看示例链接]). 二. 重点提示 南丁格尔玫瑰图的画法有很多种,Echarts中提供的以半径或面积两种不同模

【带着canvas去流浪(6)】绘制雷达图

目录 一. 任务说明 二. 重点提示 三. 示例代码 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文目录 华为云社区地址:[你要的前端打怪升级指南] 一. 任务说明 使用原生canvasAPI绘制雷达图.(截图以及数据来自于百度Echarts官方示例库[查看示例链接]). 二. 重点提示 雷达图绘制的看起来并不复杂,无非就是一些路径点的连线,其中的难点都在于一些细节. 坐标转换 为了避免在绘制过程中不断根据

无码系列-2-代码架构空想【华为云技术分享】

无码系列-2-代码架构空想 原文地址:https://www.cnblogs.com/huaweicloud/p/12016529.html

【华为云技术分享】【昇腾】【玩转Atlas200DK系列】Atlas 200 DK安装python的hiai库以及opencv

[摘要] Atlas 200 DK安装python的hiai库以及opencv [昇腾]开发板上安装python的hiai库和opencv库 Matrix是已经支持phthon接口了,但是发现目前python的hiai库并没有自动安装,需要自己安装: 话不多说下面是安装步骤: 步骤1. 开发板联网,如果已联网则跳过该步骤: 否则请参考以下链接配置开发板联网(https://bbs.huaweicloud.com/forum/thread-26546-1-1.html ) 接下来需要在开发板上配置

【华为云技术分享】MongoDB经典故障系列一:数据库频繁启动失败怎么办?

MongoDB频繁启动失败怎么办?别慌,华为云数据库给您提供一个小妙招:一看报错日志探究竟,二查目录文件揪根因,三要认真仔细不犯小错,让您轻松搞定启动难题. 此外,华为云数据库特别推出了免费专区活动,MySQL与DDS免费试用2个月,更多活动详情请前往华为云官网——最新活动——新手福利——疫情专区——云数据库. 原文地址:https://www.cnblogs.com/huaweicloud/p/12384889.html

恒天云技术分享系列4 – OpenStack网络攻击与防御

恒天云技术分享系列:http://www.hengtianyun.com/download-show-id-13.html 云主机的网络结构本质上和传统的网络结构一致,区别大概有两点. 1.软网络管理设备(如nova-network,open switch)部分替代硬件网络设备 . 2.多虚拟服务器共享一个宿主机物理网卡(使用Trunk技术). 那么对于云服务器的安全,我们也可以采用传统的网络安全技术去防御.对于云主机,我们同时也需要做好宿主机的防火墙配置,以及安全设置. 1.对于虚拟机进行虚拟

【恒天云技术分享系列11】Sheepdog简介

sheepdog是近几年开源社区新兴的分布式块存储文件系统,采用完全对称的结构,没有类似元数据服务的中心节点.这种架构带来了线性可扩展性,没有单点故障和容易管理的特性.对于磁盘和物理节点,SheepDog实现了动态管理容量以及隐藏硬件错误的特性.对于数据管理,SheepDog利用冗余来实现高可用性,并提供自动恢复数据数据,平衡数据存储的特性.除此之外,sheepdog还有具有零配置.高可靠.智能节点管理.容量线性扩展.虚拟机感知(底层支持冷热迁移和快照.克隆等).支持计算与存储混合架构的特点等.

【恒天云技术分享系列10】OpenStack块存储技术

原文:http://www.hengtianyun.com/download-show-id-101.html 块存储,简单来说就是提供了块设备存储的接口.用户需要把块存储卷附加到虚拟机(或者裸机)上后才可以与其交互.这些卷都是持久的,它们可以被从运行实例上解除或者重新附加而数据保持完整不变.OpenStack 中的实例是不能持久化的,需要挂载 volume,在 volume 中实现持久化.Cinder 就是提供对 volume 实际需要的存储块单元的实现管理功能. 1.单机块存储 1.1 LV

R语言数据分析系列之八

R语言数据分析系列之八 -- by comaple.zhang 再谈多项式回归,本节再次提及多项式回归分析,理解过拟合现象,并深入cross-validation(交叉验证),regularization(正则化)框架,来避免产生过拟合现象,从更加深入的角度探讨理论基础以及基于R如何将理想照进现实. 本节知识点,以及数据集生成 1,        ggplot2进行绘图; 2,        为了拟合更复杂的数据数据集采用sin函数加上服从正太分布的随机白噪声数据; 3,        poly