【问题标题】:Pixel by pixel collision detection pinball逐像素碰撞检测弹球
【发布时间】:2016-09-08 20:19:11
【问题描述】:

我目前正在使用 HTML5 Canvas 和 JavaScript 开发弹球游戏。现在我很难处理逐个像素的碰撞,这是因为脚蹼的根本原因。

现在我的边界框碰撞似乎正在工作

checkCollision(element) {
    if (this.checkCollisionBoundingBox(element)) {
        console.log("colision with the element bounding box");

        if (this.checkCollisionPixelByPixel(element)) {
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

checkCollisionBoundingBox(element) {
    if (this.pos.x < element.pos.x + element.width && this.pos.x + this.width > element.pos.x && this.pos.y < element.pos.y + element.height && this.pos.y + this.height > element.pos.y) {
          return true;
    } else {
          return false;
      }
    }

我尝试了几种逐像素实现的方法,但由于某种原因,它不能完美地工作(在墙壁上、图像上、精灵上等)。我会把它们留在这里:

checkCollisionPixelByPixel(element) {
        var x_left = Math.floor(Math.max(this.pos.x, element.pos.x));
        var x_right = Math.floor(Math.min(this.pos.x + this.width, element.pos.x + element.width));
        var y_top = Math.floor(Math.max(this.pos.y, element.pos.y));
        var y_bottom = Math.floor(Math.min(this.pos.y + this.height, element.pos.y + element.height));

        for (var y = y_top; y < y_bottom; y++) {
            for (var x = x_left; x < x_right; x++) {
                var x_0 = Math.round(x - this.pos.x);
                var y_0 = Math.round(y - this.pos.y);
                var n_pix = y_0 * (this.width * this.total) + (this.width * (this.actual-1)) + x_0; //n pixel to check
                var pix_op = this.imgData.data[4 * n_pix + 3]; //opacity (R G B A)

                var element_x_0 = Math.round(x - element.pos.x);
                var element_y_0 = Math.round(y - element.pos.y);
                var element_n_pix = element_y_0 * (element.width * element.total) + (element.width * (element.actual-1)) + element_x_0; //n pixel to check
                var element_pix_op = element.imgData.data[4 * element_n_pix + 3]; //opacity (R G B A)
                console.log(element_pix_op);
                if (pix_op == 255 && element_pix_op == 255) {

                    console.log("Colision pixel by pixel");
                    /*Debug*/
                    /*console.log("This -> (R:" + this.imgData.data[4 * n_pix] + ", G:" + this.imgData.data[4 * n_pix + 1] + ", B:" + this.imgData.data[4 * n_pix + 2] + ", A:" + pix_op + ")");
                    console.log("Element -> (R:" + element.imgData.data[4 * element_n_pix] + ", G:" + element.imgData.data[4 * element_n_pix + 1] + ", B:" + element.imgData.data[4 * element_n_pix + 2] + ", A:" + element_pix_op + ")");
                    console.log("Collision -> (x:" + x + ", y:" + y +")");
                    console.log("This(Local) -> (x:" + x_0 + ", y:" + y_0+")");
                    console.log("Element(Local) -> (x:" + element_x_0 + ", y:" + element_y_0+")");*/
                    /*ball vector*/
                    var vector = {
                        x: (x_0 - Math.floor(this.imgData.width / 2)),
                        y: -(y_0 - Math.floor(this.imgData.height / 2))
                    };
                    //console.log("ball vector -> ("+vector.x+", "+vector.y+") , Angulo: "+ Math.atan(vector.y/vector.x)* 180/Math.PI);

                     // THIS WAS THE FIRST TRY, IT DIDN'T WORK WHEN THE BALL WAS GOING NORTHEAST AND COLLIDED WITH A WALL. DIDN'T WORK AT ALL WITH SPRITES
                    //this.angle = (Math.atan2(vector.y, vector.x) - Math.PI) * (180 / Math.PI);


                     // THIS WAS THE SECOND ATTEMPT, WORKS WORSE THAN THE FIRST ONE :/
                    //normal vector
                    var normal = {
                        x: (x_0 - (this.imgData.width / 2)),
                        y: -(y_0 - (this.imgData.height / 2))
                    };
                    //Normalizar o vetor
                    var norm = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
                    if (norm != 0) {
                        normal.x = normal.x / norm;
                        normal.y = normal.y / norm;
                    }
                    var n_rad = Math.atan2(normal.y, normal.x);
                    var n_deg = (n_rad + Math.PI) * 180 / Math.PI;
                    console.log("Vetor Normal -> (" + normal.x + ", " + normal.y + ") , Angulo: " + n_deg);
                    //Vetor Velocidade
                    var velocity = {
                        x: Math.cos((this.angle * Math.PI / 180) - Math.PI),
                        y: Math.sin((this.angle * Math.PI / 180) - Math.PI)
                    };
                    console.log("Vetor Velocidade -> (" + velocity.x + ", " + velocity.y + ") , Angulo: " + this.angle);
                    //Vetor Reflexao
                    var ndotv = normal.x * velocity.x + normal.y * velocity.y;
                    var reflection = {
                        x: -2 * ndotv * normal.x + velocity.x,
                        y: -2 * ndotv * normal.y + velocity.y
                    };
                    var r_rad = Math.atan2(reflection.y, reflection.x);
                    var r_deg = (r_rad + Math.PI) * 180 / Math.PI;
                    console.log("Vetor Reflexao -> (" + reflection.x + ", " + reflection.y + ") , Angulo: " + r_deg);

                    this.angle = r_deg;


                    return true;
                }
            }
        }
        return false;
    }
}

球类

class Ball extends Element {
    constructor(img, pos, width, height, n, sound, angle, speed) {
        super(img, pos, width, height, n, sound);
        this.angle = angle; //direction [0:360[
        this.speed = speed;
    }
    move(ctx, cw, ch) {
        var rads = this.angle * Math.PI / 180
        var vx = Math.cos(rads) * this.speed / 60;
        var vy = Math.sin(rads) * this.speed / 60;

        this.pos.x += vx;
        this.pos.y -= vy;

        ctx.clearRect(0, 0, cw, ch);
        this.draw(ctx, 1);
    }
}

【问题讨论】:

  • 这个答案stackoverflow.com/a/36026906/3877726有一个方法,速度非常快,适应各种不规则的精灵形状。它仅限于不会自行弯曲的形状。它需要一点初始化代码,但碰撞测试比任何基于软件的像素碰撞测试都要快很多倍,在性能上可与圆形/盒子碰撞相媲美

标签: javascript html canvas


【解决方案1】:

假设“脚蹼”由 2 条弧线和 2 条线组成,那么在数学上进行碰撞检测会比通过慢得多的像素测试方法快得多。然后你只需要 4 次数学碰撞测试。

即使你的脚蹼比弧线+线复杂一点,数学命中测试也会“足够好”——这意味着在你快速移动的游戏中,用户无法直观地注意到近似的数学结果与像素——完美的结果和两种测试之间的差异根本不会影响游戏玩法。但是像素测试版本将花费更多的时间和资源来完成。 ;-)

前两个圆与圆碰撞测试:

function CirclesColliding(c1,c2){
    var dx=c2.x-c1.x;
    var dy=c2.y-c1.y;
    var rSum=c1.r+c2.r;
    return(dx*dx+dy*dy<=rSum*rSum);
}

然后是两个圆与线段碰撞测试:

// [x0,y0] to [x1,y1] define a line segment
// [cx,cy] is circle centerpoint, cr is circle radius 
function isCircleSegmentColliding(x0,y0,x1,y1,cx,cy,cr){

    // calc delta distance: source point to line start
    var dx=cx-x0;
    var dy=cy-y0;

    // calc delta distance: line start to end
    var dxx=x1-x0;
    var dyy=y1-y0;

    // Calc position on line normalized between 0.00 & 1.00
    // == dot product divided by delta line distances squared
    var t=(dx*dxx+dy*dyy)/(dxx*dxx+dyy*dyy);

    // calc nearest pt on line
    var x=x0+dxx*t;
    var y=y0+dyy*t;

    // clamp results to being on the segment
    if(t<0){x=x0;y=y0;}
    if(t>1){x=x1;y=y1;}

    return( (cx-x)*(cx-x)+(cy-y)*(cy-y) < cr*cr );
}

【讨论】:

  • 这是个好主意,但不幸的是,其中一个要求是采用逐像素碰撞方法来处理构成弹球机的所有类型的对象。对不起,我应该早点提到的!
  • 最后一次再试一次... 但并非所有保险杠等都由线条或弧线组成。数学测试与像素测试一样有效——甚至可能更多考虑影响像素命中测试的抗锯齿。
  • 我怎样才能让它适应精灵?
  • @PedroCaseiro。只需用线条、矩形和/或弧线绑定精灵。 :-) 顺便说一句,这就是有多少物理引擎(包括box2d)可以做到这一点:简化对线条、矩形和圆形的测试。同样,不要过度强调像素完美,因为现实情况是你的玩家在快节奏的游戏中不会识别出一两个像素的差异。此外,请务必在问题的 cmets 中检查 Blindman67 链接解决方案。我没有仔细检查过他的数学,但这个概念看起来很可靠(而且很有创意!)——即使对于形状非常不规则的精灵也是如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-28
  • 1970-01-01
相关资源
最近更新 更多