逐像素碰撞检测弹球



我目前正在使用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);
    }
}

假设"翻转器"由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 );
}

最新更新