目录
- 前言
- 先画一个圆
- 完善我们的类
- 小球动起来
- 最简单的碰撞计算,接触墙壁反弹
- 向量类的完善
- 检测两小球之间的碰撞
- 完善碰撞的效果
- 重复计算的问题
- 撞击墙壁定格问题
- 内存问题
- 随机数生成多个小球
- 参考资料
前言
在前端开发里,canvas 是 HTML5 里最炫酷的工具。我们今天就来搞一个这样的梦幻的效果,学习一下 ES6 的类在开发一个完整项目的思路(即 ES5 的构造函数),还有物理碰撞的程序的实现,当然,效果也很酷炫!
完整代码在此处。
先画一个圆
使用“类”这种被广泛应用的面向对象的概念,我们可以更好的整理我们的代码,做出更大的项目。
所以我们先创建一个 画板的类 class Canvas { } ,以便抽象我们之后对 的操作。
然后再向类里添加第一个方法 drawCircle() ,作为我们的测试吧,就是先画一个最简单的元素 --- 圆!
完整代码如下 (可以在 这个编辑器 进行简单调试):- [/code]在代码里,我们定义了一个圆的属性,即 位置 x y 和半径 、 颜色。通过这种井井有条又优雅的方式,我们的目的就达到了!
- [align=center]
[/align] - 这就是一切的基础,一切从这里开始。
- [size=5]完善我们的类[/size]
- 我们直接使用 ball 显然是不够的,小球它们要有自己的思想,我们的 Canvas 类要只负责绘制,所以我们需要重新开辟一个类,叫 Ball 类,来处理它们自己的“思想”。
- 而 canvas 类也需要更多的可扩展性,今天我们是画圆,明天我们想画圈、方块,我们也要考虑到,所以现在,我们要完善一下。
- 完整代码如下,这样就完美了 ~
- [code]
复制代码 图像能画出来,那么下一步就是运动了。这个要复杂了,一下子想不到要怎么弄,所以要一步一步来。
小球动起来
我们想一下,小球动起来,必定需要把画板清空,然后更改位置、绘制,再清空,再更改位置、绘制... 一帧一帧来。
所以,
- 画板需要有一个方法,清空画板 方法
- 计算小球下一帧的位置
- 再封装一个 【一键更新数据】,用于操作更新数据的逻辑,以及记录和返回计算的结果(表示当前一帧整个游戏的宏观状态)
(第三点的这种思想,可以看这个文章)
先实现第一条,这个很好搞,canvas 只需要使用白色画笔,画一个覆盖全画板的矩形即可:
(不过,我们可以不使用纯白,使用 0.4 的透明度,可以一点一点将上一帧给缓缓刷白,效果很好!)- clearDisplay(){ // 清空画布
- this.ctx.fillStyle = 'rgba(255, 255, 255, 0.4)'; // 这个透明度 0.4 是精华,绘制轨迹效果的关键
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- }
复制代码 然后是第二条。
小球如果要运动,必然需要知道要往哪里运动。现在我们引入物理的概念 --- 速度(velocity),这是一个向量值。
而下一帧要去的地方,就是当前的位置,加上当前的速度向量。比如速度是向右 5m/s,那下一秒的位置就是当前位置加上向右 5 米。
这是属于球的个人的“思想”,所以我们写到 Ball 类里面,同时 球 也要加上 速度 这个属性,位置和速度都是向量,都是 x y。
(当然,向量又是一个复杂的个体,所以我们需要再单独开辟一个向量类 Vector )- // 球类
- class Ball {
- constructor(config){
- Object.assign(this,{
- type : 'circle',
- position : new Vector(100, 100), // 位置也是向量
- velocity : new Vector(5, 3), // 当前的速度
- color : 'blue',
- radius : 25,
- },config);
- }
- nextFrameUpdate(){ // 计算下一帧,小球的位置
- return new Ball({
- ...this, // 其他属性保持不变
- position: this.position.add(this.velocity), // 所谓的计算,其实就是根据向量 +1
- });
- }
- }
复制代码 在 canvas 里,x 和 y 的两个正方向如图所示,所以当前小球的速度是向右下:
下面就是我们当前的向量类 Vector :- // 向量(可作为位置 和 速度)
- class Vector {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- }
- add(vector) { // 两个向量相加,就是这样
- return new Vector(this.x + vector.x, this.y + vector.y);
- }
- }
复制代码 然后,就是使用 js 里用烂了的 requestAnimationFrame 让这个画面一帧一帧动起来,它是根据浏览器的性能实时智能控制帧率的,一般是 100帧/s 左右。不熟悉的同学可以看这个 MDN 的介绍 。
完整的代码如下:- [/code][size=5]最简单的碰撞计算,接触墙壁反弹[/size]
- 这个,还几乎用不到物理碰撞算法之类。其实实现这个功能特别简单,只需要检测到小球到达墙壁边界,然后相应的速度正负转化一下即可!
- 代码很简单,很易懂,将 Ball 类里的 nextFrameUpdate 计算下一帧位置 的这个方法添加两个判断即可:
- [code]nextFrameUpdate(displayState){ // 计算下一帧,小球的位置
- // 如果小球左右到达边界,X 速度取反
- if (this.position.x >= displayState.displayEle.canvas.width - this.radius || this.position.x <= this.radius) {
- this.velocity = new Vector(-this.velocity.x, this.velocity.y);
- }
- // 如果小球上下到达边界,Y 速度取反
- if (this.position.y >= displayState.displayEle.canvas.height - this.radius || this.position.y <= this.radius) {
- this.velocity = new Vector(this.velocity.x, -this.velocity.y);
- }
- return new Ball({
- ...this, // 其他属性保持不变
- position: this.position.add(this.velocity),
- });
- }
复制代码 每当 collisions 元素的数量达到 10 个以上,就只保留最后三个元素。
这样,我们就基本完成碰撞的检测和碰撞的效果了 ~ 我们来实验一下效果吧!
完整代码:- [/code][size=5]随机数生成多个小球[/size]
- 现在,我们就可以写一个循环和随机数结合的脚本,生成一大堆个小球,像开头的那个动画一样的效果了。
- [code]dotProduct(vector) { // 数量积
- return this.x * vector.x + this.y * vector.y;
- }
复制代码 最后的效果如下面这个页内框架所示:
参考资料
- https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial
- https://gist.github.com/joshuabradley012/bd2bc96bbe1909ca8555a792d6a36e04
- https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional
- https://eloquentjavascript.net/16_game.html
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |