繁体   English   中英

碰撞检测,鼠标控制

[英]Collision detection, control by mouse

我有一个简单的物理引擎来检测圆圈和桨之间的碰撞,现在我可以设置桨是否由鼠标控制。 但我希望能够通过鼠标控制其中一个圆圈。 在底部 bat[i].update() 将通过该索引控制指定的桨。 但我希望能够通过鼠标控制圆圈来进一步扩展这个引擎。 有什么办法可以做到这一点吗?

 const canvas = document.querySelector('canvas') const ctx = canvas.getContext("2d"); const mouse = { x: 0, y: 0, button: false } function mouseEvents(e) { mouse.x = e.pageX; mouse.y = e.pageY; mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button; } ["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents)); // short cut vars var w = canvas.width; var h = canvas.height; var cw = w / 2; // center var ch = h / 2; const gravity = 0; var balls = [] var bats = [] // constants and helpers const PI2 = Math.PI * 2; const setStyle = (ctx, style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) }; function random(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function distance(x1, y1, x2, y2) { const xDist = x2 - x1; const yDist = y2 - y1; return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)); } // the ball class ball { constructor() { this.r = 25 this.x = random(50, 1500) this.y = random(50, 1500) this.dx = 15 this.dy = 15 this.mass = 1 this.maxSpeed = 15 this.style = { lineWidth: 12, strokeStyle: "green" } } draw(ctx) { setStyle(ctx, this.style); ctx.beginPath(); ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2); ctx.stroke(); } update() { this.dy += gravity; var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy); var x = this.x + this.dx; var y = this.y + this.dy; if (y > canvas.height - this.r) { y = (canvas.height - this.r) - (y - (canvas.height - this.r)); this.dy = -this.dy; } if (y < this.r) { y = this.r - (y - this.r); this.dy = -this.dy; } if (x > canvas.width - this.r) { x = (canvas.width - this.r) - (x - (canvas.width - this.r)); this.dx = -this.dx; } if (x < this.r) { x = this.r - (x - this.r); this.dx = -this.dx; } this.x = x; this.y = y; if (speed > this.maxSpeed) { // if over speed then slow the ball down gradualy var reduceSpeed = this.maxSpeed + (speed - this.maxSpeed) * 0.9; // reduce speed if over max speed this.dx = (this.dx / speed) * reduceSpeed; this.dy = (this.dy / speed) * reduceSpeed; } for (var i = 0; i < balls.length; i++) { if (this === balls[i]) continue if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) { resolveCollision(this, balls[i]) } } } } class player { constructor() { this.r = 50 this.x = random(50, 1500) this.y = random(50, 1500) this.dx = 0.2 this.dy = 0.2 this.mass = 1 this.maxSpeed = 1000 this.style = { lineWidth: 12, strokeStyle: "blue" } } draw(ctx) { setStyle(ctx, this.style); ctx.beginPath(); ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2); ctx.stroke(); } update() { this.dx = mouse.x - this.x; this.dy = mouse.y - this.y; var x = this.x + this.dx; var y = this.y + this.dy; x < this.width / 2 && (x / 2); y < this.height / 2 && (y / 2); x > canvas.width / 2 && (x = canvas.width / 2); y > canvas.height / 2 && (y = canvas.height / 2); this.dx = x - this.x; this.dy = y - this.y; this.x = x; this.y = y; for (var i = 0; i < balls.length; i++) { if (this === balls[i]) continue if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) { resolveCollision(this, balls[i]) } } } } const ballShadow = { r: 50, x: 50, y: 50, dx: 0.2, dy: 0.2, } //bat class bat { constructor(x, y, w, h) { this.x = x this.y = y this.dx = 0 this.dy = 0 this.width = w this.height = h this.style = { lineWidth: 2, strokeStyle: "black", } } draw(ctx) { setStyle(ctx, this.style); ctx.strokeRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height); } update() { this.dx = mouse.x - this.x; this.dy = mouse.y - this.y; var x = this.x + this.dx; var y = this.y + this.dy; x < this.width / 2 && (x = this.width / 2); y < this.height / 2 && (y = this.height / 2); x > canvas.width - this.width / 2 && (x = canvas.width - this.width / 2); y > canvas.height - this.height / 2 && (y = canvas.height - this.height / 2); this.dx = x - this.x; this.dy = y - this.y; this.x = x; this.y = y; } } function doBatBall(bat, ball) { var mirrorX = 1; var mirrorY = 1; const s = ballShadow; // alias sx = ball.x; sy = ball.y; s.dx = ball.dx; s.dy = ball.dy; sx -= s.dx; sy -= s.dy; // get the bat half width height const batW2 = bat.width / 2; const batH2 = bat.height / 2; // and bat size plus radius of ball var batH = batH2 + ball.r; var batW = batW2 + ball.r; // set ball position relative to bats last pos sx -= bat.x; sy -= bat.y; // set ball delta relative to bat s.dx -= bat.dx; s.dy -= bat.dy; // mirror x and or y if needed if (sx < 0) { mirrorX = -1; sx = -sx; s.dx = -s.dx; } if (sy < 0) { mirrorY = -1; sy = -sy; s.dy = -s.dy; } // bat now only has a bottom, right sides and bottom right corner var distY = (batH - sy); // distance from bottom var distX = (batW - sx); // distance from right if (s.dx > 0 && s.dy > 0) { return } // ball moving away so no hit var ballSpeed = Math.sqrt(s.dx * s.dx + s.dy * s.dy) // get ball speed relative to bat // get x location of intercept for bottom of bat var bottomX = sx + (s.dx / s.dy) * distY; // get y location of intercept for right of bat var rightY = sy + (s.dy / s.dx) * distX; // get distance to bottom and right intercepts var distB = Math.hypot(bottomX - sx, batH - sy); var distR = Math.hypot(batW - sx, rightY - sy); var hit = false; if (s.dy < 0 && bottomX <= batW2 && distB <= ballSpeed && distB < distR) { // if hit is on bottom and bottom hit is closest hit = true; sy = batH - s.dy * ((ballSpeed - distB) / ballSpeed); s.dy = -s.dy; } if (!hit && s.dx < 0 && rightY <= batH2 && distR <= ballSpeed && distR <= distB) { // if hit is on right and right hit is closest hit = true; sx = batW - s.dx * ((ballSpeed - distR) / ballSpeed);; s.dx = -s.dx; } if (!hit) { // if no hit may have intercepted the corner. // find the distance that the corner is from the line segment from the balls pos to the next pos const u = ((batW2 - sx) * s.dx + (batH2 - sy) * s.dy) / (ballSpeed * ballSpeed); // get the closest point on the line to the corner var cpx = sx + s.dx * u; var cpy = sy + s.dy * u; // get ball radius squared const radSqr = ball.r * ball.r; // get the distance of that point from the corner squared const dist = (cpx - batW2) * (cpx - batW2) + (cpy - batH2) * (cpy - batH2); // is that distance greater than ball radius if (dist > radSqr) { return } // no hit // solves the triangle from center to closest point on balls trajectory var d = Math.sqrt(radSqr - dist) / ballSpeed; // intercept point is closest to line start cpx -= s.dx * d; cpy -= s.dy * d; // get the distance from the ball current pos to the intercept point d = Math.hypot(cpx - sx, cpy - sy); // is the distance greater than the ball speed then its a miss if (d > ballSpeed) { return } // no hit return sx = cpx; // position of contact sy = cpy; // find the normalised tangent at intercept point const ty = (cpx - batW2) / ball.r; const tx = -(cpy - batH2) / ball.r; // calculate the reflection vector const bsx = s.dx / ballSpeed; // normalise ball speed const bsy = s.dy / ballSpeed; const dot = (bsx * tx + bsy * ty) * 2; // get the distance the ball travels past the intercept d = ballSpeed - d; // the reflected vector is the balls new delta (this delta is normalised) s.dx = (tx * dot - bsx); s.dy = (ty * dot - bsy); // move the ball the remaining distance away from corner sx += s.dx * d; sy += s.dy * d; // set the ball delta to the balls speed s.dx *= ballSpeed s.dy *= ballSpeed hit = true; } // if the ball hit the bat restore absolute position if (hit) { // reverse mirror sx *= mirrorX; s.dx *= mirrorX; sy *= mirrorY; s.dy *= mirrorY; // remove bat relative position sx += bat.x; sy += bat.y; // remove bat relative delta s.dx += bat.dx; s.dy += bat.dy; // set the balls new position and delta ball.x = sx; ball.y = sy; ball.dx = s.dx; ball.dy = s.dy; } } function rotate(velocity, angle) { const rotatedVelocities = { x: velocity.x * Math.cos(angle) + velocity.y * Math.sin(angle), y: -velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle) }; return rotatedVelocities; } function resolveCollision(particle, otherParticle) { const xVelocityDiff = particle.dx - otherParticle.dx; const yVelocityDiff = particle.dy - otherParticle.dy; const xDist = otherParticle.x - particle.x; const yDist = otherParticle.y - particle.y; if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) { const angle = Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x); const m1 = particle.mass; const m2 = otherParticle.mass; const u1 = rotate({ x: particle.dx, y: particle.dy }, angle); const u2 = rotate({ x: otherParticle.dx, y: otherParticle.dy }, angle); const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y }; const v2 = { x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), y: u2.y }; const vFinal1 = rotate(v1, -angle); const vFinal2 = rotate(v2, -angle); particle.dx = vFinal1.x; particle.dy = vFinal1.y; otherParticle.dx = vFinal2.x; otherParticle.dy = vFinal2.y; } } for (let i = 0; i < 10; i++) { balls.push(new ball()) } balls.push(new player()) //x,y,w,h bats.push(new bat((window.innerWidth / 2) - 150, window.innerHeight / 2, 10, 100)) bats.push(new bat((window.innerWidth / 2) + 150, window.innerHeight / 2, 10, 100)) // main update function function update(timer) { if (w !== innerWidth || h !== innerHeight) { cw = (w = canvas.width = innerWidth) / 2; ch = (h = canvas.height = innerHeight) / 2; } ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0, 0, w, h); // move bat and ball for (var i = 0; i < balls.length; i++) { for (var j = 0; j < bats.length; j++) { doBatBall(bats[j], balls[i]) } balls[i].update() balls[i].draw(ctx) } bats.forEach(bat => { //bat.update(); bat.draw(ctx); }) // check for bal bat contact and change ball position and trajectory if needed // draw ball and bat requestAnimationFrame(update); } requestAnimationFrame(update);
 <body> <canvas></canvas> </body>

将您的玩家添加到球阵列中并不是您想要做的。 将该对象分开。 将您的播放器创建为变量,然后在您的循环中绘制并更新播放器

let player = new Player();

function update(timer) {
  ...
player.update();
player.draw(ctx);
  ...
}

 const canvas = document.querySelector('canvas') const ctx = canvas.getContext("2d"); const mouse = { x: 0, y: 0, button: false } function mouseEvents(e) { mouse.x = ex; mouse.y = ey; mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button; } ["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents)); // short cut vars var w = canvas.width; var h = canvas.height; var cw = w / 2; // center var ch = h / 2; const gravity = 0; var balls = [] var bats = [] // constants and helpers const PI2 = Math.PI * 2; const setStyle = (ctx, style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) }; function random(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function distance(x1, y1, x2, y2) { const xDist = x2 - x1; const yDist = y2 - y1; return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)); } // the ball class ball { constructor() { this.r = 25 this.x = random(50, 1500) this.y = random(50, 1500) this.dx = 15 this.dy = 15 this.mass = 1 this.maxSpeed = 15 this.style = { lineWidth: 12, strokeStyle: "green" } } draw(ctx) { setStyle(ctx, this.style); ctx.beginPath(); ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2); ctx.stroke(); } update() { this.dy += gravity; var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy); var x = this.x + this.dx; var y = this.y + this.dy; if (y > canvas.height - this.r) { y = (canvas.height - this.r) - (y - (canvas.height - this.r)); this.dy = -this.dy; } if (y < this.r) { y = this.r - (y - this.r); this.dy = -this.dy; } if (x > canvas.width - this.r) { x = (canvas.width - this.r) - (x - (canvas.width - this.r)); this.dx = -this.dx; } if (x < this.r) { x = this.r - (x - this.r); this.dx = -this.dx; } this.x = x; this.y = y; if (speed > this.maxSpeed) { // if over speed then slow the ball down gradualy var reduceSpeed = this.maxSpeed + (speed - this.maxSpeed) * 0.9; // reduce speed if over max speed this.dx = (this.dx / speed) * reduceSpeed; this.dy = (this.dy / speed) * reduceSpeed; } for (var i = 0; i < balls.length; i++) { if (this === balls[i]) continue if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) { resolveCollision(this, balls[i]) } } } } class Player { constructor() { this.r = 50 this.x = random(50, 1500) this.y = random(50, 1500) this.dx = 0.2 this.dy = 0.2 this.mass = 1 this.maxSpeed = 1000 this.style = { lineWidth: 12, strokeStyle: "blue" } } draw(ctx) { setStyle(ctx, this.style); ctx.beginPath(); ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2); ctx.stroke(); } update() { this.dx = mouse.x - this.x; this.dy = mouse.y - this.y; var x = this.x + this.dx; var y = this.y + this.dy; /*x < this.width / 2 && (x / 2); y < this.height / 2 && (y / 2); x > canvas.width / 2 && (x = canvas.width / 2); y > canvas.height / 2 && (y = canvas.height / 2);*/ this.dx = x - this.x; this.dy = y - this.y; this.x = x; this.y = y; for (var i = 0; i < balls.length; i++) { //if (this === balls[i]) continue if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) { resolveCollision(this, balls[i]) } } } } const ballShadow = { r: 50, x: 50, y: 50, dx: 0.2, dy: 0.2, } //bat class bat { constructor(x, y, w, h) { this.x = x this.y = y this.dx = 0 this.dy = 0 this.width = w this.height = h this.style = { lineWidth: 2, strokeStyle: "black", } } draw(ctx) { setStyle(ctx, this.style); ctx.strokeRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height); } update() { this.dx = mouse.x - this.x; this.dy = mouse.y - this.y; var x = this.x + this.dx; var y = this.y + this.dy; x < this.width / 2 && (x = this.width / 2); y < this.height / 2 && (y = this.height / 2); x > canvas.width - this.width / 2 && (x = canvas.width - this.width / 2); y > canvas.height - this.height / 2 && (y = canvas.height - this.height / 2); this.dx = x - this.x; this.dy = y - this.y; this.x = x; this.y = y; } } function doBatBall(bat, ball) { var mirrorX = 1; var mirrorY = 1; const s = ballShadow; // alias sx = ball.x; sy = ball.y; s.dx = ball.dx; s.dy = ball.dy; sx -= s.dx; sy -= s.dy; // get the bat half width height const batW2 = bat.width / 2; const batH2 = bat.height / 2; // and bat size plus radius of ball var batH = batH2 + ball.r; var batW = batW2 + ball.r; // set ball position relative to bats last pos sx -= bat.x; sy -= bat.y; // set ball delta relative to bat s.dx -= bat.dx; s.dy -= bat.dy; // mirror x and or y if needed if (sx < 0) { mirrorX = -1; sx = -sx; s.dx = -s.dx; } if (sy < 0) { mirrorY = -1; sy = -sy; s.dy = -s.dy; } // bat now only has a bottom, right sides and bottom right corner var distY = (batH - sy); // distance from bottom var distX = (batW - sx); // distance from right if (s.dx > 0 && s.dy > 0) { return } // ball moving away so no hit var ballSpeed = Math.sqrt(s.dx * s.dx + s.dy * s.dy) // get ball speed relative to bat // get x location of intercept for bottom of bat var bottomX = sx + (s.dx / s.dy) * distY; // get y location of intercept for right of bat var rightY = sy + (s.dy / s.dx) * distX; // get distance to bottom and right intercepts var distB = Math.hypot(bottomX - sx, batH - sy); var distR = Math.hypot(batW - sx, rightY - sy); var hit = false; if (s.dy < 0 && bottomX <= batW2 && distB <= ballSpeed && distB < distR) { // if hit is on bottom and bottom hit is closest hit = true; sy = batH - s.dy * ((ballSpeed - distB) / ballSpeed); s.dy = -s.dy; } if (!hit && s.dx < 0 && rightY <= batH2 && distR <= ballSpeed && distR <= distB) { // if hit is on right and right hit is closest hit = true; sx = batW - s.dx * ((ballSpeed - distR) / ballSpeed);; s.dx = -s.dx; } if (!hit) { // if no hit may have intercepted the corner. // find the distance that the corner is from the line segment from the balls pos to the next pos const u = ((batW2 - sx) * s.dx + (batH2 - sy) * s.dy) / (ballSpeed * ballSpeed); // get the closest point on the line to the corner var cpx = sx + s.dx * u; var cpy = sy + s.dy * u; // get ball radius squared const radSqr = ball.r * ball.r; // get the distance of that point from the corner squared const dist = (cpx - batW2) * (cpx - batW2) + (cpy - batH2) * (cpy - batH2); // is that distance greater than ball radius if (dist > radSqr) { return } // no hit // solves the triangle from center to closest point on balls trajectory var d = Math.sqrt(radSqr - dist) / ballSpeed; // intercept point is closest to line start cpx -= s.dx * d; cpy -= s.dy * d; // get the distance from the ball current pos to the intercept point d = Math.hypot(cpx - sx, cpy - sy); // is the distance greater than the ball speed then its a miss if (d > ballSpeed) { return } // no hit return sx = cpx; // position of contact sy = cpy; // find the normalised tangent at intercept point const ty = (cpx - batW2) / ball.r; const tx = -(cpy - batH2) / ball.r; // calculate the reflection vector const bsx = s.dx / ballSpeed; // normalise ball speed const bsy = s.dy / ballSpeed; const dot = (bsx * tx + bsy * ty) * 2; // get the distance the ball travels past the intercept d = ballSpeed - d; // the reflected vector is the balls new delta (this delta is normalised) s.dx = (tx * dot - bsx); s.dy = (ty * dot - bsy); // move the ball the remaining distance away from corner sx += s.dx * d; sy += s.dy * d; // set the ball delta to the balls speed s.dx *= ballSpeed s.dy *= ballSpeed hit = true; } // if the ball hit the bat restore absolute position if (hit) { // reverse mirror sx *= mirrorX; s.dx *= mirrorX; sy *= mirrorY; s.dy *= mirrorY; // remove bat relative position sx += bat.x; sy += bat.y; // remove bat relative delta s.dx += bat.dx; s.dy += bat.dy; // set the balls new position and delta ball.x = sx; ball.y = sy; ball.dx = s.dx; ball.dy = s.dy; } } function rotate(velocity, angle) { const rotatedVelocities = { x: velocity.x * Math.cos(angle) + velocity.y * Math.sin(angle), y: -velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle) }; return rotatedVelocities; } function resolveCollision(particle, otherParticle) { const xVelocityDiff = particle.dx - otherParticle.dx; const yVelocityDiff = particle.dy - otherParticle.dy; const xDist = otherParticle.x - particle.x; const yDist = otherParticle.y - particle.y; if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) { const angle = Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x); const m1 = particle.mass; const m2 = otherParticle.mass; const u1 = rotate({ x: particle.dx, y: particle.dy }, angle); const u2 = rotate({ x: otherParticle.dx, y: otherParticle.dy }, angle); const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y }; const v2 = { x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), y: u2.y }; const vFinal1 = rotate(v1, -angle); const vFinal2 = rotate(v2, -angle); particle.dx = vFinal1.x; particle.dy = vFinal1.y; otherParticle.dx = vFinal2.x; otherParticle.dy = vFinal2.y; } } for (let i = 0; i < 10; i++) { balls.push(new ball()) } let player = new Player(); //x,y,w,h bats.push(new bat((window.innerWidth / 2) - 150, window.innerHeight / 2, 10, 100)) bats.push(new bat((window.innerWidth / 2) + 150, window.innerHeight / 2, 10, 100)) // main update function function update(timer) { if (w !== innerWidth || h !== innerHeight) { cw = (w = canvas.width = innerWidth) / 2; ch = (h = canvas.height = innerHeight) / 2; } ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0, 0, w, h); // move bat and ball for (var i = 1; i < balls.length; i++) { for (var j = 0; j < bats.length; j++) { doBatBall(bats[j], balls[i]) } balls[i].update() balls[i].draw(ctx) } player.update(); player.draw(ctx); bats.forEach(bat => { //bat.update(); bat.draw(ctx); }) // check for bal bat contact and change ball position and trajectory if needed // draw ball and bat requestAnimationFrame(update); } update();
 <canvas></canvas>

请记住,鼠标并不完全位于圆的中心,因为您没有考虑 mousemove 函数中的画布位置。 此外,您还有一些限制玩家对象在整个画布上移动的能力。

编辑:

这就是导致您的播放器无法在整个画布上移动的原因,因此请对其进行注释。

/* x < this.width / 2 && (x / 2);
y < this.height / 2 && (y / 2);
x > canvas.width / 2 && (x = canvas.width / 2);
y > canvas.height / 2 && (y = canvas.height / 2);*/

并且您的碰撞略微偏离,因为在您的播放器类中,您正在检查与this.r * 2距离。 我会把它改成

 for (var i = 0; i < balls.length; i++) {
            //if (this === balls[i]) continue //not really needed
            if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r + balls[i].r) {
                resolveCollision(this, balls[i])
            }

球的速度不能为 0,但玩家可以。 因此,它无法除以零:

        const u = ((batW2 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);

只需添加|| 1 || 1到行尾应该修复它。

 const canvas = document.querySelector('canvas') const ctx = canvas.getContext("2d"); const mouse = { x: 0, y: 0, button: false } function mouseEvents(e) { mouse.x = e.pageX; mouse.y = e.pageY; mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button; } ["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents)); // short cut vars var w = canvas.width; var h = canvas.height; var cw = w / 2; // center var ch = h / 2; const gravity = 0; var balls = [] var bats = [] // constants and helpers const PI2 = Math.PI * 2; const setStyle = (ctx, style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) }; function random(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function distance(x1, y1, x2, y2) { const xDist = x2 - x1; const yDist = y2 - y1; return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2)); } // the ball class ball { constructor() { this.r = 25 this.x = random(50, 1500) this.y = random(50, 1500) this.dx = 15 this.dy = 15 this.mass = 1 this.maxSpeed = 15 this.style = { lineWidth: 12, strokeStyle: "green" } } draw(ctx) { setStyle(ctx, this.style); ctx.beginPath(); ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2); ctx.stroke(); } update() { this.dy += gravity; var speed = Math.sqrt(this.dx * this.dx + this.dy * this.dy); var x = this.x + this.dx; var y = this.y + this.dy; if (y > canvas.height - this.r) { y = (canvas.height - this.r) - (y - (canvas.height - this.r)); this.dy = -this.dy; } if (y < this.r) { y = this.r - (y - this.r); this.dy = -this.dy; } if (x > canvas.width - this.r) { x = (canvas.width - this.r) - (x - (canvas.width - this.r)); this.dx = -this.dx; } if (x < this.r) { x = this.r - (x - this.r); this.dx = -this.dx; } this.x = x; this.y = y; if (speed > this.maxSpeed) { // if over speed then slow the ball down gradualy var reduceSpeed = this.maxSpeed + (speed - this.maxSpeed) * 0.9; // reduce speed if over max speed this.dx = (this.dx / speed) * reduceSpeed; this.dy = (this.dy / speed) * reduceSpeed; } for (var i = 0; i < balls.length; i++) { if (this === balls[i]) continue if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) { resolveCollision(this, balls[i]) } } } } class player { constructor() { this.r = 50 this.x = random(50, 1500) this.y = random(50, 1500) this.dx = 0.2 this.dy = 0.2 this.mass = 1 this.maxSpeed = 1000 this.width = this.r this.height = this.r this.style = { lineWidth: 12, strokeStyle: "blue" } } draw(ctx) { setStyle(ctx, this.style); ctx.beginPath(); ctx.arc(this.x, this.y, this.r - this.style.lineWidth * 0.45, 0, PI2); ctx.stroke(); } update() { this.dx = mouse.x - this.x; this.dy = mouse.y - this.y; var x = this.x + this.dx; var y = this.y + this.dy; /* change */ /* x < this.width / 2 && (x / 2); y < this.height / 2 && (y / 2); x > canvas.width / 2 && (x = canvas.width / 2); y > canvas.height / 2 && (y = canvas.height / 2); */ x < this.width && (x = this.width); y < this.height && (y = this.height); x > canvas.width - this.width && (x = canvas.width - this.width); y > canvas.height - this.height && (y = canvas.height - this.height); /* end change */ this.dx = x - this.x; this.dy = y - this.y; this.x = x; this.y = y; for (var i = 0; i < balls.length; i++) { if (this === balls[i]) continue if (distance(this.x, this.y, balls[i].x, balls[i].y) < this.r * 2) { resolveCollision(this, balls[i]) } } } } const ballShadow = { r: 50, x: 50, y: 50, dx: 0.2, dy: 0.2, } //bat class bat { constructor(x, y, w, h) { this.x = x this.y = y this.dx = 0 this.dy = 0 this.width = w this.height = h this.style = { lineWidth: 2, strokeStyle: "black", } } draw(ctx) { setStyle(ctx, this.style); ctx.strokeRect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height); } update() { this.dx = mouse.x - this.x; this.dy = mouse.y - this.y; var x = this.x + this.dx; var y = this.y + this.dy; x < this.width / 2 && (x = this.width / 2); y < this.height / 2 && (y = this.height / 2); x > canvas.width - this.width / 2 && (x = canvas.width - this.width / 2); y > canvas.height - this.height / 2 && (y = canvas.height - this.height / 2); this.dx = x - this.x; this.dy = y - this.y; this.x = x; this.y = y; } } function doBatBall(bat, ball) { var mirrorX = 1; var mirrorY = 1; const s = ballShadow; // alias sx = ball.x; sy = ball.y; s.dx = ball.dx; s.dy = ball.dy; sx -= s.dx; sy -= s.dy; // get the bat half width height const batW2 = bat.width / 2; const batH2 = bat.height / 2; // and bat size plus radius of ball var batH = batH2 + ball.r; var batW = batW2 + ball.r; // set ball position relative to bats last pos sx -= bat.x; sy -= bat.y; // set ball delta relative to bat s.dx -= bat.dx; s.dy -= bat.dy; // mirror x and or y if needed if (sx < 0) { mirrorX = -1; sx = -sx; s.dx = -s.dx; } if (sy < 0) { mirrorY = -1; sy = -sy; s.dy = -s.dy; } // bat now only has a bottom, right sides and bottom right corner var distY = (batH - sy); // distance from bottom var distX = (batW - sx); // distance from right if (s.dx > 0 && s.dy > 0) { return } // ball moving away so no hit var ballSpeed = Math.sqrt(s.dx * s.dx + s.dy * s.dy) // get ball speed relative to bat // get x location of intercept for bottom of bat var bottomX = sx + (s.dx / s.dy) * distY; // get y location of intercept for right of bat var rightY = sy + (s.dy / s.dx) * distX; // get distance to bottom and right intercepts var distB = Math.hypot(bottomX - sx, batH - sy); var distR = Math.hypot(batW - sx, rightY - sy); var hit = false; if (s.dy < 0 && bottomX <= batW2 && distB <= ballSpeed && distB < distR) { // if hit is on bottom and bottom hit is closest hit = true; sy = batH - s.dy * ((ballSpeed - distB) / ballSpeed); s.dy = -s.dy; } if (!hit && s.dx < 0 && rightY <= batH2 && distR <= ballSpeed && distR <= distB) { // if hit is on right and right hit is closest hit = true; sx = batW - s.dx * ((ballSpeed - distR) / ballSpeed);; s.dx = -s.dx; } if (!hit) { // if no hit may have intercepted the corner. // find the distance that the corner is from the line segment from the balls pos to the next pos /* change */ // const u = ((batW2 - sx) * s.dx + (batH2 - sy) * s.dy) / (ballSpeed * ballSpeed); const u = ((batW2 - sx) * s.dx + (batH2 - sy) * s.dy) / (ballSpeed * ballSpeed) || 1; /* end change */ // get the closest point on the line to the corner var cpx = sx + s.dx * u; var cpy = sy + s.dy * u; // get ball radius squared const radSqr = ball.r * ball.r; // get the distance of that point from the corner squared const dist = (cpx - batW2) * (cpx - batW2) + (cpy - batH2) * (cpy - batH2); // is that distance greater than ball radius if (dist > radSqr) { return } // no hit // solves the triangle from center to closest point on balls trajectory var d = Math.sqrt(radSqr - dist) / ballSpeed; // intercept point is closest to line start cpx -= s.dx * d; cpy -= s.dy * d; // get the distance from the ball current pos to the intercept point d = Math.hypot(cpx - sx, cpy - sy); // is the distance greater than the ball speed then its a miss if (d > ballSpeed) { return } // no hit return sx = cpx; // position of contact sy = cpy; // find the normalised tangent at intercept point const ty = (cpx - batW2) / ball.r; const tx = -(cpy - batH2) / ball.r; // calculate the reflection vector const bsx = s.dx / ballSpeed; // normalise ball speed const bsy = s.dy / ballSpeed; const dot = (bsx * tx + bsy * ty) * 2; // get the distance the ball travels past the intercept d = ballSpeed - d; // the reflected vector is the balls new delta (this delta is normalised) s.dx = (tx * dot - bsx); s.dy = (ty * dot - bsy); // move the ball the remaining distance away from corner sx += s.dx * d; sy += s.dy * d; // set the ball delta to the balls speed s.dx *= ballSpeed s.dy *= ballSpeed hit = true; } // if the ball hit the bat restore absolute position if (hit) { // reverse mirror sx *= mirrorX; s.dx *= mirrorX; sy *= mirrorY; s.dy *= mirrorY; // remove bat relative position sx += bat.x; sy += bat.y; // remove bat relative delta s.dx += bat.dx; s.dy += bat.dy; // set the balls new position and delta ball.x = sx; ball.y = sy; ball.dx = s.dx; ball.dy = s.dy; } } function rotate(velocity, angle) { const rotatedVelocities = { x: velocity.x * Math.cos(angle) + velocity.y * Math.sin(angle), y: -velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle) }; return rotatedVelocities; } function resolveCollision(particle, otherParticle) { const xVelocityDiff = particle.dx - otherParticle.dx; const yVelocityDiff = particle.dy - otherParticle.dy; const xDist = otherParticle.x - particle.x; const yDist = otherParticle.y - particle.y; if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) { const angle = Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x); const m1 = particle.mass; const m2 = otherParticle.mass; const u1 = rotate({ x: particle.dx, y: particle.dy }, angle); const u2 = rotate({ x: otherParticle.dx, y: otherParticle.dy }, angle); const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y }; const v2 = { x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), y: u2.y }; const vFinal1 = rotate(v1, -angle); const vFinal2 = rotate(v2, -angle); particle.dx = vFinal1.x; particle.dy = vFinal1.y; otherParticle.dx = vFinal2.x; otherParticle.dy = vFinal2.y; } } for (let i = 0; i < 10; i++) { balls.push(new ball()) } balls.push(new player()) //x,y,w,h bats.push(new bat((window.innerWidth / 2) - 150, window.innerHeight / 2, 10, 100)) bats.push(new bat((window.innerWidth / 2) + 150, window.innerHeight / 2, 10, 100)) // main update function function update(timer) { if (w !== innerWidth || h !== innerHeight) { cw = (w = canvas.width = innerWidth) / 2; ch = (h = canvas.height = innerHeight) / 2; } ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0, 0, w, h); // move bat and ball for (var i = 0; i < balls.length; i++) { for (var j = 0; j < bats.length; j++) { doBatBall(bats[j], balls[i]) } balls[i].update() balls[i].draw(ctx) } bats.forEach(bat => { //bat.update(); bat.draw(ctx); }) // check for bal bat contact and change ball position and trajectory if needed // draw ball and bat requestAnimationFrame(update); } requestAnimationFrame(update);
 <body> <canvas></canvas> </body>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM