Sprites HTML5 Canvas下的边界矩形

时间:2014-12-13 00:43:36

标签: javascript html5-canvas bounding-box

我上传了几张图片作为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();

          }  

1 个答案:

答案 0 :(得分:3)

旋转矩形之间的碰撞检测在数学上很复杂,有几种风格:

检测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>

以上演示最好以全屏模式观看