书接上文,再来欣赏一下最终要实现的效果
一、实现鼠标交互
要实现鼠标交互就必须通过监听Canvas的mousemove事件,拿到鼠标的实时位置,然后每个小球在每一帧去检测鼠标是否在小球内,判断是否有鼠标与其交互。
-
检测鼠标交互必须要捕获鼠标的两个点,且这两个点都要在小球内,这样就可以通过这两个点的距离和方向,去计算出鼠标对小球作用力的大小,以及力的方向。
-
增加了一个最大外力限制的变量,因为我们用鼠标去hover小球时有可能移动速度会非常快,计算出来的力就会非常大,小球的速度也会非常快,需要限制一下小球速度。
-
小球增加firstHoverX和firstHoverY两个属性,保存检测到的鼠标第一次在小球上的点位置,当检测到第二个点时才触发交互效果,给小球添加外力。
-
小球增加outForceLastTime属性,保存小球最后一次被添加鼠标外力的时间,如果outForceLastTime的时间与下次检测到hover的时间间隔不超过400毫秒,则不触发交互效果,防止小球短时间内连续触发鼠标外力引发的BUG。
-
计算小球获取的外力(也就是小球在水平和垂直方向上速度的变化量)都是调用getOutForce方法获取,计算外力时是通过速度值变化的多少来表示的,因为外力最终是作用在小球速度上的变化,后面实现小球碰撞时也会共用这个方法,只不过会通过传入的isMouse这个标识来额外增加一些判断逻辑。
-
计算小球获取的鼠标外力时,作用力的点位置应该是第一次捕获到hover小球的点位置。
-
计算外力时有一个相对速度的概念,试想一下,当外力与小球运动方向一致且速度相同时,其实小球是没有受到外力的,所以当外力与小球运动方向一致时,就要通过外力速度与小球速度的差值来最终确定外力的大小。
-
当外力速度为0时,说明是鼠标或其他小球处于静止状态,而当前小球与其发送碰撞,此时计算最终外力时给它赋值与小球速度相反的两倍大小即可,让小球回弹回去。
const fontSize = 40; // 文字大小
const textColor = "#E76F5A"; // 文字颜色
+ const maxOutForce = 25; // 最大外力限制
const canvas = document.getElementById("myCanvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext("2d");
const globuleList = []; // 保存所有小球实例
+ let mouseX = 0; // 当前鼠标所在的x位置
+ let mouseY = 0; // 当前鼠标所在的y位置
class Globule {
constructor(x, y, radius, color = "blue", text = "", fontSize = 30, textColor = "red") {
. . .
this.fontSize = fontSize; // 文字大小
this.textColor = textColor; // 文字颜色
+ this.firstHoverX = null; // 检测到鼠标第一次hover到小球的x位置
+ this.firstHoverY = null; // 检测到鼠标第一次hover到小球的y位置
+ this.outForceLastTime = null; // 最后一次鼠标施加外力的时间
}
. . .
// 移动
move() {
this.draw();
+ // 检测鼠标交互
+ this.checkHover();
+
+ this.x += this.vx;
+ this.y += this.vy;
}
+ // 计算获取外力
+ getOutForce(forceX, forceY, forceVX, forceVY, isMouse = false) {
+ const {
+ x,
+ y,
+ vx,
+ vy
+ } = this;
+ let outForceVX = 0;
+ let outForceVY = 0;
+ const dist = Math.hypot(forceX - x, forceY - y);
+ if(dist !== 0) {
+ outForceVX = (Math.abs(x - forceX) / dist) * forceVX;
+ outForceVY = (Math.abs(y - forceY) / dist) * forceVY;
+ }
+ if(forceVX > 0) {
+ if(vx > 0) {
+ outForceVX -= vx;
+ }
+ } else if(forceVX < 0) {
+ if(vx <= 0) {
+ outForceVX -= vx;
+ }
+ } else {
+ outForceVX = -this.vx * 2;
+ }
+ if(forceVY > 0) {
+ if(vy >= 0) {
+ outForceVY -= vy;
+ }
+ } else if(forceVY < 0) {
+ if(vy <= 0) {
+ outForceVY -= vy;
+ }
+ } else {
+ outForceVY = -this.vy * 2;
+ }
+ // 如果是鼠标施加的外力,限制外力大小不能超过一定值
+ if(isMouse) {
+ if(outForceVX > maxOutForce) {
+ outForceVX = maxOutForce;
+ } else if(outForceVX < -maxOutForce) {
+ outForceVX = -maxOutForce;
+ }
+ if(outForceVY > maxOutForce) {
+ outForceVY = maxOutForce;
+ } else if(outForceVY < -maxOutForce) {
+ outForceVY = -maxOutForce;
+ }
+ }
+
+ return {
+ outForceVX,
+ outForceVY
+ };
+ }
+ // 添加外力
+ addOutForce(outForceVX, outForceVY) {
+ this.vx += outForceVX;
+ this.vy += outForceVY;
+ }
+
+ // 检测鼠标是否在小球内
+ checkHover() {
+ const dist = Math.hypot(mouseX - this.x, mouseY - this.y); // 鼠标与小球圆心的距离
+ if(dist <= this.radius) {
+ // 如果之前最后一次鼠标作用在小球的时间与现在间隔不超过400毫秒,则不触发交互效果
+ if(this.outForceLastTime) {
+ const nowTime = new Date().getTime();
+ if(nowTime - this.outForceLastTime < 400) {
+ return;
+ }
+ }
+ if(this.firstHoverX !== null && this.firstHoverY !== null) {
+ const mouseVX = mouseX - this.firstHoverX;
+ const mouseVY = mouseY - this.firstHoverY;
+ const {
+ outForceVX,
+ outForceVY
+ } = this.getOutForce(this.firstHoverX, this.firstHoverY, mouseVX, mouseVY, true);
+ this.addOutForce(outForceVX, outForceVY);
+ this.outForceLastTime = new Date().getTime();
+ this.firstHoverX = null;
+ this.firstHoverY = null;
+ } else {
+ this.firstHoverX = mouseX;
+ this.firstHoverY = mouseY;
+ }
+ }
+ }
}
. . .
// 循环绘制(loop函数中继续调用requestAnimationFrame函数)
requestAnimationFrame(loop);
+ const canvasInfo = canvas.getBoundingClientRect();
+ canvas.addEventListener("mousemove", function(e) {
+ mouseX = e.clientX - canvasInfo.left;
+ mouseY = e.clientY - canvasInfo.top;
+ });
二、实现撞墙效果
实现撞墙效果的原理就是,在每一帧中检测小球的位置是否到达了墙体的边缘,如果到达了,则给小球添加一个与之速度相反的两倍的力,让小球回弹回去。
class Globule {
. . .
// 移动
move(index) {
this.draw();
// 检测鼠标交互
this.checkHover();
+ // 检测和墙体碰撞
+ this.checkCollisionWall();
this.x += this.vx;
this.y += this.vy;
}
. . .
// 检测鼠标是否在小球内
checkHover() {
. . .
}
+ // 检测是否与墙体发生碰撞
+ checkCollisionWall() {
+ const {
+ x,
+ y,
+ radius,
+ vx,
+ vy
+ } = this;
+ let outForceVX = 0;
+ let outForceVY = 0;
+ let isCollision = false;
+ if((x <= radius || x >= canvasWidth - radius) && vx !== 0) {
+ outForceVX = -vx * 2;
+ isCollision = true;
+ }
+ if((y <= radius || y >= canvasHeight - radius) && vy !== 0) {
+ outForceVY = -vy * 2;
+ isCollision = true;
+ }
+ if(isCollision) {
+ this.addOutForce(outForceVX, outForceVY);
+ }
+ }
}
想要学习完整的课程,请点击此处查看
更多个人文章