当我在 HTML 画布中添加新的弹力球时,如何阻止我的球被移动到随机位置?

问题描述 投票:0回答:1

我目前有一个程序,当您点击“添加球”按钮时,就会创建一个新球,并且它们会从墙壁和其他球上弹起。然而,假设您已经有另外两个球,并且点击了“添加球”按钮:这两个旧球将随机创建在其他地方(最初不是的地方)。

问题:每次点击“添加球”按钮时,如何阻止我的球进入随机位置?

function lineMessage(msg) {
    document.querySelector('#myMessage').textContent += msg + '. ';
}

function groupMessage(msg) {
    document.querySelector('#myMessage').innerHTML += msg + '<br/>';
}

const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 550;
const ballCount = document.querySelector('#ball-count');

const gravity = 0;
const wallLoss = 1;
let numBalls = 0;  // approx as will not add ball if space can not be found
const minBallSize = 13;
const maxBallSize = 20;
const velMin = 1;
const velMax = 5; 
const maxResolutionCycles = 100;

function addBall() {
    numBalls++;
    ballCount.innerHTML = numBalls;

    Math.TAU = Math.PI * 2;
    Math.rand = (min, max) => Math.random() * (max - min) + min;
    Math.randI = (min, max) => Math.random() * (max - min) + min | 0; // only for positive numbers 32bit signed int
    Math.randItem = arr => arr[Math.random() * arr.length | 0]; // only for arrays with length < 2 ** 31 - 1
    // contact points of two circles radius r1, r2 moving along two lines (a,e)-(b,f) and (c,g)-(d,h) [where (,) is coord (x,y)]
    Math.circlesInterceptUnitTime = (a, e, b, f, c, g, d, h, r1, r2) => { // args (x1, y1, x2, y2, x3, y3, x4, y4, r1, r2)
        const A = a * a, B = b * b, C = c * c, D = d * d;
        const E = e * e, F = f * f, G = g * g, H = h * h;
        var R = (r1 + r2) ** 2;
        const AA = A + B + C + F + G + H + D + E + b * c + c * b + f * g + g * f + 2 * (a * d - a * b - a * c - b * d - c * d - e * f + e * h - e * g - f * h - g * h);
        const BB = 2 * (-A + a * b + 2 * a * c - a * d - c * b - C + c * d - E + e * f + 2 * e * g - e * h - g * f - G + g * h);
        const CC = A - 2 * a * c + C + E - 2 * e * g + G - R;
        return Math.quadRoots(AA, BB, CC);
    }  

    Math.quadRoots = (a, b, c) => { // find roots for quadratic
        if (Math.abs(a) < 1e-6) {
            return b != 0 ? [-c / b] : [] 
        }

        b /= a;
        var d = b * b - 4 * (c / a);

        if (d > 0) {
            d = d ** 0.5;
            return  [0.5 * (-b + d), 0.5 * (-b - d)]
        }

        return d === 0 ? [0.5 * -b] : [];
    }

    Math.interceptLineBallTime = (x, y, vx, vy, x1, y1, x2, y2, r) => {
        const xx = x2 - x1;
        const yy = y2 - y1;
        const d = vx * yy - vy * xx;

        if (d > 0) {  // only if moving towards the line
            const dd = r / (xx * xx + yy * yy) ** 0.5;
            const nx = xx * dd;
            const ny = yy * dd;
            return (xx * (y - (y1 + nx)) - yy * (x - (x1 - ny))) / d;
        }
    }

    const balls = [];
    const lines = [];

    function Line(x1, y1, x2, y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    Line.prototype = {
        draw() {
            ctx.moveTo(this.x1, this.y1);
            ctx.lineTo(this.x2, this.y2);
        },
        reverse() {
            const x = this.x1;
            const y = this.y1;
            this.x1 = this.x2;
            this.y1 = this.y2;
            this.x2 = x;
            this.y2 = y;
            return this;
        }
    }
        
    function Ball(x, y, vx, vy, r = 45, m = 4 / 3 * Math.PI * (r ** 3)) {
        this.r = r;
        this.m = m;
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
    }

    Ball.prototype = {
        update() {
            this.x += this.vx;
            this.y += this.vy;
            this.vy += gravity;
        },
        draw() {
            ctx.moveTo(this.x + this.r, this.y);
            ctx.arc(this.x, this.y, this.r, 0, Math.TAU);
        },
        interceptLineTime(l, time) {
            const u = Math.interceptLineBallTime(this.x, this.y, this.vx, this.vy, l.x1, l.y1, l.x2, l.y2, this.r);

            if (u >= time && u <= 1) {
                return u;
            }
        },
        checkBallBallTime(t, minTime) {
            return t > minTime && t <= 1;
        },
        interceptBallTime(b, time) {
            const x = this.x - b.x;
            const y = this.y - b.y;
            const d = (x * x + y * y) ** 0.5;

            if (d > this.r + b.r) {
                const times = Math.circlesInterceptUnitTime(
                    this.x, this.y, 
                    this.x + this.vx, this.y + this.vy, 
                    b.x, b.y,
                    b.x + b.vx, b.y + b.vy, 
                    this.r, b.r
                )

                if (times.length) {
                    if (times.length === 1) {
                        if (this.checkBallBallTime(times[0], time)) {
                            return times[0]
                        }

                        return;
                    }

                    if (times[0] <= times[1]) {
                        if (this.checkBallBallTime(times[0], time)) {
                            return times[0]
                        }

                        if (this.checkBallBallTime(times[1], time)) {
                            return times[1]
                        }

                        return
                    }

                    if (this.checkBallBallTime(times[1], time)) { 
                        return times[1]
                    }      

                    if (this.checkBallBallTime(times[0], time)) {
                        return times[0]
                    }
                }
            }
        },
        collideLine(l, time) {
            const x1 = l.x2 - l.x1;
            const y1 = l.y2 - l.y1;
            const d = (x1 * x1 + y1 * y1) ** 0.5;
            const nx = x1 / d;
            const ny = y1 / d;            
            const u = (this.vx  * nx + this.vy  * ny) * 2;
            this.x += this.vx * time;   
            this.y += this.vy * time;   
            this.vx = (nx * u - this.vx) * wallLoss;
            this.vy = (ny * u - this.vy) * wallLoss;
            this.x -= this.vx * time;
            this.y -= this.vy * time;
        },
        collide(b, time) {
            const a = this;
            const m1 = a.m;
            const m2 = b.m;
            const x = a.x - b.x
            const y = a.y - b.y  
            const d = (x * x + y * y);
            const u1 = (a.vx * x + a.vy * y) / d
            const u2 = (x * a.vy - y * a.vx ) / d
            const u3 = (b.vx * x + b.vy * y) / d
            const u4 = (x * b.vy - y * b.vx ) / d
            const mm = m1 + m2;
            const vu3 = (m1 - m2) / mm * u1 + (2 * m2) / mm * u3;
            const vu1 = (m2 - m1) / mm * u3 + (2 * m1) / mm * u1;
            a.x = a.x + a.vx * time;
            a.y = a.y + a.vy * time;
            b.x = b.x + b.vx * time;
            b.y = b.y + b.vy * time;
            b.vx = x * vu1 - y * u4;
            b.vy = y * vu1 + x * u4;
            a.vx = x * vu3 - y * u2;
            a.vy = y * vu3 + x * u2;
            a.x = a.x - a.vx * time;
            a.y = a.y - a.vy * time;
            b.x = b.x - b.vx * time;
            b.y = b.y - b.vy * time;
        },
        doesOverlap(ball) {
            const x = this.x - ball.x;
            const y = this.y - ball.y;
            return  (this.r + ball.r) > ((x * x + y * y) ** 0.5);  
        }       
    }

    function canAdd(ball) {
        for (const b of balls) {
            if (ball.doesOverlap(b)) {
                return false
            }
        }

        return true;
    }

    function create(bCount) {
        lines.push(new Line(-10, 10, ctx.canvas.width + 10, 5));
        lines.push((new Line(-10, ctx.canvas.height - 2, ctx.canvas.width + 10, ctx.canvas.height - 10)).reverse());
        lines.push((new Line(10, -10, 4, ctx.canvas.height + 10)).reverse());
        lines.push(new Line(ctx.canvas.width - 3, -10, ctx.canvas.width - 10, ctx.canvas.height + 10)); 

        while (bCount--) {
            let tries = 100;
            debugger

            while (tries--) {
                const dir = Math.rand(0, Math.TAU);
                const vel = Math.rand(velMin, velMax);
                const ball = new Ball(
                    Math.rand(maxBallSize + 10, canvas.width - maxBallSize - 10), 
                    Math.rand(maxBallSize + 10, canvas.height - maxBallSize - 10),
                    Math.cos(dir) * vel,
                    Math.sin(dir) * vel,
                    Math.rand(minBallSize, maxBallSize),
                )

                if (canAdd(ball)) {
                    balls.push(ball);
                    break;
                }
            }
        }
    }

    function resolveCollisions() {
        var minTime = 0, minObj, minBall, resolving = true, idx = 0, idx1, after = 0, e = 0;
        
        while (resolving && e++ < maxResolutionCycles) { // too main ball may create very lone resolution cycle. e limits this
            resolving = false;
            minObj = undefined;
            minBall = undefined;
            minTime = 1;
            idx = 0;

            for(const b of balls) {
                idx1 = idx + 1;
                while (idx1 < balls.length) {
                    const b1 = balls[idx1++];
                    const time = b.interceptBallTime(b1, after);

                    if (time !== undefined) {
                        if (time <= minTime) {
                            minTime = time;
                            minObj = b1;
                            minBall = b;
                            resolving = true;
                        }
                    }
                }

                for (const l of lines) {
                    const time = b.interceptLineTime(l, after);
                    if (time !== undefined) {
                        if (time <= minTime) {
                            minTime = time;
                            minObj = l;
                            minBall = b;
                            resolving = true;
                        }
                    }
                }

                idx++;
            }

            if (resolving) {
                if (minObj instanceof Ball) {
                    minBall.collide(minObj, minTime);
                } else {
                    minBall.collideLine(minObj, minTime);
                }

                after = minTime;
            }
        }
    }

    create(numBalls);
    mainLoop();

    function mainLoop() {
        ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
        resolveCollisions();

        for (const b of balls) {
            b.update()
        }

        ctx.strokeStyle = "#000";
        ctx.beginPath();

        for (const b of balls) {
            b.draw()
        }

        for (const l of lines) {
            l.draw()
        }

        ctx.stroke();
        requestAnimationFrame(mainLoop);
    }
}
#canvas {
    width: 1000px;
    height: 550px
}

#myConsole {
    background-color: black;
    color: white;
    min-height: 100px;
}
<!DOCTYPE html>
<html lang="en">

<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE-edge">
        <meta name="viewport", content="width=device-width, initial-scale=1.0">
        <meta name="author" content="Christian Davis">
        <link rel="stylesheet" href="styles.css">

        <title>Bouncy Balls</title>
    </head>

    <body>
        <button onclick="addBall()">Add Ball</button><br>
        <div>Ball Count: <span id="ball-count">0</span></div>
        <canvas id="canvas"></canvas>
        <p id="myConsole">&gt;&nbsp;<span id="myMessage"></span></p>

        <script src="app.js"></script>
    </body>
</html>

javascript html5-canvas
1个回答
0
投票

您的代码有多个问题。主要问题是您已将所有内容放入

addBall
函数中。例如,该函数内定义的数组
balls
,因此每次按下添加球按钮时,您都会扔掉所有旧球并创建具有新随机位置的新球。

我做的第一件事就是将除了用于添加球的代码之外的所有内容移出

addBall
。使用这个新代码,我删除了函数
create
,但将函数主体保留在
addBall
内。现在看起来像这样:

function addBall() {
  numBalls++;
  ballCount.innerHTML = numBalls;

  lines.push(new Line(-10, 10, ctx.canvas.width + 10, 5));
  lines.push(new Line(-10, ctx.canvas.height - 2, ctx.canvas.width + 10, ctx.canvas.height - 10).reverse());
  lines.push(new Line(10, -10, 4, ctx.canvas.height + 10).reverse());
  lines.push(new Line(ctx.canvas.width - 3, -10, ctx.canvas.width - 10, ctx.canvas.height + 10));

  let tries = 100;

  while (tries--) {
    const dir = rand(0, TAU);
    const vel = rand(velMin, velMax);
    const ball = new Ball(
      rand(maxBallSize + 10, canvas.width - maxBallSize - 10),
      rand(maxBallSize + 10, canvas.height - maxBallSize - 10),
      Math.cos(dir) * vel,
      Math.sin(dir) * vel,
      rand(minBallSize, maxBallSize)
    );

    if (canAdd(ball)) {
      balls.push(ball);
      break;
    }
  }
}

我还从

mainLoop();
函数内部删除了对
addBall
的调用,否则该函数将为每个球调用一次,但由于该函数处理所有球,因此每个添加的球都会越来越快球。

我还将旧的原型定义类转换为 ES6 语法,将添加的函数移至 Math 至独立函数。我知道这是您自己的代码,但向内置对象添加额外的函数被认为是非常糟糕的做法,除非它是为了在当前引擎中未实现的填充函数。

由于您尝试将功能移至

addBall
之外但失败了,我将向您概述如何重构代码。我没有对代码进行任何其他分析,只是复制了您的代码,所以还有一些需要改进的地方。

const TAU = Math.PI * 2;

const balls = [];
const lines = [];

function rand(min, max) {}
function circlesInterceptUnitTime(a, e, b, f, c, g, d, h, r1, r2) {}
function quadRoots(a, b, c) {}
function interceptLineBallTime(x, y, vx, vy, x1, y1, x2, y2, r) {}
function lineMessage(msg) {}
function groupMessage(msg) {}

class Ball {
  constructor(x, y, vx, vy, r = 45, m = (4 / 3) * Math.PI * r ** 3) {}
  update() {}
  draw() {}
  interceptLineTime(l, time) {}
  checkBallBallTime(t, minTime) {}
  interceptBallTime(b, time) {}
  collideLine(l, time) {}
  collide(b, time) {}
  doesOverlap(ball) {}
}

class Line {
  constructor(x1, y1, x2, y2) {}
  draw() {}
  reverse() {}
}

function resolveCollisions() {}
function mainLoop() {}   
function canAdd(ball) {}

const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1000;
canvas.height = 550;
const ballCount = document.querySelector('#ball-count');

const gravity = 0;
const wallLoss = 1;
let numBalls = 0; 
const minBallSize = 13;
const maxBallSize = 20;
const velMin = 1;
const velMax = 5;
const maxResolutionCycles = 100;

function addBall() {}

mainLoop(); // Only one call to mainLoop
© www.soinside.com 2019 - 2024. All rights reserved.