I'm currently coding a Pac-man clone with p5.js, and have ran into an issue. I have created a function which draws the map by using a multi-dimensional array, drawing a wall block where a 1 is, and nothing where a 0 is.
This works fine, however i'm struggling to detect collision between the player and the walls. I have tried to use a for loop to go through the array, checking the x and y co-ordinates to see if there is a collision, however it doesn't register at all.This is the code i have used to detect collision:
for(i=0;i<walls.length;i++){
walls[i].draw();
if(player.x > walls[i].x && player.x < walls[i].x + gridsize && player.y > walls[i].y && player.y < walls[i].y + gridsize){
console.log('collision')
}
}
I can't see where the issue is here, as it seems to have worked in other programs.
This runs in the Draw() function, meaning it loops 30 times a second.
This is the full code, incase the issue lies elsewhere:
var gridsize = 20;
var walls = [];
var dots = [];
var player;
var score =0;
var maps = [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,1,1,0,1,1,1,1,1,1,0,1,1,0,1],
[1,0,1,1,0,0,0,0,0,0,0,0,1,1,0,1],
[1,0,0,0,0,1,1,1,1,1,1,0,0,0,0,1],
[1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1],
[1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,1],
[1,0,1,0,0,1,0,0,0,0,1,0,0,1,0,1],
[1,0,1,0,0,1,0,0,0,0,1,0,0,1,0,1],
[1,0,1,0,0,1,1,1,1,1,1,0,0,1,0,1],
[1,0,1,0,0,0,0,2,0,0,0,0,0,1,0,1],
[1,0,1,1,1,0,1,1,1,1,0,1,1,1,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,1,1,1,1,1,1,1,1,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]];
function setup(){
createCanvas(320,320);
frameRate(30);
createMap();
}
function draw(){
background(51);
for(i=0;i<walls.length;i++){
walls[i].draw();
if(player.x > walls[i].x && player.x < walls[i].x + gridsize && player.y
> walls[i].y && player.y < walls[i].y + gridsize){
console.log('collision')
}
}
fill('white');
text('Score: ' + score, 5,10);
for(i=0;i<dots.length;i++){
if(player.x == dots[i].x && player.y == dots[i].y && dots[i].collect ==
false){
dots[i].collect = true;
score++;
}
dots[i].draw();
}
player.update();
player.draw();
}
function Block(x,y){
this.x = x;
this.y = y;
this.draw = function(){
fill('black');
rect(this.x,this.y,gridsize,gridsize);
}
}
function Dot(x,y){
this.x = x;
this.y = y;
this.collect = false;
this.draw = function(){
if(!this.collect){
fill('yellow');
ellipse(this.x+gridsize/2,this.y+gridsize/2,gridsize/3,gridsize/3);
}else if(this.collect){
noFill();
noStroke();
ellipse(this.x+gridsize/2,this.y+gridsize/2,gridsize/3,gridsize/3);
}
}
}
function Player(x,y){
this.x = x;
this.y = y;
this.update = function(){
if(keyIsDown(UP_ARROW) && frameCount%5 == 0){
player.y -= gridsize;
}
if(keyIsDown(DOWN_ARROW) && frameCount%5 == 0){
player.y += gridsize;
}
if(keyIsDown(LEFT_ARROW) && frameCount%5 == 0){
player.x -= gridsize;
}
if(keyIsDown(RIGHT_ARROW) && frameCount%5 == 0){
player.x += gridsize;
}
}
this.draw = function(){
fill('blue');
ellipse(this.x+gridsize/2,this.y+gridsize/2,gridsize/1.2,gridsize/1.2);
}
}
function createMap(){
for(i=0;i<maps.length;i++){
for(j=0;j<maps[i].length;j++){
if (maps[i][j] == 1){
walls.push(new Block(j*gridsize,i*gridsize));
}else if(maps[i][j] == 0){
dots.push(new Dot(j*gridsize,i*gridsize))
}else if(maps[i][j] = 2){
player = new Player(j*gridsize,i*gridsize)
}
}
}
}
I presume the issue lies with the fact that the walls are stored in an array, however i have done very similar programs in which the same code works.
PacMan controls
The best way to check for this type of map is to use the player's input.
The player must line up with the walls so assuming the player position is relative to the top left and the player is one map unit wide and deep.
Key input requests a direction to move dx, dy hold the directions which could be more than one at a time. If dx or dy are not 0 then first check if the player is lined up with a passage, if so then check if a block is in the direction of travel. If the player is not lined up or blocked set the movement var to 0
After checking both x and y directions, then if dx or dy have a value then that must be a valid move.
Code changes
Remove the player collision checking code from the main loop and call the player update function with the current map as the 2D original.
player.update(maps); // move the player
Change the Player and update function
function Player(x,y){
this.x = x;
this.y = y;
var dx = 0; // hold current movement
var dy = 0;
const speed = 1; // per Frame pixel speed best as an integer (whole number) and evenly divisible into gridSize
// need the map so that must be passed to the update function
this.update = function(map){
// assuming keys are held to move up to stop
dx = 0; // stop by default
dy = 0;
if (keyIsDown(UP_ARROW)) { dy = -speed }
if (keyIsDown(DOWN_ARROW)) { dy = speed }
if (keyIsDown(LEFT_ARROW)) { dx = -speed }
if (keyIsDown(RIGHT_ARROW)){ dx = speed }
// get player map coords
var x = Math.floor(this.x / gridSize); // get map coord
var y = Math.floor(this.y / gridSize); // get map coord
// the two if statement are best aas function
// so you can select which one to call first. Depending on the latest
// new keys down and if the result allows movement in that
// direction then set the other direction to zero.
if (dy !== 0) { // is moving up or down?
if (this.y % gridsize === 0) { // only if lined up
if (dy > 0){ // is moving down
if (map[y + 1][x] === 1) { // down blocked
dy = 0;
}
}else if (map[y - 1][x] === 1) { // up blocked
dy = 0;
}
} else { // block move if not lined up with passage
dy = 0;
}
}
if(dx !== 0){ // is moving left or right?
if (this.x % gridsize === 0) { // only if lined up
if (dx > 0) { // is moving right
if (map[y][x + 1] === 1) { // right blocked
dx = 0;
}
} else if (map[y][x - 1] === 1) { // left blocked
dx = 0;
}
} else { // block move if not lined up with passage
dx = 0;
}
}
// could have two moves, but can only move left right or up down
// you need to add some input smarts to pick which one
// this just favours up down
if(dy !== 0) { dx = 0 };
// only valid moves will come through the filter above.
// so move the player.
this.x += dx;
this.y += dy;
}
Adding more smarts
Note I have changed the way the player moves, I set a speed per frame (1 pixel) that must be an even divisor of gridSize.
The code above is the simplest implementation. This type of games needs some extra smarts in controls. You should check in the direction of the newest key down. Ie if the player traveling down and right is pressed then moving right should have priority. If player moving right and left is pressed then you should move left, not keep moving right.
Extras
While looking at this question I wanted to visualize the map. Maps as arrays are painful to create and modify, and very hard to find mistakes in. Much easier as a a set of strings that gets converted to an array at run time.
As i have done the conversion no point wasting it. maps is identical to the original array but now easier to read and change.
const maps = [
"################",
"# #",
"# ## ###### ## #",
"# ## ## #",
"# ###### #",
"#### ####",
"# ## ## #",
"# # # # # #",
"# # # # # #",
"# # ###### # #",
"# # 2 # #",
"# ### #### ### #",
"# #",
"# ######## #",
"# #",
"################"
].map(row => row.split("").map(c => c === "#" ? 1 : c === " " ? 0 : 2));
I'm not quite sure why you're using rectangle-rectangle collision detection when you could just use grid-based collision detection. You could just use the array directly.
But since you are using rectangle-rectangle collision, this line looks a little bit off:
if(player.x > walls[i].x && player.x < walls[i].x + gridsize && player.y > walls[i].y && player.y < walls[i].y + gridsize){
You're checking whether the left edge of the player is inside the wall and whether the top edge of the player is inside the wall. But you aren't detecting the other edges. Usually you'd want to do something like this:
if(rectOneRight > rectTwoLeft && rectOneLeft < rectTwoRight && rectOneBottom > rectTwoTop && rectOneTop < rectTwoBottom){
Notice how this if statement checks all of the edges, not just the top and left. But like I said, you might be better off just using grid collision detection, since you already have a grid of walls.
Shameless self-promotion: here is a tutorial on collision detection. It's written for Processing, but everything should translate pretty directly to P5.js.
if the player is not sprite here then point-in-rect collision detection will be appropriate here.
// point in rect collision detection
function pointInRect (x, y, rect) {
return inRange(x, rect.x, rect.x + gridsize) &&
inRange(y, rect.y, rect.y + gridsize);
}
// check a value is in range or not
function inRange (value, min, max) {
return value >= Math.min(min, max) && value <= Math.max(min, max);
}
// checking player is hitting the wall or not
if(pointInRect(player.x,player.y,walls[i].x,walls[i].y))
{
console.log('collision')
}
OK, so I am making a Pacman game using HTML5 . the problem is whenever I hit one of the brick blocks I want the sprite to stop moving but it keeps going until it hits the left most brick object.
how do I fix this? please help... here is the code I'm using to make the sprite stop.
here is all my code, if you have time, please parse it, and tell me what I have done wrong.
function init(){
var canvas=document.getElementById("ctx");
var ctx = canvas.getContext("2d");
var player = {sx:6,sy:6,sw:15,sh:15,x:230,y:377,w:20,h:20}
var ss = new Image();
ss.src="SS.png";
var right=false,left= true,up = false,down = false
var b = [{x:0,y:0,w:25,h:((canvas.height/2)-25)},{x:0,y:((canvas.height/2)),w:25,h:((canvas.height/2))},{x:50,y:25,w:50,h:50},{x:125,y:25,w:75,h:50},{x:225,y:0,w:25,h:75},{x:275,y:25,w:75,h:50},{x:375,y:25,w:50,h:50},{x:50,y:100,w:50,h:25},{x:125,y:100,w:25,h:125},{x:125,y:150,w:75,h:25},{x:175,y:100,w:125,h:25},{x:225,y:125,w:25,h:50},{x:325,y:100,w:25,h:125},{x:275,y:150,w:75,h:25},{x:375,y:100,w:50,h:25},{x:25,y:150,w:75,h:75},{x:375,y:150,w:75,h:75},{x:375,y:250,w:75,h:75},{x:25,y:250,w:75,h:75},{x:125,y:250,w:25,h:75},{x:325,y:250,w:25,h:75},{x:175,y:300,w:125,h:25},{x:225,y:325,w:25,h:50},{x:50,y:350,w:50,h:25},{x:75,y:350,w:25,h:75},{x:125,y:350,w:75,h:25},{x:275,y:350,w:75,h:25},{x:375,y:350,w:50,h:25},{x:375,y:350,w:25,h:75},{x:25,y:400,w:25,h:25},{x:125,y:400,w:25,h:75},{x:50,y:450,w:150,h:25},{x:275,y:450,w:150,h:25},{x:325,y:400,w:25,h:50},{x:425,y:400,w:25,h:25},{x:175,y:400,w:125,h:25},{x:225,y:425,w:25,h:50},{x:450,y:0,w:50,h:((canvas.height/2)-25)},{x:450,y:(canvas.height/2),w:50,h:((canvas.height/2))}];
function gen(){
for(var i=0;i<b.length;i++){
ctx.fillStyle="blue"
ctx.fillRect(b[i].x,b[i].y,b[i].w,b[i].h)
}
ctx.drawImage(ss,player.sx,player.sy,player.sw,player.sh,player.x,player.y,player.w,player.h)
}
function move(){
for(var i=0;i<b.length;i++){
//((a.x + a.width) < b.x)
if(left &&
player.x > b[i].x && (player.x + player.w) < (b[i].x + b[i].w) &&
player.y > b[i].y && (player.y + player.h) < (b[i].y + b[i].h)) {
// here you can tell that the user is colliding an object
player.x-=1
}
else {
}
}
}
function animate(){
ctx.save()
ctx.clearRect(0,0,canvas.width,canvas.width);
gen()
move()
ctx.restore();
}
var ani = setInterval(animate, 30)
}
window.addEventListener("load",function(){
init()
})
First, I can see a problem with the first part of your if condition:
left = true && ...
Should be
left === true && ...
Or even better
left && ...
Now for the collision part it usually is top-left or in the middle of the object
I'd suggest this top-left origin collision check:
if(left &&
(player.x >= b[i].x && player.x + player.w <= b[i].x + b[i].w) &&
(player.y >= b[i].y && player.y + player.h <= b[i].y + b[i].h) {
// here you can tell that the user is colliding an object
}
It checks several cases, this part
(player.x >= b[i].x && player.x + player.w <= b[i].x + b[i].w)
Will meet requirements if the player's x (with its width component) inside the occupied x range of the current object
The second part
(player.y >= b[i].y && player.y + player.h <= b[i].y + b[i].h)
Will meet requirements if the player's y (with its height component) is inside the occupied y range of the current object.
It will only execute the if statement if the condition is satisfied for both of the above cases.
You can tell if you should reposition the player on the left or on the right, by substracting the players x component to the object's x component, same goes for top or bottom with y component. The previous sentence is only valid if you move in a grid cell by cell.