带碰撞检测功能的 HTML Canvas 和 JavaScript 旋转对象

Sam*_* D. 0 html javascript canvas detection collision

我正在使用 JavaScript 和 HTML Canvas 创建游戏。这是一款多人 2D 游戏,其中的坦克试图互相攻击。坦克可以移动也可以旋转。如何计算旋转矩形物体的碰撞检测?我知道,我可以让它们变成方形并使用圆形检测,但是当坦克撞到墙上时它看起来非常混乱。感谢所有试图提供帮助的人:)

Bli*_*n67 6

将命中点移动到本地空间

首先替代

有很多方法可以做到。最简单的方法。当您计算点和线之间的叉积时,如果该点位于线的右侧,则为负,如果在左侧,则为正。如果您依次进行四个边中的每一个,并且它们都是相同的符号,则该点必须在里面。

得到一条线和一个点的叉积

//x1,y1,x2,y2   is a line
// px,py is a point
// first move line and point relative to the origin
// so that the line and point is a vector
px -= x1;
py -= y1;
x2 -= x1;
y2 -= y1;
var cross = x2 * py - y2 * px; 
if(cross < 0){ 
     // point left of line
}else if(cross > 0) {
    // point right of line
}else {
    // point on the line
}
Run Code Online (Sandbox Code Playgroud)

一种更快的方式。

但是对于每个对象和每个子弹来说,这是很多数学运算。

最好的方法是将子弹转换为坦克局部坐标系,然后它只是测试边界,左,右,上,下的简单问题。

为此,您需要反转坦克变换矩阵。不幸的是,简单的方法目前仍然落后于浏览器标志/前缀,因此您需要在 javascript 中创建和操作转换。(在ctx.getTransform()全面实现并填补 canvas 2d API 中非常需要的性能漏洞之前应该不会太久)

如果 ctx.getTransform 可用

所以你在 x,y 处有一个坦克并旋转了 r 并且你用

ctx.translate(x,y);
ctx.rotate(r);
// render the tank
ctx.fillRect(-20,-10,40,20); // rotated about center
Run Code Online (Sandbox Code Playgroud)

变换包含我们进行计算所需的一切,我们需要做的就是反转它,然后将项目符号与反转矩阵相乘

var tankInvMatrix = ctx.getTransform().invertSelf(); // get the inverted matrix
Run Code Online (Sandbox Code Playgroud)

子弹在 bx,因此创建一个DOMPoint

var bullet = new DOMPoint(bx,by);
Run Code Online (Sandbox Code Playgroud)

然后对于每个坦克用DOMMatrix.transformPoint变换子弹

var relBullet = tankInvMatrix.transformPoint(bullet); // transform the point 
                                                      // returning the bullet 
                                                      // relative to the tank
Run Code Online (Sandbox Code Playgroud)

现在只需在坦克局部坐标空间中进行测试

if(relBullet.x > -20 && relBullet.x < 20 && relBullet.x > -10 && relBullet.x < 10){
      /// bullet has hit the tank
}
Run Code Online (Sandbox Code Playgroud)

Javascript 方式

好吧,直到成为常态之前,您必须走很长的路。使用相同的 x,y,r 用于坦克,bx,by 用于子弹。

// create a vector aligned to the tanks direction
var xdx = Math.cos(r);
var xdy = Math.sin(r);

// set the 2D API to the tank location and rotation
ctx.setTransform(xdx,xdy,-xdy,xdx,x,y);  // create the transform for the tank

// draw the tank
ctx.fillRect(-20,-10,40,20); // rotated about center

// create inverted matrix for the tank 
// Only invert the tank matrix once per frame

var d =  xdx * xdx - xdy * -xdy;
var xIx  = xdx / d;
var xIy  = -xdy / d;
// I am skipping c,d of the matrix as it is perpendicular to a,b
// thus c = -b and d = a
var ix = (-xdy * y - xdx * x) / d;
var iy = -(xdx * y - xdy * x) / d;

// For each bullet per tank
// multiply the bullet with the inverted tank matrix
// bullet local x & y
var blx = bx * xIx - by * xIy + ix;
var bly = bx * xIy + by * xIx + iy;

// and you are done.
if(blx > -20 && blx < 20 && bly > -10 && bly < 10){
      // tank and bullet are one Kaaboommmm 
}
Run Code Online (Sandbox Code Playgroud)

测试以确保它有效

太多的底片,xdx,xdy 等让我无法查看我是否正确(结果我在行列式中输入了错误的符号)所以这里有一个快速演示来展示它的实际效果。

使用鼠标在坦克身上移动,它会显示它被击中为红色。您可以轻松地将其扩展以撞击坦克的运动部件。您只需要对炮塔进行逆变换即可在局部空间中获取子弹来进行测试。

更新

添加代码以阻止坦克作为交叉画布边缘在视觉上弹出和弹出。这是通过OFFSET在显示时从每个罐中减去 1来完成的。在通过添加OFFSET到测试坐标进行命中测试时,必须将此偏移量考虑在内。

//x1,y1,x2,y2   is a line
// px,py is a point
// first move line and point relative to the origin
// so that the line and point is a vector
px -= x1;
py -= y1;
x2 -= x1;
y2 -= y1;
var cross = x2 * py - y2 * px; 
if(cross < 0){ 
     // point left of line
}else if(cross > 0) {
    // point right of line
}else {
    // point on the line
}
Run Code Online (Sandbox Code Playgroud)