JavaScript 多维数组中对象的碰撞检测
JavaScript collision detection with objects in a multi-dimensional array
我目前正在使用 p5.js 编写吃豆人克隆代码,并且 运行 遇到了问题。我创建了一个函数,它使用多维数组绘制地图,在 1 所在的位置绘制墙块,在 0 所在的位置不绘制任何内容。
这很好用,但是我很难检测到玩家和墙壁之间的碰撞。我尝试使用 for 循环遍历数组,检查 x 和 y 坐标以查看是否存在碰撞,但是它没有在 all.This 处注册是我用来检测的代码碰撞:
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')
}
}
我看不出问题出在哪里,因为它似乎在其他程序中有效。
这在 Draw() 函数中运行,这意味着它每秒循环 30 次。
这是完整代码,以防问题出在其他地方:
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)
}
}
}
}
我认为问题在于墙壁存储在一个数组中,但是我已经完成了非常相似的程序,其中的代码相同。
我不太清楚您为什么要使用矩形-矩形碰撞检测,而您可以只使用基于网格的碰撞检测。你可以直接使用数组。
但是由于您使用的是矩形-矩形碰撞,这条线看起来有点不对劲:
if(player.x > walls[i].x && player.x < walls[i].x + gridsize && player.y > walls[i].y && player.y < walls[i].y + gridsize){
您正在检查播放器的左边缘是否在墙内以及播放器的上边缘是否在墙内。但是您没有检测到其他边缘。通常你想做这样的事情:
if(rectOneRight > rectTwoLeft && rectOneLeft < rectTwoRight && rectOneBottom > rectTwoTop && rectOneTop < rectTwoBottom){
请注意此 if
语句如何检查所有边缘,而不仅仅是顶部和左侧。但就像我说的,你最好只使用网格碰撞检测,因为你已经有了一个墙网格。
无耻的自我推销:here是一个关于碰撞检测的教程。它是为处理而编写的,但所有内容都应该直接转换为 P5.js.
如果玩家在这里不是 sprite,那么此处的点碰撞检测将是合适的。
// 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')
}
吃豆人控制
检查此类地图的最佳方法是使用玩家的输入。
玩家必须与墙对齐,所以假设玩家位置相对于左上角,玩家的宽度和深度为一个地图单位。
键输入请求移动方向dx
,dy
保持方向,一次可以有多个方向。如果 dx
或 dy
不为 0,则首先检查玩家是否与通道对齐,如果是,则检查行进方向是否有方块。如果玩家未排队或被阻挡,则将 movement var 设置为 0
在检查了 x 和 y 方向后,如果 dx
或 dy
有一个值,那么那一定是一个有效的移动。
代码更改
从主循环中删除播放器碰撞检查代码,并以当前地图作为2D原图调用播放器更新函数。
player.update(maps); // move the player
更改播放器和更新功能
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;
}
添加更多智能
注意我已经改变了玩家移动的方式,我设置的每帧速度(1 像素)必须是 gridSize
的偶数。
上面的代码是最简单的实现。这种类型的游戏需要一些额外的控制智能。您应该检查最新键的方向。也就是说,如果玩家向下和向右移动被按下,那么向右移动应该有优先权。如果玩家左右移动被按下,那么你应该向左移动,而不是一直向右移动。
额外内容
在看这个问题时,我想可视化地图。作为数组的映射很难创建和修改,而且很难发现其中的错误。作为一组在 运行 时间转换为数组的字符串要容易得多。
因为我已经完成了转换,所以没有必要浪费它。 maps
与原始数组相同,但现在更易于阅读和更改。
const maps = [
"################",
"# #",
"# ## ###### ## #",
"# ## ## #",
"# ###### #",
"#### ####",
"# ## ## #",
"# # # # # #",
"# # # # # #",
"# # ###### # #",
"# # 2 # #",
"# ### #### ### #",
"# #",
"# ######## #",
"# #",
"################"
].map(row => row.split("").map(c => c === "#" ? 1 : c === " " ? 0 : 2));
我目前正在使用 p5.js 编写吃豆人克隆代码,并且 运行 遇到了问题。我创建了一个函数,它使用多维数组绘制地图,在 1 所在的位置绘制墙块,在 0 所在的位置不绘制任何内容。
这很好用,但是我很难检测到玩家和墙壁之间的碰撞。我尝试使用 for 循环遍历数组,检查 x 和 y 坐标以查看是否存在碰撞,但是它没有在 all.This 处注册是我用来检测的代码碰撞:
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')
}
}
我看不出问题出在哪里,因为它似乎在其他程序中有效。 这在 Draw() 函数中运行,这意味着它每秒循环 30 次。
这是完整代码,以防问题出在其他地方:
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)
}
}
}
}
我认为问题在于墙壁存储在一个数组中,但是我已经完成了非常相似的程序,其中的代码相同。
我不太清楚您为什么要使用矩形-矩形碰撞检测,而您可以只使用基于网格的碰撞检测。你可以直接使用数组。
但是由于您使用的是矩形-矩形碰撞,这条线看起来有点不对劲:
if(player.x > walls[i].x && player.x < walls[i].x + gridsize && player.y > walls[i].y && player.y < walls[i].y + gridsize){
您正在检查播放器的左边缘是否在墙内以及播放器的上边缘是否在墙内。但是您没有检测到其他边缘。通常你想做这样的事情:
if(rectOneRight > rectTwoLeft && rectOneLeft < rectTwoRight && rectOneBottom > rectTwoTop && rectOneTop < rectTwoBottom){
请注意此 if
语句如何检查所有边缘,而不仅仅是顶部和左侧。但就像我说的,你最好只使用网格碰撞检测,因为你已经有了一个墙网格。
无耻的自我推销:here是一个关于碰撞检测的教程。它是为处理而编写的,但所有内容都应该直接转换为 P5.js.
如果玩家在这里不是 sprite,那么此处的点碰撞检测将是合适的。
// 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')
}
吃豆人控制
检查此类地图的最佳方法是使用玩家的输入。
玩家必须与墙对齐,所以假设玩家位置相对于左上角,玩家的宽度和深度为一个地图单位。
键输入请求移动方向dx
,dy
保持方向,一次可以有多个方向。如果 dx
或 dy
不为 0,则首先检查玩家是否与通道对齐,如果是,则检查行进方向是否有方块。如果玩家未排队或被阻挡,则将 movement var 设置为 0
在检查了 x 和 y 方向后,如果 dx
或 dy
有一个值,那么那一定是一个有效的移动。
代码更改
从主循环中删除播放器碰撞检查代码,并以当前地图作为2D原图调用播放器更新函数。
player.update(maps); // move the player
更改播放器和更新功能
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;
}
添加更多智能
注意我已经改变了玩家移动的方式,我设置的每帧速度(1 像素)必须是 gridSize
的偶数。
上面的代码是最简单的实现。这种类型的游戏需要一些额外的控制智能。您应该检查最新键的方向。也就是说,如果玩家向下和向右移动被按下,那么向右移动应该有优先权。如果玩家左右移动被按下,那么你应该向左移动,而不是一直向右移动。
额外内容
在看这个问题时,我想可视化地图。作为数组的映射很难创建和修改,而且很难发现其中的错误。作为一组在 运行 时间转换为数组的字符串要容易得多。
因为我已经完成了转换,所以没有必要浪费它。 maps
与原始数组相同,但现在更易于阅读和更改。
const maps = [
"################",
"# #",
"# ## ###### ## #",
"# ## ## #",
"# ###### #",
"#### ####",
"# ## ## #",
"# # # # # #",
"# # # # # #",
"# # ###### # #",
"# # 2 # #",
"# ### #### ### #",
"# #",
"# ######## #",
"# #",
"################"
].map(row => row.split("").map(c => c === "#" ? 1 : c === " " ? 0 : 2));