如何在画布上创造障碍

时间:2016-01-05 09:11:27

标签: javascript html canvas

我正在尝试制作一个像游戏一样简单的平台游戏。我使用的代码如下所示

window.onload = function(){
	var canvas = document.getElementById('game');
	var ctx = canvas.getContext("2d");

	var rightKeyPress = false;
	var leftKeyPress = false;
	var upKeyPress = false;
	var downKeyPress = false;
	var playerX = canvas.width / 2;
	var playerY = -50;
	var dx = 3;
	var dy = 3;
	var dxp = 3;
	var dyp = 3;
	var dxn = 3;
	var dyn = 3;
	var prevDxp = dxp;
	var prevDyp = dyp;
	var prevDxn = dxn;
	var prevDyn = dyn;
	var playerWidth = 50;
	var playerHeight = 50;
	var obstacleWidth = 150;
	var obstacleHeight = 50;
	var obstaclePadding = 10;
	var G = .98;
	var currentVelocity = 0;
	var obstacles = [];
	var imageLoaded = false;

	document.addEventListener("keyup",keyUp,false);
	document.addEventListener("keydown",keyDown,false);

	function keyDown(e){
		if(e.keyCode == 37){
			leftKeyPress = true;
			if(currentVelocity > 2){
				currentVelocity -= .1;
			}
		}
		if(e.keyCode == 38){
			upKeyPress = true;
		}
		if(e.keyCode == 39){
			rightKeyPress = true;
			if(currentVelocity < 2){
				currentVelocity += .1;
			}
		}
		if(e.keyCode == 40){
			downKeyPress = true;
		}
	}
	function keyUp(e){
		if(e.keyCode == 37){
			leftKeyPress = false;
		}
		if(e.keyCode == 38){
			upKeyPress = false;
		}
		if(e.keyCode == 39){
			rightKeyPress = false;
		}
		if(e.keyCode == 40){
			downKeyPress = false;
		}
	}
	function createObstacles(){
		for(x=0;x < 4;x++){
			var obX = (200 * x) + Math.round(Math.random() * 150);
			var obY = 50 + Math.round(Math.random() * 400);
			obstacles.push({"x":obX,"y":obY});
		}
	}
	createObstacles();
	function drawObstacles(){
		ctx.beginPath();
		for(x=0;x < 4;x++){
			var obX = obstacles[x].x;
			var obY = obstacles[x].y;
			ctx.rect(obX,obY,obstacleWidth,obstacleHeight)
		}	
		ctx.fillStyle = "grey";
		ctx.fill();
		ctx.closePath();
	}
	function initPlayer(){
		ctx.beginPath();
		ctx.rect(playerX,playerY,50,50);
		ctx.fillStyle="orange";
		ctx.fill();
		ctx.closePath();
	}
	function KeyPressAndGravity(){
		checkObstacleCollision();
		playerX += currentVelocity;
		if(rightKeyPress && playerX + 50 < canvas.width){
			playerX += dxp;
		}
		if(leftKeyPress && playerX > 0){
			playerX -= dxn;
		}
		if(upKeyPress && playerY > 0){
			playerY -= dyn;
		}
		if(downKeyPress && playerY + 50 < canvas.height){
			playerY += dyp;
		}
		if(playerY+50 < canvas.height){
			playerY += G;
		}
		if(playerX <= 0){
			currentVelocity = 0;
		}else if(playerX + 50 >= canvas.width){
			currentVelocity = 0;
		}
		dxp = prevDxp;
		dyp = prevDyp;
		dxn = prevDxn;
		dyn = prevDyn;
		G = .98;
		if(currentVelocity != 0){
			if(currentVelocity > 0){
				currentVelocity -= .01;
			}else{
				currentVelocity += .01;
			}
		}
	}
  /*-----------------------------------------------------------
  -------------------------------------------------------------
  -------------------------------------------------------------
  ---------------------------Check this part-------------------
  -------------------------------------------------------------
  -------------------------------------------------------------
  -------------------------------------------------------------
  ------------------------------------------------------------*/
	function checkObstacleCollision(){
		var obLen = obstacles.length;
		for(var x=0;x<obLen;x++){
			var obX = obstacles[x].x;
			var obY = obstacles[x].y;
			if((playerX + playerWidth > obX && playerX + playerWidth < obX + obstacleWidth || playerX > obX && playerX < obX + obstacleWidth) && playerY + playerHeight > obY - obstaclePadding && playerY + playerHeight < obY){
				dyp = 0;
				G = 0;
			}else if((playerX + playerWidth > obX && playerX + playerWidth < obX + obstacleWidth || playerX > obX && playerX < obX + obstacleWidth) && playerY > obY + obstacleHeight && playerY < obY + obstacleHeight + obstaclePadding){
				dyn = 0;
			}else if(playerX + playerWidth > obX - obstaclePadding && playerX + playerWidth < obX && ((playerY + playerHeight > obY && playerY + playerHeight < obY + obstacleHeight) || (playerY > obY && playerY < obY + obstacleHeight))){
				dxp = 0;
			}else if(playerX  > obX + obstacleWidth && playerX < obX + obstacleWidth + obstaclePadding && ((playerY + playerHeight > obY && playerY + playerHeight < obY + obstacleHeight) || (playerY > obY && playerY < obY +  obstacleHeight))){
				dxn = 0;
			}

		}
	}
	function draw(){
		ctx.clearRect(0,0,canvas.width,canvas.height);
		initPlayer();
		KeyPressAndGravity();
		drawObstacles();
	}

	setInterval(draw,15);
}
<canvas id="game" width="1000" height="600" style="border:1px solid #000;"></canvas>

问题在于,有时当“玩家”的速度很高时,它可能会遇到如下图所示的障碍。我怎么能阻止这种情况发生呢?

enter image description here

所以我想要的是玩家应该在他到达障碍物时停下来而不是通过它

3 个答案:

答案 0 :(得分:5)

碰撞测试快速移动的对象时会出现复杂情况

您必须确定您的球员和障碍物是否在移动过程中 任何时间 交叉 - 即使玩家在移动结束时已移出障碍物。因此,您必须考虑到玩家从移动开始到结束移动的完整路径。

enter image description here ... enter image description here

然后,您可以通过检查玩家的轨道是否与障碍物相交来检查玩家是否在移动过程中与障碍物相交。

enter image description here

测试涉及快速移动物体的碰撞的相对有效的方法

  1. 定义连接播放器起始矩形的3个顶点的3个线段,这些顶点最接近玩家的结束矩形。
  2. enter image description here

    1. 对于与障碍物相交的3条线中的任何一条,计算线段与障碍物的距离。选择起始顶点和障碍物之间距离最短的线。
    2. enter image description here enter image description here

      1. 计算&#34; x&#34; &安培; &#34; Y&#34;所选线段的距离。

        var dx = obstacleIntersection.x - start.x;
        var dy = obstacleIntersection.y - start.y;
        
      2. 将玩家从起始位置移动#3中计算的距离。这导致玩家移动到第一次与障碍物碰撞的地方。

        player.x += dx;
        player.y += dy;
        
      3. enter image description here

        代码和演示:

        代码中的有用功能:

        • setPlayerVertices确定连接玩家的起始矩形的3个顶点的3个线段,这些顶点最接近玩家的结束矩形。

        • hasCollided找到连接玩家起始位置的顶点与障碍物上的碰撞点的最短段。

        • line2lineIntersection找到2行之间的交点(如果有)。这用于测试从开始到结束段(来自#1)和构成障碍矩形的4个线段中的任何一个的交叉点。 归因:此功能改编自Paul Bourke的有用treatice on intersections

        enter image description here

        以下是示例代码和演示,演示如何在障碍物的碰撞点停止玩家:

        &#13;
        &#13;
        var canvas=document.getElementById("canvas");
        var ctx=canvas.getContext("2d");
        var cw=canvas.width;
        var ch=canvas.height;
        function reOffset(){
            var BB=canvas.getBoundingClientRect();
            offsetX=BB.left;
            offsetY=BB.top;        
        }
        var offsetX,offsetY;
        reOffset();
        window.onscroll=function(e){ reOffset(); }
        window.onresize=function(e){ reOffset(); }
        
        var isDown=false;
        var startX,startY,dragging;
        
        ctx.translate(0.50,0.50);
        ctx.textAlign='center';
        ctx.textBaseline='middle';
        
        var pts;
        var p1={x:50,y:50,w:25,h:25,fill:''};
        var p2={x:250,y:250,w:25,h:25,fill:''};
        var ob={x:100,y:150,w:125,h:25,fill:''};
        var obVertices=[
            {x:ob.x,y:ob.y},
            {x:ob.x+ob.w,y:ob.y},
            {x:ob.x+ob.w,y:ob.y+ob.h},
            {x:ob.x,y:ob.y+ob.h}
        ];
        var s1,s2,s3,e1,e2,e3,o1,o2,o3,o4;
        
        draw();
        
        $("#canvas").mousedown(function(e){handleMouseDown(e);});
        $("#canvas").mousemove(function(e){handleMouseMove(e);});
        $("#canvas").mouseup(function(e){handleMouseUpOut(e);});
        $("#canvas").mouseout(function(e){handleMouseUpOut(e);});
        
        
        function draw(){
            ctx.clearRect(0,0,cw,ch);
            //
            ctx.lineWidth=4;
            ctx.globalAlpha=0.250;
            ctx.strokeStyle='blue';
            ctx.strokeRect(ob.x,ob.y,ob.w,ob.h);
            ctx.globalAlpha=1.00;
            ctx.fillStyle='black';
            ctx.fillText('obstacle',ob.x+ob.w/2,ob.y+ob.h/2);
            //
            ctx.globalAlpha=0.250;
            ctx.strokeStyle='gold';
            ctx.strokeRect(p1.x,p1.y,p1.w,p1.h);
            ctx.strokeStyle='purple';
            ctx.strokeRect(p2.x,p2.y,p2.w,p2.h);
            ctx.fillStyle='black';
            ctx.globalAlpha=1.00;
            ctx.fillText('start',p1.x+p1.w/2,p1.y+p1.h/2);
            ctx.fillText('end',p2.x+p2.w/2,p2.y+p2.h/2);
        }
        
        
        function handleMouseDown(e){
          // tell the browser we're handling this event
          e.preventDefault();
          e.stopPropagation();
          
          startX=parseInt(e.clientX-offsetX);
          startY=parseInt(e.clientY-offsetY);
        
          // Put your mousedown stuff here
          var mx=startX;
          var my=startY;
          if(mx>p1.x && mx<p1.x+p1.w && my>p1.y && my<p1.y+p1.h){
              isDown=true;
              dragging=p1;
          }else if(mx>p2.x && mx<p2.x+p2.w && my>p2.y && my<p2.y+p2.h){
              isDown=true;
              dragging=p2;
          }
        }
        
        function handleMouseUpOut(e){
          // tell the browser we're handling this event
          e.preventDefault();
          e.stopPropagation();
          // Put your mouseup stuff here
          isDown=false;
          dragging=null;
        }
        
        function handleMouseMove(e){
          if(!isDown){return;}
          // tell the browser we're handling this event
          e.preventDefault();
          e.stopPropagation();
        
          mouseX=parseInt(e.clientX-offsetX);
          mouseY=parseInt(e.clientY-offsetY);
        
          // Put your mousemove stuff here
          var dx=mouseX-startX;
          var dy=mouseY-startY;
          startX=mouseX;
          startY=mouseY;
          //
          dragging.x+=dx;
          dragging.y+=dy;
          //
          draw();
          //
          setPlayerVertices(p1,p2);
          var c=hasCollided(obVertices);
          if(c.dx){
              ctx.strokeStyle='gold';
              ctx.strokeRect(p1.x+c.dx,p1.y+c.dy,p1.w,p1.h);
              ctx.fillStyle='black';
              ctx.fillText('hit',p1.x+c.dx+p1.w/2,p1.y+c.dy+p1.h/2);
              line(c.s,c.i,'red');
          }
        }
        
        function setPlayerVertices(p1,p2){
            var tl1={x:p1.x,      y:p1.y};
            var tl2={x:p2.x,      y:p2.y};
            var tr1={x:p1.x+p1.w, y:p1.y};
            var tr2={x:p2.x+p2.w, y:p2.y};
            var br1={x:p1.x+p1.w, y:p1.y+p1.h};
            var br2={x:p2.x+p2.w, y:p2.y+p2.h};
            var bl1={x:p1.x,      y:p1.y+p1.h};
            var bl2={x:p2.x,      y:p2.y+p2.h};
            //
            if(p1.x<=p2.x && p1.y<=p2.y){
                s1=tr1; s2=br1; s3=bl1;
                e1=tr2; e2=br2; e3=bl2;
                o1=0; o2=1; o3=3; o4=0;
            }else if(p1.x<=p2.x && p1.y>=p2.y){
                s1=tl1; s2=tr1; s3=br1;
                e1=tl2; e2=tr2; e3=br2;
                o1=2; o2=3; o3=3; o4=0;
            }else if(p1.x>=p2.x && p1.y<=p2.y){
                s1=tl1; s2=br1; s3=bl1;
                e1=tl2; e2=br2; e3=bl2;
                o1=0; o2=1; o3=1; o4=2;
            }else if(p1.x>=p2.x && p1.y>=p2.y){
                s1=tl1; s2=tr1; s3=bl1;
                e1=tl2; e2=tr2; e3=bl2;
                o1=1; o2=2; o3=2; o4=3;
            }
        }
        
        function hasCollided(o){
            //
            var i1=line2lineIntersection(s1,e1,o[o1],o[o2]);
            var i2=line2lineIntersection(s2,e2,o[o1],o[o2]);
            var i3=line2lineIntersection(s3,e3,o[o1],o[o2]);
            var i4=line2lineIntersection(s1,e1,o[o3],o[o4]);
            var i5=line2lineIntersection(s2,e2,o[o3],o[o4]);
            var i6=line2lineIntersection(s3,e3,o[o3],o[o4]);
            //
            var tracks=[];
            if(i1){tracks.push(track(s1,e1,i1));}
            if(i2){tracks.push(track(s2,e2,i2));}
            if(i3){tracks.push(track(s3,e3,i3));}
            if(i4){tracks.push(track(s1,e1,i4));}
            if(i5){tracks.push(track(s2,e2,i5));}
            if(i6){tracks.push(track(s3,e3,i6));}
            //
            var nohitDist=10000000;
            var minDistSq=nohitDist;
            var halt={dx:null,dy:null,};
            for(var i=0;i<tracks.length;i++){
                var t=tracks[i];
                var testdist=t.dx*t.dx+t.dy*t.dy;
                if(testdist<minDistSq){
                    minDistSq=testdist;
                    halt.dx=t.dx;
                    halt.dy=t.dy;
                    halt.s=t.s;
                    halt.i=t.i;
                }
            }
            return(halt);
        }
        //
        function track(s,e,i){
            dot(s);dot(i);line(s,i);line(i,e);
            return({ dx:i.x-s.x, dy:i.y-s.y, s:s, i:i });
        }
        
        
        function line2lineIntersection(p0,p1,p2,p3) {
            var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
            var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
            var denominator  = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);        
            // Test if Coincident
            // If the denominator and numerator for the ua and ub are 0
            //    then the two lines are coincident.    
            if(unknownA==0 && unknownB==0 && denominator==0){return(null);}
            // Test if Parallel 
            // If the denominator for the equations for ua and ub is 0
            //     then the two lines are parallel. 
            if (denominator == 0) return null;
            // If the intersection of line segments is required 
            // then it is only necessary to test if ua and ub lie between 0 and 1.
            // Whichever one lies within that range then the corresponding
            // line segment contains the intersection point. 
            // If both lie within the range of 0 to 1 then 
            // the intersection point is within both line segments. 
            unknownA /= denominator;
            unknownB /= denominator;
            var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)
            if(!isIntersecting){return(null);}
            return({
                x: p0.x + unknownA * (p1.x-p0.x),
                y: p0.y + unknownA * (p1.y-p0.y)
            });
        }
        
        function dot(pt){
            ctx.beginPath();
            ctx.arc(pt.x,pt.y,3,0,Math.PI*2);
            ctx.closePath();
            ctx.fill();
        }
        
        function line(p0,p1,stroke,lw){
            ctx.beginPath();
            ctx.moveTo(p0.x,p0.y);
            ctx.lineTo(p1.x,p1.y);
            ctx.lineWidth=lw || 1;
            ctx.strokeStyle=stroke || 'gray';
            ctx.stroke();
        }
        &#13;
        body{ background-color: ivory; }
        #canvas{border:1px solid red; }
        &#13;
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <h4>Drag start & end player position rects<br>The shortest segment intersecting the obstacle is red.<br>The repositioned player is shown on the obstacle.</h4>
        <canvas id="canvas" width=400 height=400></canvas>
        &#13;
        &#13;
        &#13;

答案 1 :(得分:1)

您所经历的通常被称为隧道。

很多有不同的方法来解决它,但最简单的方法通常是保存最后一个位置并执行以下操作之一。

<强> A

为每个元素计算一个新的更大的碰撞框,其中包含元素的最后位置及其新元素。将其视为包含元素两次的框。一次为它的最后一个位置(LP)和一个用于它的新位置(NP),

 ------------
|| LP |      |
||____|      |
|       ____ |
|      | NP ||
|______|____||

现在,如果您使用此新框来检查碰撞,则会考虑行进路径以避免隧道挖掘。这可能会在右上角和左下角产生意外碰撞,但这只是一个简单的实现,并且可能值得权衡。

<强>乙

检查从其最后位置到其新位置的路径上每个步骤的碰撞。如果您的元素自上一帧开始已经移动了5个像素,则可以检查每个像素的碰撞一次(或最小可接受的碰撞距离)。

 ____
| LP |      
|____||      
  ---- |___ 
   |___|NP |
      |____|

这当然会增加碰撞检测次数并对性能产生影响。在这里,您可以查看四叉树以补偿性能损失。

展望未来,有更多优雅和先进的解决方案,但这里的主题是广泛的完整答案。

希望它有所帮助!

答案 2 :(得分:1)

好吧,几个月前我制作了一个“碰撞计算器”,所以你可以随意改变和使用下面的代码:)为了更好的解释:

  • p_x是最后一名球员位置x加上他的宽度
  • p_y是最后一名球员位置y加上他的身高
  • p_x_m是最后一位玩家位置x
  • p_y_m是最后一位玩家位置y
  • y_m是新玩家位置(他的y - somevalue)
  • x_m是新玩家位置(他的x - somevalue)
  • y_p是新玩家位置(他的y + somevalue +他的身高)
  • y_p_m是新玩家位置(他的y + somevalue)
  • x_p是新玩家位置(他的x + somevalue +他的宽度)
  • x_p_m是新玩家位置(他的x + somevalue)
  • w_x是墙位x
  • w_y是墙位置y
  • w_w是墙宽
  • w_h是墙高

  • pressedKeys是一个字符串,告诉按下哪个按键播放器(例如“是”或“wd”或“ad”等)

  • this.walls是一个带墙的变量(例如,如果我有4个墙,则数组看起来像[false,'s',false,false],因为我用“s”键触及第二个墙。)

代码:

if(
    pressedKeys.indexOf("s")>-1 &&
    (
        (                                                                                   //      P
            p_y>w_y&&p_y<(w_y+w_h)&&x_p_m>w_x && x_p-5>w_x && x_m<w_x                       //      +----
        ) ||                                                                                //      |

        (                                                                                   //      P
            y_p>w_y&&p_y<(w_y+w_h) && x_p-5>w_x && x_p<=(w_x+w_w)                           //  +--------+
        ) ||                                                                                //  |        |

        (                                                                                   //      P
            y_p>w_y&&p_y<(w_y+w_h) && x_p>(w_x+w_w)&&p_x_m<(w_x+w_w) && x_m+5<(w_x+w_w)     //  ----+
        )                                                                                   //      |
    )
)
{
    if(this.walls[i] == false)
        this.walls[i] = "";
    this.walls[i] += "s";
}
if(
    pressedKeys.indexOf("d")>-1 &&
    (
        (                                                                                   //      P+----
            p_x>w_x&&p_x<(w_x+w_w)&&y_p_m>w_y && y_p-5>w_y && y_m<w_y                       //       |
        ) ||                                                                                //       |

        (                                                                                   //       |
            x_p>w_x&&p_x<(w_x+w_w) && y_p-5>w_y && y_p<=(w_y+w_h)                           //      P|
        ) ||                                                                                //       |

        (                                                                                   //       |
            x_p>w_x&&p_x<(w_x+w_w) && y_p>(w_y+w_h)&&p_y_m<(w_y+w_h) && y_m+5<(w_y+w_h)     //       |
        )                                                                                   //      P+----
    )
)
{
    if(this.walls[i] == false)
        this.walls[i] = "";
    this.walls[i] += "d";
}
if(
    pressedKeys.indexOf("w")>-1 &&
    (
        (                                                                                   //      |
            y_m<(w_y+w_h)&&y_p-5>w_y && x_p-5>w_x && x_m<w_x &&x_p_m>w_x                    //      +----
        ) ||                                                                                //      P

        (                                                                                   //  |        |
            y_m<(w_y+w_h)&&y_p-5>w_y && x_p-5>w_x && x_p<=(w_x+w_w)                         //  +--------+
        ) ||                                                                                //      P    

        (                                                                                   //      |
            y_m<(w_y+w_h)&&y_p-5>w_y && x_p>(w_x+w_w)&&p_x_m<(w_x+w_w) && x_m+5<(w_x+w_w)   //  ----+
        )                                                                                   //      P
    )
)
{
    if(this.walls[i] == false)
        this.walls[i] = "";
    this.walls[i] += "w";
}
if(
    pressedKeys.indexOf("a")>-1 &&
    (
        (                                                                                   //  ----+P
            x_m<(w_x+w_w)&&x_p-5>w_x && y_p-5>w_y && y_m<w_y &&y_p_m>w_y                    //      |
        ) ||                                                                                //      |

        (                                                                                   //      |
            x_m<(w_x+w_w)&&x_p-5>w_x && y_p-5>w_y && y_p<=(w_y+w_h)                         //      |P
        ) ||                                                                                //      |    

        (                                                                                   //      |
            x_m<(w_x+w_w)&&x_p-5>w_x && y_p>(w_y+w_h)&&p_y_m<(w_y+w_h) && y_m+5<(w_y+w_h)   //      |P
        )                                                                                   //  ----+P
    )
)
{
    if(this.walls[i] == false)
        this.walls[i] = "";
    this.walls[i] += "a";
}

代码右侧的评论显示玩家是如何发生碰撞的。

此代码100%有效,每次我想检查碰撞时都会使用它。

希望它有所帮助:)