【问题标题】:Bounding Rectangles under Sprites HTML5 CanvasSprites HTML5 Canvas下的边界矩形
【发布时间】:2014-12-14 05:14:20
【问题描述】:

我上传了几张图片作为 2D HTML5 Canvas 汽车游戏的精灵。我一直在尝试仅使用精灵的坐标来进行碰撞检测,但它不能顺利进行。我之前听说过边界矩形,据我所知,它们是精灵下的不可见矩形,有助于碰撞检测(如果我错了,请纠正我)。

我在网上看到过一些东西,比如Element.getBoundingClientRect()。 谁能帮我在我的精灵下放一些边界矩形,因为我一无所知,我在网上找不到任何基本教程。

Js代码:Jsbin链接:http://jsbin.com/muzulutaci/2/edit?

var canvas = document.getElementById('background');
var context = canvas.getContext('2d');

//================
//ENTER: USER CAR
//================

//Uploading car sprite
var usercar = new Image();
usercar.src = "http://www.iconshock.com/img_jpg/BETA/communications/jpg/128/car_icon.jpg";

//Setting properties of car
var x = 450;
var y = 730;
var speed = 10;
var angle = -90;
var mod = 0;

function drawUserCar() {
    context.clearRect(0, 0, canvas.width, canvas.height);

    context.save();
    context.translate(x, y);
    context.rotate(Math.PI / 180 * angle);
    context.drawImage(usercar, -(usercar.width / 2), -(usercar.height / 2));
    context.restore();

    obstacleCar1();
}

//Interval for animation
var moveInterval = setInterval(function () {
    drawUserCar();
}, 30);

//=====================
//ENTER: OBSTACLE CAR 1
//=====================

//Uploading obstacle car
var obstcar = new Image();
obstcar.src = "http://www.iconshock.com/img_jpg/BETA/communications/jpg/128/car_icon.jpg";

//Setting properties of obstacle car
var x1 = 450;
var y1 = 300;
var speed1 = 5;
var angle1 = 90;
var mod1 = 0;

function obstacleCar1() {

          x1 += (speed1 * mod1) * Math.cos(Math.PI / 180 * angle1);
          y1 += (speed1 * mod1) * Math.sin(Math.PI / 180 * angle1);

          context.save();
          context.translate(x1, y1);
          context.rotate(Math.PI / 180 * angle1);
          context.drawImage(obstcar, -(obstcar.width / 1), -(obstcar.height / 1));
          context.restore();

          }  

【问题讨论】:

    标签: javascript html5-canvas bounding-box


    【解决方案1】:

    旋转矩形之间的碰撞检测在数学上很复杂,并且有多种形式:

    检测 2 个旋转的矩形是否相交(碰撞)

    要检测 2 个旋转的矩形是否发生碰撞(但不检测它们碰撞的位置),您可以使用分离轴定理。一个很好的解释在这里:http://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169

    检测旋转的矩形在哪里发生碰撞并对矩形应用反弹

    要在 2 个旋转的矩形碰撞时应用反弹需要一些复杂的物理特性。也许最简单的方法是使用像 Box2dJS 这样的物理库。这是一个很好的 Box2dJS 演示,展示了碰撞矩形:http://box2d-js.sourceforge.net/index2.html

    示例代码和演示:

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    
    var BB=canvas.getBoundingClientRect();
    var offsetX=BB.left;
    var offsetY=BB.top;
    
    var isDown=false;
    var startX;
    var startY;
    
    var PI=Math.PI;
    
    var car1Rect,car2Rect;
    var cars=[];
    
    var Closure=(function(){
      // ctor
      function Closure(x,y,imageObject){
        var iw=imageObject.width;
        var ih=imageObject.height;
        this.img=imageObject;
        this.x=x;
        this.y=y;
        this.w=iw;
        this.h=iw;
        this.cx=x+iw/2;
        this.cy=y+ih/2;
        this.radius=Math.sqrt(iw*iw+ih*ih)/2;
        this.rotation=0;
        this.corners=[];
        this.isDragging=false;
        this.collisionType=0;
    
        // corner angles
        var w2=iw/2;
        var h2=ih/2;
        this.negHalfWidth=-w2;
        this.negHalfHeight=-h2;
        this.cornerAngles=[
          Math.atan2(-h2,-w2),    // top-left
          Math.atan2(h2,-w2),     // top-right
          Math.atan2(h2,w2),      // bottom-right
          Math.atan2(-h2,w2)      // bottom-left
        ];
    
        this.rotateTo(0);
    
      }
      //
      Closure.prototype.draw=function(){   
        this.drawImage();
        this.drawBB(true);
        this.drawBoundingCircle(true);
      };
      Closure.prototype.moveBy=function(dx,dy){
        this.cx+=dx;
        this.cy+=dy;
      };
      Closure.prototype.rotateTo=function(angle){
        this.rotation=angle;
        this.setCorners();
      };
      Closure.prototype.setCorners=function(){
        this.corners.length=0;
        for(var i=0;i<this.cornerAngles.length;i++){
          var a=this.cornerAngles[i]+this.rotation;
          var x=this.radius*Math.cos(a);
          var y=this.radius*Math.sin(a);
          this.corners.push({x:x,y:y});
        }    
      };
      Closure.prototype.drawBB=function(withStroke){
        var p=this.corners;
        var cx=this.cx;
        var cy=this.cy;
        ctx.beginPath();
        ctx.moveTo(cx+p[0].x,cy+p[0].y);
        for(var i=1;i<p.length;i++){
          ctx.lineTo(cx+p[i].x,cy+p[i].y);
        }
        ctx.closePath();
        if(withStroke){
          switch(this.collisionType){
            case 0:ctx.strokeStyle='gray';break;
            case 1:ctx.strokeStyle='green';break;
            case 2:ctx.strokeStyle='red';break;
          }
          ctx.stroke();
        }
      };
      Closure.prototype.drawBoundingCircle=function(withStroke){
        var p=this.corners;
        var cx=this.cx;
        var cy=this.cy;
        ctx.beginPath();
        ctx.arc(this.cx,this.cy,this.radius,0,PI*2);
        ctx.closePath();
        if(withStroke){
          switch(this.collisionType){
            case 0:ctx.strokeStyle='gray';break;
            case 1:ctx.strokeStyle='red';break;
            case 2:ctx.strokeStyle='red';break;
          }        
          ctx.stroke();
        }
      };
      Closure.prototype.drawImage=function(){
        ctx.globalAlpha=0.50;
        ctx.translate(this.cx,this.cy);
        ctx.rotate(this.rotation);
        ctx.drawImage(this.img,this.negHalfWidth,this.negHalfHeight);
        ctx.setTransform(1,0,0,1,0,0);
        ctx.globalAlpha=1.00;
      };
      //    Closure.prototype.=function(){};
      return(Closure);
    })();
    
    
    function calculateCollisionType(r1,r2){
    
      // rough but fast circular bounds hit-test
      var dx=r2.cx-r1.cx;
      var dy=r2.cy-r1.cy;
      var rr=r1.radius+r2.radius;
      if(dx*dx+dy*dy>rr*rr){
        r1.collisionType=0; // no collision
        r2.collisionType=0; // no collision
        return(false);
      }   
    
      // hit-test the bounding rectangles
      if(RectanglesIntersect(r1,r2)){
        r1.collisionType=2; // bounding rectangles collide
        r2.collisionType=2;        
      }else{
        r1.collisionType=1; // circular bounds collide
        r2.collisionType=1;
      }
    
      return(true);
    }
    
    
    $car1Angle=$('#car1Angle');
    $car2Angle=$('#car2Angle');
    $car1Angle.val(0);
    $car2Angle.val(0);
    
    var carCount=2;
    var car1=new Image();
    car1.onload=start;
    car1.src="https://dl.dropboxusercontent.com/u/139992952/multple/car1.png";
    var car2=new Image();
    car2.onload=start;
    car2.src="https://dl.dropboxusercontent.com/u/139992952/multple/car2.png";
    function start(){
    
      if(--carCount>0){return;}
    
      car1Rect=new Closure(50,100,car1);
      cars.push(car1Rect);
      car2Rect=new Closure(50,250,car2);
      cars.push(car2Rect);
    
      $("#canvas").mousedown(function(e){handleMouseDown(e);});
      $("#canvas").mousemove(function(e){handleMouseMove(e);});
      $("#canvas").mouseup(function(e){handleMouseUpOut(e);});
      $("#canvas").mouseout(function(e){handleMouseUpOut(e);});
    
      $car1Angle.change(function(){
        car1Rect.rotateTo($(this).val()*PI/180);
        draw();
      });
      $car2Angle.change(function(){
        car2Rect.rotateTo($(this).val()*PI/180);
        draw();
      });
    
      calculateCollisionType(car1Rect,car2Rect);
      draw();
    
      $('#testCollision').click(function(){
        log(RectanglesIntersect(car1Rect,car2Rect));
      });
    
    }
    
    function draw(){
      ctx.clearRect(0,0,cw,ch);      
      car2Rect.draw();
      car1Rect.draw();
    }
    
    
    
    function handleMouseDown(e){
      e.preventDefault();
      e.stopPropagation();
    
      startX=parseInt(e.clientX-offsetX);
      startY=parseInt(e.clientY-offsetY);
    
      isDown=false;
      for(var i=0;i<cars.length;i++){
        var c=cars[i];
        c.drawBB(false);
        if(ctx.isPointInPath(startX,startY)){
          c.isDragging=true;
          isDown=true;
        }
      }
    }
    
    function handleMouseUpOut(e){
      e.preventDefault();
      e.stopPropagation();
    
      isDown=false;
      for(var i=0;i<cars.length;i++){
        cars[i].isDragging=false;
      }
    }
    
    function handleMouseMove(e){
      if(!isDown){return;}
    
      e.preventDefault();
      e.stopPropagation();
    
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);
    
      var dx=mouseX-startX;
      var dy=mouseY-startY;
      startX=mouseX;
      startY=mouseY;
    
      for(var i=0;i<cars.length;i++){
        var c=cars[i];
        if(c.isDragging){ c.moveBy(dx,dy); }
      }
    
      calculateCollisionType(car1Rect,car2Rect);
      draw();
    
    }
    
    
    ///////////////////////////////////////
    // Attribution for RectanglesIntersect() & isProjectedAxisCollision()
    // https://github.com/jozefchutka/YCanvas/blob/master/YCanvasLibrary/libs/yoz/sk/yoz/math/FastCollisions.as
    //
    
    function RectanglesIntersect(r1,r2){
    
      // rotated rectangle hit-test
      var cx,cy,c;
      //
      cx=r1.cx;
      cy=r1.cy;
      c=r1.corners;
      //
      var r1p1x=cx+c[0].x;
      var r1p2x=cx+c[1].x;
      var r1p3x=cx+c[2].x;
      var r1p4x=cx+c[3].x;
      //
      var r1p1y=cy+c[0].y;
      var r1p2y=cy+c[1].y;
      var r1p3y=cy+c[2].y;
      var r1p4y=cy+c[3].y;
      //
      cx=r2.cx;
      cy=r2.cy;
      c=r2.corners;
      //
      var r2p1x=cx+c[0].x;
      var r2p2x=cx+c[1].x;
      var r2p3x=cx+c[2].x;
      var r2p4x=cx+c[3].x;
      //
      var r2p1y=cy+c[0].y;
      var r2p2y=cy+c[1].y;
      var r2p3y=cy+c[2].y;
      var r2p4y=cy+c[3].y;
    
      //
      if(!isProjectedAxisCollision(r1p1x, r1p1y, r1p2x, r1p2y, 
                                   r2p1x, r2p1y, r2p2x, r2p2y, r2p3x, r2p3y, r2p4x, r2p4y))
        return false;
    
      if(!isProjectedAxisCollision(r1p2x, r1p2y, r1p3x, r1p3y, 
                                   r2p1x, r2p1y, r2p2x, r2p2y, r2p3x, r2p3y, r2p4x, r2p4y))
        return false;
    
      if(!isProjectedAxisCollision(r2p1x, r2p1y, r2p2x, r2p2y, 
                                   r1p1x, r1p1y, r1p2x, r1p2y, r1p3x, r1p3y, r1p4x, r1p4y))
        return false;
    
      if(!isProjectedAxisCollision(r2p2x, r2p2y, r2p3x, r2p3y, 
                                   r1p1x, r1p1y, r1p2x, r1p2y, r1p3x, r1p3y, r1p4x, r1p4y))
        return false;
      //
      return true;
    }
    
    function isProjectedAxisCollision( b1x, b1y, b2x, b2y, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y){
      var x1, x2, x3, x4;
      var y1, y2, y3, y4;
      if(b1x == b2x){
    
        x1 = x2 = x3 = x4 = b1x;
        y1 = p1y;
        y2 = p2y;
        y3 = p3y;
        y4 = p4y;
    
        if(b1y > b2y)
        {
          if((y1 > b1y && y2 > b1y && y3 > b1y && y4 > b1y) || 
             (y1 < b2y && y2 < b2y && y3 < b2y && y4 < b2y))
            return false;
        }
        else
        {
          if((y1 > b2y && y2 > b2y && y3 > b2y && y4 > b2y) ||
             (y1 < b1y && y2 < b1y && y3 < b1y && y4 < b1y))
            return false;
        }
        return true;
      }
      else if(b1y == b2y){
    
        x1 = p1x;
        x2 = p2x;
        x3 = p3x;
        x4 = p4x;
        y1 = y2 = y3 = y4 = b1y;
    
      }else{
    
        var a = (b1y - b2y) / (b1x - b2x);
        var ia = 1 / a;
        var t1 = b2x * a - b2y;
        var t2 = 1 / (a + ia);
    
        x1 = (p1y + t1 + p1x * ia) * t2;
        x2 = (p2y + t1 + p2x * ia) * t2;
        x3 = (p3y + t1 + p3x * ia) * t2;
        x4 = (p4y + t1 + p4x * ia) * t2;
    
        y1 = p1y + (p1x - x1) * ia;
        y2 = p2y + (p2x - x2) * ia;
        y3 = p3y + (p3x - x3) * ia;
        y4 = p4y + (p4x - x4) * ia;
      }
    
      if(b1x > b2x){
    
        if((x1 > b1x && x2 > b1x && x3 > b1x && x4 > b1x) ||
           (x1 < b2x && x2 < b2x && x3 < b2x && x4 < b2x))
          return false;
    
      }else{
    
        if((x1 > b2x && x2 > b2x && x3 > b2x && x4 > b2x) ||
           (x1 < b1x && x2 < b1x && x3 < b1x && x4 < b1x))
          return false;
      }
    
      return true;
    }
    body{ background-color: ivory; }
    #canvas{border:1px solid red;}
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    Red car angle:&nbsp<input id=car1Angle type=range min=0 max=360 value=0><br>
    Gold car angle:&nbsp<input id=car2Angle type=range min=0 max=360 value=0><br>
    <h4>Use sliders above to rotate cars.<br>Drag cars closer.<br>Bounding Circles turn green if they collide.<br>Bounding Rectangles turn green if they collide.</h4>
    <br>
    <canvas id="canvas" width=400 height=500></canvas>

    上面的演示最好在全屏模式下观看

    【讨论】:

    • 有没有比使用边界矩形更简单的碰撞检测方法?
    • 是的,我的示例命中测试了边界矩形和边界圆。边界圆比旋转的边界矩形简单得多。请参阅我的示例中使用//rough but fast circular bounds hit-test 注释的边界圆的部分。干杯!
    猜你喜欢
    • 2011-05-30
    • 2013-06-05
    • 1970-01-01
    • 2011-10-17
    • 2014-09-22
    • 1970-01-01
    • 1970-01-01
    • 2013-02-24
    • 2014-10-03
    相关资源
    最近更新 更多