【问题标题】:Collision detection, control by mouse碰撞检测,鼠标控制
【发布时间】:2021-07-24 18:06:45
【问题描述】:

我有一个简单的物理引擎来检测圆圈和桨之间的碰撞,现在我可以设置桨是否由鼠标控制。但我希望能够通过鼠标控制其中一个圆圈。在底部 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
    s.x = ball.x;
    s.y = ball.y;
    s.dx = ball.dx;
    s.dy = ball.dy;
    s.x -= s.dx;
    s.y -= 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
    s.x -= bat.x;
    s.y -= bat.y;

    // set ball delta relative to bat
    s.dx -= bat.dx;
    s.dy -= bat.dy;

    // mirror x and or y if needed
    if (s.x < 0) {
        mirrorX = -1;
        s.x = -s.x;
        s.dx = -s.dx;
    }
    if (s.y < 0) {
        mirrorY = -1;
        s.y = -s.y;
        s.dy = -s.dy;
    }


    // bat now only has a bottom, right sides and bottom right corner
    var distY = (batH - s.y); // distance from bottom 
    var distX = (batW - s.x); // 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 = s.x + (s.dx / s.dy) * distY;

    // get y location of intercept for right of bat
    var rightY = s.y + (s.dy / s.dx) * distX;

    // get distance to bottom and right intercepts
    var distB = Math.hypot(bottomX - s.x, batH - s.y);
    var distR = Math.hypot(batW - s.x, rightY - s.y);
    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;
        s.y = 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;
        s.x = 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 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);

        // get the closest point on the line to the corner
        var cpx = s.x + s.dx * u;
        var cpy = s.y + 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 - s.x, cpy - s.y);

        // is the distance greater than the ball speed then its a miss
        if (d > ballSpeed) { return } // no hit return

        s.x = cpx; // position of contact
        s.y = 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
        s.x += s.dx * d;
        s.y += 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
        s.x *= mirrorX;
        s.dx *= mirrorX;
        s.y *= mirrorY;
        s.dy *= mirrorY;

        // remove bat relative position
        s.x += bat.x;
        s.y += bat.y;

        // remove bat relative delta
        s.dx += bat.dx;
        s.dy += bat.dy;

        // set the balls new position and delta
        ball.x = s.x;
        ball.y = s.y;
        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>

【问题讨论】:

    标签: javascript html5-canvas collision-detection game-physics


    【解决方案1】:

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

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

    只需在行尾添加 || 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
        s.x = ball.x;
        s.y = ball.y;
        s.dx = ball.dx;
        s.dy = ball.dy;
        s.x -= s.dx;
        s.y -= 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
        s.x -= bat.x;
        s.y -= bat.y;
    
        // set ball delta relative to bat
        s.dx -= bat.dx;
        s.dy -= bat.dy;
    
        // mirror x and or y if needed
        if (s.x < 0) {
            mirrorX = -1;
            s.x = -s.x;
            s.dx = -s.dx;
        }
        if (s.y < 0) {
            mirrorY = -1;
            s.y = -s.y;
            s.dy = -s.dy;
        }
    
    
        // bat now only has a bottom, right sides and bottom right corner
        var distY = (batH - s.y); // distance from bottom 
        var distX = (batW - s.x); // 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 = s.x + (s.dx / s.dy) * distY;
    
        // get y location of intercept for right of bat
        var rightY = s.y + (s.dy / s.dx) * distX;
    
        // get distance to bottom and right intercepts
        var distB = Math.hypot(bottomX - s.x, batH - s.y);
        var distR = Math.hypot(batW - s.x, rightY - s.y);
        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;
            s.y = 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;
            s.x = 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 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);
            const u = ((batW2 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed) || 1;
    /* end change */
            // get the closest point on the line to the corner
            var cpx = s.x + s.dx * u;
            var cpy = s.y + 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 - s.x, cpy - s.y);
    
            // is the distance greater than the ball speed then its a miss
            if (d > ballSpeed) { return } // no hit return
    
            s.x = cpx; // position of contact
            s.y = 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
            s.x += s.dx * d;
            s.y += 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
            s.x *= mirrorX;
            s.dx *= mirrorX;
            s.y *= mirrorY;
            s.dy *= mirrorY;
    
            // remove bat relative position
            s.x += bat.x;
            s.y += bat.y;
    
            // remove bat relative delta
            s.dx += bat.dx;
            s.dy += bat.dy;
    
            // set the balls new position and delta
            ball.x = s.x;
            ball.y = s.y;
            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>

    【讨论】:

      【解决方案2】:

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

      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 = e.x;
          mouse.y = e.y;
          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
          s.x = ball.x;
          s.y = ball.y;
          s.dx = ball.dx;
          s.dy = ball.dy;
          s.x -= s.dx;
          s.y -= 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
          s.x -= bat.x;
          s.y -= bat.y;
      
          // set ball delta relative to bat
          s.dx -= bat.dx;
          s.dy -= bat.dy;
      
          // mirror x and or y if needed
          if (s.x < 0) {
              mirrorX = -1;
              s.x = -s.x;
              s.dx = -s.dx;
          }
          if (s.y < 0) {
              mirrorY = -1;
              s.y = -s.y;
              s.dy = -s.dy;
          }
      
      
          // bat now only has a bottom, right sides and bottom right corner
          var distY = (batH - s.y); // distance from bottom 
          var distX = (batW - s.x); // 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 = s.x + (s.dx / s.dy) * distY;
      
          // get y location of intercept for right of bat
          var rightY = s.y + (s.dy / s.dx) * distX;
      
          // get distance to bottom and right intercepts
          var distB = Math.hypot(bottomX - s.x, batH - s.y);
          var distR = Math.hypot(batW - s.x, rightY - s.y);
          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;
              s.y = 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;
              s.x = 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 - s.x) * s.dx + (batH2 - s.y) * s.dy) / (ballSpeed * ballSpeed);
      
              // get the closest point on the line to the corner
              var cpx = s.x + s.dx * u;
              var cpy = s.y + 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 - s.x, cpy - s.y);
      
              // is the distance greater than the ball speed then its a miss
              if (d > ballSpeed) { return } // no hit return
      
              s.x = cpx; // position of contact
              s.y = 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
              s.x += s.dx * d;
              s.y += 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
              s.x *= mirrorX;
              s.dx *= mirrorX;
              s.y *= mirrorY;
              s.dy *= mirrorY;
      
              // remove bat relative position
              s.x += bat.x;
              s.y += bat.y;
      
              // remove bat relative delta
              s.dx += bat.dx;
              s.dy += bat.dy;
      
              // set the balls new position and delta
              ball.x = s.x;
              ball.y = s.y;
              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();
       &lt;canvas&gt;&lt;/canvas&gt;

      请记住,鼠标并不完全位于圆的中心,因为您没有考虑 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])
                  }
      

      【讨论】:

      • 是的,感谢您提供额外的碰撞评论,我意识到半径计算有问题。
      • 没有问题。祝你游戏好运。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-04
      • 2011-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多