简体   繁体   中英

JavaScript collision detection with objects in a multi-dimensional array

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 aa 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')
     }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM