Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create obstacles in canvas

I am trying to make a simple platformer like game.The code i am using is shown below

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>

The problem is that sometimes when the speed of the "player" is high it can go through obstacles like the below image. How can i stop that from happening ?

enter image description here

So what i want is that the player should stop right as he reaches the obstacle and not pass through it

like image 353
Akshay Avatar asked Jan 05 '16 09:01

Akshay


1 Answers

There is a complication when collision testing objects that are moving quickly

You must determine if your player and obstacle intersected at any time during the move -- even if the player has moved beyond the obstacle by the end of the move. Therefore you must account for the complete path the player has moved from start to end of the move.

enter image description here ... enter image description here

Then you can check if the player ever intersected the obstacle during the move by checking if the player's track intersects the obstacle.

enter image description here

A relatively efficient method for testing collisions involving fast moving objects

  1. Define the 3 line segments that connect the 3 vertices of the player's starting rectangle that are closest to the player's ending rectangle.

enter image description here

  1. For any of the 3 lines that intersect an obstacle, calculate the distance of the line segment to the obstacle. Select the line that has the shortest distance between starting vertex and the obstacle.

enter image description here enter image description here

  1. Calculate the "x" & "y" distances of the selected line segment.

    var dx = obstacleIntersection.x - start.x;
    var dy = obstacleIntersection.y - start.y;
    
  2. Move the player from their starting position by the distance calculated in #3. This results in the player moving to the spot where it first collided with the obstacle.

    player.x += dx;
    player.y += dy;
    

enter image description here

Code and Demo:

Useful functions in the code:

  • setPlayerVertices determines the 3 line segments that connect the 3 vertices of the player's starting rectangle that are closest to the player's ending rectangle.

  • hasCollided finds the shortest segment connecting a vertex from the player's starting position with the collision point on the obstacle.

  • line2lineIntersection finds the intersection point (if any) between 2 lines. This is used to test for an intersection between a start-to-end segment (from #1) and any of the 4 line segments that make up the obstacle rectangle. Attribution: This function is adapted from Paul Bourke's useful treatice on intersections.

enter image description here

Here is example code and a Demo showing how to halt the player at the collision point on the obstacle:

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();
}
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>
<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>
like image 72
markE Avatar answered Oct 20 '22 21:10

markE