如何对两个部分透明的图像进行像素完美碰撞检测

所以基本上我要这样做,当屏幕上的两个角色触摸而有人按下按钮时,它会带走他们的健康。 我唯一不知道该怎么做就是如何检测它们何时接触。

$(document).ready(function(){ var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); canvas.width = 1000; canvas.height = 600; document.body.appendChild(canvas); var kGroundHeight = 500; /*var upKey = 38; var downKey = 40; var leftKey = 37; var rightKey = 39; */ var render = function() { gravity(); gravity1(); context.clearRect(0, 0, canvas.width, canvas.height); context.fillRect(0,kGroundHeight,canvas.width,10); context.drawImage(kirby, kirbyObject.x, kirbyObject.y); context.drawImage(link, linkObject.x, linkObject.y); }; var main = function() { render(); window.requestAnimationFrame(main); }; main(); }); var linkReady = false; var link = new Image(); link.onLoad = function() { linkReady = true; }; linkObject = {}; link.src= "http://sofzh.miximages.com/javascript/Link_(Sprite)_The_Legend_of_Zelda.png/revision/latest?cb=20130117162823"; linkObject.x = 200; linkObject.y = 200; var keys = {}; $(document).keydown(function(e) { console.log(e); move1(e.keyCode); if (keys[87] && keys[65]) { linkObject.y -=50; linkObject.x -=50; }else if(keys[87] && keys[68]){ linkObject.y -=50; linkObject.x +=50; }else if(keys[83] && keys[68]){ linkObject.y +=50; linkObject.x +=50; }else if(keys[83] && keys[65]){ linkObject.y +=50; linkObject.x -=50; } keys[e.keyCode] = true; }).keyup(function(e) { keys[e.keyCode] = false; }); var upKey1 = 87; var downKey1 = 83; var leftKey1 = 65; var rightKey1 = 68; var attackKey1 = 75; var move1 = function(key) { if (key == upKey1) { linkObject.y -= 50; }else if(key == downKey1){ var kGroundHeight = 500; if(linkObject.y + link.height == kGroundHeight){ linkObject.y +=0; }else{ linkObject.y +=50; } //console.log("down"); console.log(linkObject.y); }else if(key == leftKey1){ linkObject.x -=50; }else if(key == rightKey1 ){ linkObject.x +=50; } // MORE DIRECTIONS!!! }; var gravity1 = function() { var kGravityScale = 1; var kGroundHeight = 500; if (linkObject.y + link.height == kGroundHeight) { linkObject.y += 0; }else{ linkObject.y += kGravityScale; //console.log(link.width); } }; var attack = function(a){ }; var kGroundHeight = 500; var keys = {}; $(document).keydown(function(e) { console.log(e); move(e.keyCode); if (keys[38] && keys[37]) { kirbyObject.y -=50; kirbyObject.x -=50; }else if(keys[38] && keys[39]){ kirbyObject.y -=50; kirbyObject.x +=50; }else if(keys[40] && keys[39]){ kirbyObject.y +=50; kirbyObject.x +=50; }else if(keys[40] && keys[37]){ kirbyObject.y +=50; kirbyObject.x -=50; } keys[e.keyCode] = true; }).keyup(function(e) { keys[e.keyCode] = false; }); var kirbyReady = false; var kirby = new Image(); kirby.onLoad = function() { kirbyReady = true; }; kirbyObject = {}; kirby.src = "http://sofzh.miximages.com/javascript/Kirby.png/revision/latest?cb=20101010225540"; kirbyObject.x = 300; kirbyObject.y = 100; var upKey = 38; var downKey = 40; var leftKey = 37; var rightKey = 39; var attackKey = 32; var move = function(key) { if (key == upKey) { kirbyObject.y -= 50; }else if(key == downKey){ var kGroundHeight = 500; if(kirbyObject.y + kirby.height == kGroundHeight){ kirbyObject.y +=0; }else{ kirbyObject.y +=50; } //console.log("down"); console.log(kirbyObject.y); }else if(key == leftKey){ kirbyObject.x -=50; }else if(key == rightKey ){ kirbyObject.x +=50; } // MORE DIRECTIONS!!! }; var gravity = function() { var kGravityScale = 1; var kGroundHeight = 500; if (kirbyObject.y + kirby.height == kGroundHeight) { kirbyObject.y += 0; }else{ kirbyObject.y += kGravityScale; } }; 

径向周长测试

通过用一组极性协调定义每个精灵的形状,可以实现快速几乎像素完美的碰撞。 每个坐标描述距中心的距离(中心是任意的,但必须在精灵内),并且沿着该方向从中心到最远像素的中心的方向。 坐标数(n)由最外面的像素的周长确定。 我不知道之前是否已经描述过,因为我现在只想到它,所以它的实际稳健性需要测试。

这个概念

下图显示了基本概念。

极地碰撞

精灵(1.)覆盖极坐标和外边界圆(2.)索引每个坐标从0到15(3)。提取检查碰撞所需的信息。 绿色(a)是每个精灵的角度原点,黄色线P是A和B之间的矢量,标记为角度来源的角度(4.)

要获得坐标,您需要访问像素信息,这可以在制作期间完成,并作为代码添加,或在游戏设置期间。 它会产生类似的数据结构

 var sprite = { ... collisionData : { maxRadius : 128, minRadius : 20, coords : [128,30,50, ... ], } } 

片段1。

现在您已将每个精灵描述为您需要进行测试的一组极坐标。

假设精灵将具有位置(x,y,以像素为单位),旋转(以弧度表示的r)和比例(具有方形纵横比的s)。

 positionData = { // position data structure x : 100, // x pos y : 100, // y pos r : Math.PI * 1.2, // rotation s : 1, // scale } 

摘录2。

碰撞试验

对于pApB引用sprite positionData(片段2)和cAcB引用每个sprite的collisionData(片段1)的两个sprite,我们首先进行距离测试,检查最大半径和最小半径。 如果发生冲突,代码将返回true。

 const TAU = Math.PI * 2; // use this alot so make it a constant var xd = pA.x - pB.x; // get x distance var yd = pA.y - pB.y; // get y distance var dist = Math.hypot(xd,yd); // get the distance between sprites // Please note that legacy browsers will not // support hypot // now scale the max radius of each sprite and test if the distance is less // than the sum of both. if (dist <= cA.maxRadius * pA.s + cB.maxRadius * pB.s){ // passed first test sprites may be touching // now check the min radius scaled if (dist <= Math.min(cA.minRadius * pA.s, cB.minRadius * pB.s) * 2 ){ // the sprites are closer than the smallest of the two's min // radius scaled so must be touching return true; // all done return true } 

摘录3。

现在你需要进行极地测试。 您需要从每个精灵到另一个精灵的方向,然后调整该方向以匹配精灵旋转,然后将方向标准化为存储在collisionData中的极坐标数。

  // on from snippet 3. var dir = Math.atan2(yd, xd); // get the direction from A to B in radians // please note that y comes first in atan2 // now subtract the rotation of each sprite from the directions var dirA = dir - pA.r; var dirB = dir + Math.PI - pB.r; // B's direction is opposite // now normalise the directions so they are in the range 0 - 1; dirA = (((dirA % TAU) + TAU) % TAU) / TAU; dirB = (((dirB % TAU) + TAU) % TAU) / TAU; 

将归一化的相对方向转换为极arrays中的正确索引的下一步需要考虑每个极坐标的角宽度。 参见图中的图3.每个极坐标顶部的平坦位是角宽度。 为此,我们在从0-1放大到坐标数时使用Math.round 。 当接近1的值将向上舍入到错误的索引时,您还必须使用模数%来确保它不会超出范围。

  var indexA = Math.round(dirA * cA.coords.length) % cA.coords.length; var indexB = Math.round(dirB * cB.coords.length) % cB.coords.length; // now we can get the length of the coordinates. // also scale them at the same time var la = cA.coords[indexA] * pA.s; var lb = cB.coords[indexB] * pB.s; // now test if the distance between the sprites is less than the sum // of the two length if( dist <= la + lb ){ // yes the two are touching return true; } } 

注意事项

就是这样。 与其他方法相比,它相对较快,但它不是像素完美的,因为它只考虑精灵的周长,如果精灵周长不是凸的,你可能会遇到算法返回不正确的命中的情况。

如果精灵非常粗糙,则可能存在碰撞未能检测到碰撞的情况,这同样适用于过度采样坐标的数量。 该图显示了精灵的16个坐标,其接近128乘128,这似乎是一个很好的数字。 更大的精灵需要更少的更小,但不要低于8。

起色

在图中,图4显示了一个未命中但是如果精灵的B坐标15(从它们之间的方向顺时针一个)稍微长一点,则会有未检测到的命中。 为了改进算法,你可以测试两侧的索引坐标与距离,但你需要减少极坐标距离,以说明它们没有指向另一个精灵的中心。 通过将极距乘以偏移角的cos来做到这一点。

  // get the angle step for A and B var angleStepA = TAU / cA.coord.length; var angleStepB = TAU / cB.coord.length; // the number of coordinates to offset var offCount = 1; // get next coord clockwise from A and scale it var lengOffA = cA.coord[(index + offCount) % cA.coord.length] * pA.s; // get next coordinate counter clockwise from B and scale it var lengOffB = cB.coord[(index + cB.coord.length - offCount) % cB.coord.length] * pB.s; // Now correct for the offest angle lengOffA *= Math.cos(offCount * angleStepA); lengOffB *= Math.cos(offCount * angleStepB); // Note that as you move away the length will end up being negative because // the coord will point away. if( dist < lengOffA + lengOffB ){ // yes a hit return true; } 

这为算法增加了一点处理但不是那么多,并且应该只有A的角度大小的一半。

您可能还希望使极坐标之间的区域成为线性斜率,并插入第一次测试的极距。

更多

有许多方法可以进行测试,您使用的方法取决于需求。 如果您只有几个精灵,那么可以使用更复杂的算法来获得更好的结果,如果您有许多精灵则使用更简单的测试。

不要过度烹饪。

请记住,像素完美测试是程序员想要的,但玩家几乎看不到像素(现代显示器的像素小于人眼的径向分辨率)。 如果两个几乎不可见的像素触摸1/60秒,谁会看到它作为一个打击???

你可以分两步完成。

  1. 检查重叠的边界圆。

  2. 如果圆圈重叠,请检查重叠的(更精确的)边界矩形。

如果你有两个图像,那么每个图像的中心等于(x + w/2), (y + h/2) 。 并且它们中的每一个的半径等于sqrt((w/2)^2 + (h/2)^2)

当两个圆之间的距离( sqrt((x1-x2)^2 + (y1-y2)^2) )小于max(radius1, radius2)时,两个圆重叠。

边界矩形碰撞检测是直观的,但计算上可能更难(可能影响大量对象)。