终於来到弹跳球的最後一部分~ 这篇我们主要就是要讲解倾斜面
存在的状况下,程序的撰写方法!
老实说我原本是打算在一篇文内把倾斜面
的范例处理完毕的。
但是因为碰上一些工作上的问题,时间有点不太够用 :(
所以我还是把这部分拆成两篇文来写了(罚跪
也就是说~这一篇是前篇
,我在这篇会先讲解这个案例的大概样貌,还有场景的架设要怎麽处理~
这边就先来看看我构建场景的源码~ 基本上因为这个案例偏复杂,所以我会使用webpack
搭配ESModule
来编写这个案例。
之後完整案例的源码我会贴在我自己的public repo
上面,供各位看官查阅~
关於源码中倾斜面碰撞侦测的原理可以看之前的文喔
// Vector2D 就是我们前面建立的向量类,
// 我这边补了一个 Point2D类,用途是用来产生一个具有x和y property 的座标物件
import { Vector2D, Point2D } from './class';
const CANVAS = {
width: 600,
height: 600,
background: 'gray'
}
const BALL = {
radius: 5,
color: '#333'
}
// 先用阵列描述六面墙壁的端点位置
const WALLS = [
[new Point2D(50, 50), new Point2D(50, 550)],// 左边界
[new Point2D(550, 50), new Point2D(550, 550)],//右边界
[new Point2D(50, 550), new Point2D(550, 550)],//下边界
[new Point2D(125, 150), new Point2D(475, 100)],// 第一斜坡
[new Point2D(75, 250), new Point2D(425, 300)],// 第二斜坡
[new Point2D(125, 450), new Point2D(550, 400)]// 第三斜坡
]
// 这个是球的类,球本身会具有随机性的初速,和向下的加速度,同时他会具备可以更新自身速度和位置的方法
class Ball {
constructor(x, y, color = BALL.color, radius = BALL.radius, randomSpeed = true) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius;
this.gravity = new Vector2D(0, 4);
this.friction = 0.999;
if (randomSpeed) {
this.velocity = new Vector2D(
(Math.random() * this.radius * 2 - radius) * 10,
(Math.random() * this.radius * 2 - radius)
)
}
else {
this.velocity = new Vector2D(0, 0);
}
}
draw(ctx) {
ctx.save()
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
ctx.restore();
}
refreshLocation(dt) {
this.x += this.velocity.x * dt;
this.y += this.velocity.y * dt;
}
refreshSpeed(dt) {
this.velocity.scaleBy(this.friction);
this.velocity.incrementBy(this.gravity.multiply(dt));
}
}
// 这个是用来统合所有墙壁的类
class Boundary {
constructor() {
this.walls = WALLS;
}
draw(ctx) {
this.walls.forEach((o, i) => {
ctx.beginPath();
ctx.moveTo(o[0].x, o[0].y);
ctx.lineTo(o[1].x, o[1].y);
ctx.closePath();
ctx.lineWidth = 5;
ctx.lineJoin = 'round';
ctx.strokeStyle = 'white';
ctx.stroke();
})
}
}
// 这个是主要的入口
class InclinedWallsAndBouncingBallsAnimation {
constructor(ctx) {
this.ctx = ctx;
this.cvs = ctx.canvas;
// balls 是一个"球池"的概念,用来集中放置所有因爲滑鼠点击而产生的球
this.balls = [];
this.frameIsPaused = false;
//入口方法
this.init();
}
init() {
this.time = 0;
// 动态决定canvas大小
this.setCanvasSize();
// 在初始的时候先绘制一次墙壁
this.initBoundary();
// 绑定滑鼠点击事件和document的visibilityChange 事件
this.initEvents();
// 启动动画
this.animate();
}
// 初始化墙壁
initBoundary() {
this.boundary = new Boundary();
this.boundary.draw(this.ctx);
}
// 绑事件
initEvents() {
this.initVisibilityChangeEvent();
this.initClickEvent();
}
//visibilityChange事件
initVisibilityChangeEvent() {
window.addEventListener('visibilitychange', () => {
if (document.visibilityState !== "visible") {
this.frameIsPaused = true;
}
else {
this.frameIsPaused = false;
this.time = performance.now();
}
});
}
// 每按一次滑鼠就会在按下去的座标生成一颗球
initClickEvent() {
this.cvs.addEventListener('click', (e) => {
const rect = e.target.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
this.balls.push(new Ball(mouseX, mouseY))
})
}
animate() {
if (this.frameIsPaused) {
this.animate();
}
const $this = this;
const dt = (performance.now() - this.time) / 100;
this.ctx.clearRect(0, 0, this.cvs.width, this.cvs.height);
this.animateBalls(dt);
this.boundary.draw(this.ctx);
this.time = performance.now();
requestAnimationFrame(this.animate.bind($this));
}
animateBalls(dt) {
//这边就是去遍历过整个球池,把每一颗球都根据其位置/大小/颜色画出来,然後就跟之前的范例一样,接着更新位置和速度
this.balls.forEach((o, i) => {
o.draw(this.ctx);
// 更新位置数据
o.refreshLocation(dt);
// 这段是我用来防止已经飞出画面外的球仍然停留在球池物件中,导致重复计算而效能爆炸,做一个简单的消除
if (o.x > this.cvs.width || o.y > this.cvs.height || o.x < 0) {
this.balls.splice(i, 1);
}
//更新速度数据
o.refreshSpeed(dt);
this.checkBoundary();
})
}
//用来动态设定 canvas大小的方法
setCanvasSize() {
this.cvs.width = CANVAS.width;
this.cvs.height = CANVAS.height;
this.cvs.style.backgroundColor = CANVAS.background;
}
// 这部分就是侦测碰撞
checkBoundary() {
// 先遍历每一面墙壁
this.boundary.walls.forEach((o, i) => {
const vectorAB = new Vector2D(
o[1].x - o[0].x,
o[1].y - o[0].y
)
// 在遍历每一颗球
this.balls.forEach((ball, index) => {
// 这边其实就是我们前面有提到的倾斜面的碰撞侦测
//墙壁端点A到球心的向量
const vectorAToBall = new Vector2D(
ball.x - o[0].x,
ball.y - o[0].y
);
//墙壁端点B到球心的向量
const vectorBToBall = new Vector2D(
ball.x - o[1].x,
ball.y - o[1].y
);
//墙壁端点A到球心的向量映射在墙壁的向量
const vectorAToBallProj = vectorAToBall.project(vectorAB);
//墙壁端点B到球心的向量映射在墙壁的向量
const vectorBToBallProj = vectorBToBall.project(vectorAB);
//向量互减
const dist = vectorAToBall.substract(vectorAToBallProj).length();
if (!dist) return;
// 这个条件就是在讲球离墙壁的距离要低於半径,且球到墙壁两端点的向量映射在墙壁上的长度不能超过墙壁长度(意思就是指球是位於墙壁的两个端点中间)
const collisionDetection =
dist < ball.radius &&
vectorAToBallProj.length() < vectorAB.length() &&
vectorBToBallProj.length() < vectorAB.length();
if (collisionDetection) {
// console.log('boom!!!')
}
})
})
}
}
document.addEventListener('DOMContentLoaded', () => {
let ctx = document.querySelector('canvas').getContext('2d');
let instance = new InclinedWallsAndBouncingBallsAnimation(ctx);
})
测试了一下~ 确认有侦测到碰撞 ! (YA)
(但是因为还没有处理反弹机制,所以球直接穿墙飞出去了XD)
在明天的部分,我们会接着继续把这个案例的reposition
和反射
运算的部分做完! 敬请期待 :D ~
<<: 【Day13】Latch 的生成条件以及如何避免(下)
● 接下来几章都是先以模拟帐户作登入,尚未使用正式证券户帐户登入 如果尚未有永丰金证券帐户的朋友,但...
接着来讲讲泛型的部分.... 简单来说泛型就是传入值、传回值不固定的情况下这时候就可以使用泛型......
感知层 将具有感测与辨识能力的元件嵌入连结上真实的物体里面,进而能够对环境进行监控与感知。 分别有...
继续昨天的歪楼笔记,昨天只有写 webpack-dev-server, 今天来加上一些基本的插件还有...
In the beginning, the only klingeltöne available w...