发生碰撞检测时如何更改圆圈的颜色?

How to change color of circle when collision detection occurs?

据我所知,即使在检测到碰撞时更改颜色后,当它与其他圆圈进行比较并且它们没有发生碰撞时,由于 else 语句,它似乎会恢复为蓝色。那么你将如何解决这个问题,以便当任何圆之间发生碰撞时它变为红色

碰撞检测

this.update = function() {
    for (let i = 0; i < circles.length; i++) {
        if (this !== circles[i] && getDistance(this.x, this.y, circles[i].x, circles[i].y) <= 200 * 200) {
            this.c = 'red';
            circles[i].c = 'red';
            resolveCollision(this, circles[i]);
        } else {
            this.c = 'blue';
            circles[i].c = 'blue';
        }

    }
    //wall deflection
    if (this.x - this.r <= 0 || this.x + this.r >= innerWidth)
        this.v.x *= -1
    if (this.y - this.r <= 0 || this.y + this.r >= innerHeight)
        this.v.y *= -1
    this.x += this.v.x;
    this.y += this.v.y;
    this.draw();
};
//deflection amongst other circles

function resolveCollision(circle, othercircle) {
    const xVelocityDiff = circle.v.x - othercircle.v.x;
    const yVelocityDiff = circle.v.y - othercircle.v.y;
    const xDist = othercircle.x - circle.x;
    const yDist = othercircle.y - circle.y;
    if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {
        const angle = -Math.atan2(othercircle.y - circle.y, othercircle.x - circle.x);
        const m1 = circle.m;
        const m2 = othercircle.m;

        const u1 = rotate(circle.v, angle);
        const u2 = rotate(othercircle.v, 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);

        circle.v.x = vFinal1.x;
        circle.v.y = vFinal1.y;
        othercircle.v.x = vFinal2.x;
        othercircle.v.y = vFinal2.y;
    }

}



const collided = {
  color: 'red',
  get current() {
    return this.color
  },
  set current(clr) {
    if (this.color === 'red') {
      this.color = 'blue'
    } else {
      this.color = 'red'
    }
  }
}
this.update= function(){
  for(let i=0;i<circles.length;i++){
      if(this!==circles[i] && getDistance(this.x,this.y,circles[i].x,circles[i].y)<=200*200){
          this.c=collided.current
          collided.current = this.c
          circles[i].c=collided.current
          resolveCollision(this,circles[i]);
      }
      // ...
    }
}

诀窍是使用 getter 和 setter 来确保永远不会重新应用最近使用的颜色值

首先,拆分 if:

  // psudo-code
  if this is not circles[i], then
    if overlapping, then
      do something
    else
      do something else
  else
    do nothing

然后,修复 if this is not circles[i]:
{x:100,y:100}!={x:100,y:100},所以
id 添加到圈子并比较 ids(我的建议),或者,
比较 .xs 和 .ys(不太需要 - 如果它们相等但不相同怎么办?),或者,
使用 JSON.stringify(a)==JSON.stringify(b).

我会添加 .overlapping 并在循环之前添加另一个循环,将所有 .overlapping 设置为 false,然后,使用修改后的原始循环,我会检查 .overlappingfalse,然后如果发生碰撞,我会将两者的 .overlapping 设置为 true。
另一种方法是创建一个数组来插入重叠圆圈的 .ids 并检查该数组 includes 是否是当前循环项的 .id.

信号量

使用保存圆的碰撞状态的信号量。

因此在您的 Circle.prototype 中会有类似这些功能和属性的东西

Circle.prototype = {
    collided: false,  // when true change color
    draw() {
        ctx.strokeStyle = this.collided ? "red" : "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - 1.5, 0, Math.PI * 2);
        ctx.stroke();
    },

    ...
    ...
    // in update 
    update() {

        // when collision is detected set semaphore
        if (collision) {
            this.collided = true;
        }

    }
}

计数器

或者您可能只想让颜色变化持续一段时间。您可以修改信号量并将其用作计数器。碰撞时将其设置为更改颜色的帧数。

Circle.prototype = {
    collided: 0,
    draw() {
        ctx.strokeStyle = this.collided ? (this.collided--, "red") : "blue";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - 1.5, 0, Math.PI * 2);
        ctx.stroke();
    },

    ...
    ...
    // in update 
    update() {

        // when collision is detected set semaphore
        if (collision) {
            this.collided = 60;  // 1 second at 60FPS
        }

    }
}

例子

这个例子取自我今年早些时候做的另一个回答。

由于有很多代码,我用

突出显示了相关代码
/*= ANSWER CODE ==============================================================
...
=============================================================================*/

该示例使用计数器并在与另一个球或墙碰撞后改变颜色 30 帧。

我没有使用信号量,因为所有的球都会在一秒钟内变红。

canvas.width = innerWidth -20;
canvas.height = innerHeight -20;
mathExt(); // creates some additional math functions
const ctx = canvas.getContext("2d");
const GRAVITY = 0;
const WALL_LOSS = 1;
const BALL_COUNT = 10;  // approx as will not add ball if space can not be found
const MIN_BALL_SIZE = 6;
const MAX_BALL_SIZE = 30;
const VEL_MIN = 1;
const VEL_MAX = 5; 
const MAX_RESOLUTION_CYCLES = 100; // Put too many balls (or too large) in the scene and the 
                                   // number of collisions per frame can grow so large that
                                   // it could block the page.

                                   // If the number of resolution steps is above this value
                                   // simulation will break and balls can pass through lines,
                                   // get trapped, or worse. LOL
const SHOW_COLLISION_TIME = 30;
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;

    /*= ANSWER CODE ==============================================================*/
    this.collided = 0;
    /*============================================================================*/
}
Ball.prototype = {
    update() {
        this.x += this.vx;
        this.y += this.vy;
        this.vy += GRAVITY;
    },
    draw() {

        /*= ANSWER CODE ==============================================================*/
        ctx.strokeStyle = this.collided ? (this.collided--, "#F00") : "#00F";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.r - 1.5, 0, Math.PI * 2);
        ctx.stroke();
       /* ============================================================================*/


    },
    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) {
    
            /*= ANSWER CODE ==============================================================*/

        this.collided = SHOW_COLLISION_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) * WALL_LOSS;
        this.vy = (ny * u - this.vy) * WALL_LOSS;
        this.x -= this.vx * time;
        this.y -= this.vy * time;
    },
    collide(b, time) { // b is second ball

        /*= ANSWER CODE ==============================================================*/

        this.collided = SHOW_COLLISION_TIME;
        b.collided = SHOW_COLLISION_TIME;
        /*============================================================================*/

        const a = this;
        const m1 = a.m;
        const m2 = b.m;
        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;        
        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;
        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, 20, ctx.canvas.width + 10, 5));
    lines.push((new Line(-10, ctx.canvas.height - 2, ctx.canvas.width + 10, ctx.canvas.height - 30)).reverse());
    lines.push((new Line(30, -10, 4, ctx.canvas.height + 10)).reverse());
    lines.push(new Line(ctx.canvas.width - 3, -10, ctx.canvas.width - 30, ctx.canvas.height + 10)); 
    while (bCount--) {
        let tries = 100;
        while (tries--) {
            const dir = Math.rand(0, Math.TAU);
            const vel = Math.rand(VEL_MIN, VEL_MAX);
            const ball = new Ball(
                Math.rand(MAX_BALL_SIZE + 30, canvas.width - MAX_BALL_SIZE - 30), 
                Math.rand(MAX_BALL_SIZE + 30, canvas.height - MAX_BALL_SIZE - 30),
                Math.cos(dir) * vel,
                Math.sin(dir) * vel,
                Math.rand(MIN_BALL_SIZE, MAX_BALL_SIZE),
            );
            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++ < MAX_RESOLUTION_CYCLES) { // 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(BALL_COUNT);
mainLoop();
function mainLoop() {
    ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);

    resolveCollisions();
    for (const b of balls) { b.update() }
    for (const b of balls) { b.draw() }

    ctx.lineWidth = 1;
    ctx.strokeStyle = "#000";
    ctx.beginPath();
    for(const l of lines) { l.draw() }
    ctx.stroke();

    requestAnimationFrame(mainLoop);
}

function mathExt() {
    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;
        }
    }
}
<canvas id="canvas"></canvas>