简体   繁体   English

与多维数组中的对象进行JavaScript冲突检测

[英]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. 我目前正在用p5.js编写一个吃豆人克隆,并遇到了一个问题。 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. 我创建了一个函数,它通过使用多维数组绘制地图,绘制一个1是的墙块,而0是什么。

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循环遍历数组,检查x和y坐标以查看是否存在冲突,但它根本没有注册。这是我用来检测碰撞的代码:

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. 这在Draw()函数中运行,这意味着它每秒循环30次。

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 PacMan控制

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. 键输入请求移动方向dxdy保持一次可以多于一个的方向。 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. 如果dxdy不为0,则首先检查玩家是否排列了一个段落,如果是,则检查一个块是否在行进方向。 If the player is not lined up or blocked set the movement var to 0 如果玩家没有排队或阻挡,则将移动变量设置为0

After checking both x and y directions, then if dx or dy have a value then that must be a valid move. 在检查x和y方向之后,如果dxdy具有值,则必须是有效移动。

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. 从主循环中删除播放器冲突检查代码,并使用当前地图作为2D原始调用播放器更新功能。

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 . 注意我已经改变了玩家移动的方式,我设置每帧的速度(1像素),它必须是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. maps与原始数组相同,但现在更容易阅读和更改。

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. 注意这个if语句如何检查所有边缘,而不仅仅是顶部和左边。 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. 它是为Processing编写的,但是所有内容都应该直接转换为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')
     }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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