基于 XNA Tile 的游戏:使用 2D 数组中的网格位置检查 2 个对象之间的碰撞
XNA Tile based Game: Checking collision between 2 objects using their grid position inside a 2D array
我想知道是否有人可以帮我弄清楚如何检查二维数组中两个对象之间的碰撞。
我正在尝试制作一个简单的 2D 俯视游戏。该地图由 20x20 二维正方形阵列组成。玩家和敌人都是正方形,各自占据网格内的一个正方形。网格由多个不同类型的正方形组成。到目前为止,我有地板方块(允许玩家和敌人移动)、墙方块(玩家和敌人不能移动过去或沿着)然后是敌人和玩家方块。这是它的屏幕截图:
我在游戏中创建并初始化二维数组Class。使用 0-3 中的数字,我可以 select 每个方块包含的内容:
0- 地板方格 1- 墙方格 2- 敌人方格 3- 玩家方格.
因此,首先我通过简单地使用嵌套 for 循环并检查玩家的上方、下方、左侧和右侧的网格位置来查看 2D 数组中的值是否为 1,让玩家与墙壁方块发生碰撞. 如果不是,那就意味着它不是一堵墙,这意味着玩家可以移动。
但是在检查玩家和敌人之间的碰撞时我不能使用这种方法,因为我只使用二维数组(2 和 3)中的值来选择最初绘制敌人和玩家方块的位置。这意味着他们当前所在的网格位置包含一个值 0.
我想我会通过存储每个玩家和敌人对象的 "grid Position" 来解决这个问题。这样我就可以检查玩家周围的每个网格块,看看它是否等于敌人的网格位置。如果它相等,那么我会将用于将玩家沿该特定方向移动的移动矢量设置为 (0,0),以防止他们沿该方向移动。当敌人不再位于玩家的一格内时,移动矢量将返回其原始值。
我在这方面取得了一些成功,但它似乎只适用于一个敌方对象。一个敌人我从任何角度都过不去,但是其他的我可以直接穿过他们。
注意:似乎屏幕最下方的敌人是玩家广场唯一可以碰撞的敌人。
我使用了断点,我可以看到当我接近任何我可以穿过的敌人时,它实际上会 运行 检查玩家旁边的方格是否是一个敌人,但实际上并不能阻止玩家穿过敌人。
所以我的问题是,我是不是做错了碰撞,有更简单的方法吗?或者如果我的代码中可能存在错误导致它无法工作?
下面是我的一些代码:
第一个示例用于 player/enemy 碰撞。这包含在敌人class中。正如我之前所说,我使用玩家和敌人的网格位置来检查它们是否在彼此的一个正方形内,如果是,那么我将玩家移动矢量设置为 0,0。当它们不再彼此相距一格时,我将运动矢量重置回其原始值。
public void CheckCollision(Player playerObject)
{
//Check above player
if (playerObject.PlayerGridPosition.Y - 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
{
//north is a vector2 variable that I use to add to the players position in order to move them about the screen. I use it to update both the players position
//and the grid position. North, South, East and West are all similar except the values contained in each are for a specific direction
playerObject.north.X = 0;
playerObject.north.Y = 0;
//This bool is used to check for when an enemy is beside and no longer beside the player.
besidePlayer = true;
}
//Check below player
if (playerObject.PlayerGridPosition.Y + 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
{
playerObject.south.X = 0;
playerObject.south.Y = 0;
besidePlayer = true;
}
//Check to right of player
if (playerObject.PlayerGridPosition.Y == gridPosition.Y && playerObject.PlayerGridPosition.X + 1 == gridPosition.X)
{
playerObject.east.X = 0;
playerObject.east.Y = 0;
besidePlayer = true;
}
//This if statement just checks to see if any of the enemies are within a squares space of the player, if they are not then the besidePlayer bool is set to false
else if (playerObject.PlayerGridPosition.Y != gridPosition.Y && playerObject.PlayerGridPosition.X + 1 != gridPosition.X && playerObject.PlayerGridPosition.Y - 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X && playerObject.PlayerGridPosition.Y + 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X)
{
besidePlayer = false;
}
//When an enemy is no longer beside the player then we can reset all the North, South, East and West vector velues back to their original values.
if (besidePlayer == false)
{
playerObject.north.X = 0;
playerObject.north.Y = -1;
playerObject.south.X = 0;
playerObject.south.Y = 1;
playerObject.east.X = 1;
playerObject.east.Y = 0;
}
}
下一段代码用于在二维数组中设置值并创建关卡布局。这也是我创建敌人对象并设置玩家对象位置和网格位置的地方,使用它们在网格上特定 "number" 的行,列来选择绘制它们的位置。
注意:行和列会翻转,否则水平面会向侧面绘制。
public void LoadLevels(int level)
{
//More levels will be added in later.
if(level == 1)
{
//Here I set the values inside the 2D array "grid". It is a 20x20 array.
//0 = Floor Square, 1 = Wall square, 2 = Enemy Square, 3 = Player Square
grid = new int[maxRows, maxCols]
{
{1,1,1,1,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,0,0,0,0,1},
{1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1},
{1,0,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,0,1},
{1,0,1,2,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,0,0,0,1,1,1,0,1,1,1,1},
{1,0,1,0,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1},
{1,1,1,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,1,1,0,1},
{1,1,1,1,1,1,1,1,0,0,0,3,0,0,0,2,0,0,0,1},
{1,0,1,0,0,0,0,1,0,1,0,1,0,1,0,1,1,1,0,1},
{1,2,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,2,1,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,0,1,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1},
{1,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,1,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
//Cycle through the array with a nested if
for (int i = 0; i < maxCols; i++)
{
for (int j = 0; j < maxRows; j++)
{
//If the the value at grid[i, j] is a 2 then create an enemy at that position
if (grid[i, j] == 2)
{
enemyList.Add(new Enemies(Content.Load<Texture2D>("redSquare"), new Vector2(j * squareWidth, i * squareHeight), new Vector2(j, i)));
//Reset the value at this position to 0 so that when the player/enemy moves from this spot, a floor tile will be drawn instead of a blank space.
grid[i, j] = 0;
}
//If the value is 3 then set the players position and grid position to the value based of [i, j] values.
if (grid[i, j] == 3)
{
playerObject.PlayerPosition = new Vector2(i * squareWidth, j * squareHeight);
playerObject.PlayerGridPosition = new Vector2(i, j);
grid[i, j] = 0;
}
}
}
}
if (level == 2)
{
}
}
旁注:您似乎缺少 west 的代码。如果这是无意的,应该很容易解决。
我认为可能发生的情况是,一旦没有一个敌人靠近玩家,它就会让玩家再次向各个方向行走,而不管之前的怪物施加的限制。
如果我是正确的,它会检查屏幕下降位置的每个敌人,并在需要时阻挡玩家。但是,如果任何剩余的敌人不在玩家旁边,则在执行对该敌人的检查并允许玩家再次穿过时,更改将被撤消。这不会发生在最后一个敌人身上,因为他们不再是不相邻的敌人来解除它的阻挡。
尝试更改它,使其在每一帧开始时重置允许的移动,而不是每次它发现怪物不在玩家旁边时。
没有看到完整的代码直接调试很难找到真正的bug在哪里,但是我觉得你的方法太复杂了。
就我个人而言,我会遵循以下logic/pseudocode:
loading () {
load level to array, no need to perform additional modification
}
game_update () {
for each tile within the array {
if ( tile is player ) {
read user's input
temp <- compute the next position of the player
if ( temp is floor ) then { current player position <- temp } // move allowed, update its position
else { the player must stay on its current position } // move rejected
}
if ( tile is enemy ) {
temp <- compute the next position of this enemy
if ( temp is floor ) then { this enemy position <- temp } // move allowed, update its position
else { this enemy must stay on its current position } // move rejected
}
}
}
game_draw () {
for each tile within the array {
draw the current tile
}
}
这样一来,任何非法的举动都会在发生之前被阻止。
However I can't use this method when checking collision between the
player and enemy as I only use the value inside the 2D array(2 and 3)
to choose where to originally draw the enemy and player squares.
也许这暴露了您设计中的缺陷,您应该修复而不是变通。我没有足够的代码来准确说明,但您似乎在 Player class 或另一个网格中复制信息。根据您的描述,每个方块包含值 0-1-2-3 的二维网格完全描述了游戏的状态。为什么不将该网格用作所有内容的 single source of truth?
我想知道是否有人可以帮我弄清楚如何检查二维数组中两个对象之间的碰撞。
我正在尝试制作一个简单的 2D 俯视游戏。该地图由 20x20 二维正方形阵列组成。玩家和敌人都是正方形,各自占据网格内的一个正方形。网格由多个不同类型的正方形组成。到目前为止,我有地板方块(允许玩家和敌人移动)、墙方块(玩家和敌人不能移动过去或沿着)然后是敌人和玩家方块。这是它的屏幕截图:
我在游戏中创建并初始化二维数组Class。使用 0-3 中的数字,我可以 select 每个方块包含的内容: 0- 地板方格 1- 墙方格 2- 敌人方格 3- 玩家方格.
因此,首先我通过简单地使用嵌套 for 循环并检查玩家的上方、下方、左侧和右侧的网格位置来查看 2D 数组中的值是否为 1,让玩家与墙壁方块发生碰撞. 如果不是,那就意味着它不是一堵墙,这意味着玩家可以移动。
但是在检查玩家和敌人之间的碰撞时我不能使用这种方法,因为我只使用二维数组(2 和 3)中的值来选择最初绘制敌人和玩家方块的位置。这意味着他们当前所在的网格位置包含一个值 0.
我想我会通过存储每个玩家和敌人对象的 "grid Position" 来解决这个问题。这样我就可以检查玩家周围的每个网格块,看看它是否等于敌人的网格位置。如果它相等,那么我会将用于将玩家沿该特定方向移动的移动矢量设置为 (0,0),以防止他们沿该方向移动。当敌人不再位于玩家的一格内时,移动矢量将返回其原始值。
我在这方面取得了一些成功,但它似乎只适用于一个敌方对象。一个敌人我从任何角度都过不去,但是其他的我可以直接穿过他们。
注意:似乎屏幕最下方的敌人是玩家广场唯一可以碰撞的敌人。
我使用了断点,我可以看到当我接近任何我可以穿过的敌人时,它实际上会 运行 检查玩家旁边的方格是否是一个敌人,但实际上并不能阻止玩家穿过敌人。
所以我的问题是,我是不是做错了碰撞,有更简单的方法吗?或者如果我的代码中可能存在错误导致它无法工作?
下面是我的一些代码:
第一个示例用于 player/enemy 碰撞。这包含在敌人class中。正如我之前所说,我使用玩家和敌人的网格位置来检查它们是否在彼此的一个正方形内,如果是,那么我将玩家移动矢量设置为 0,0。当它们不再彼此相距一格时,我将运动矢量重置回其原始值。
public void CheckCollision(Player playerObject)
{
//Check above player
if (playerObject.PlayerGridPosition.Y - 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
{
//north is a vector2 variable that I use to add to the players position in order to move them about the screen. I use it to update both the players position
//and the grid position. North, South, East and West are all similar except the values contained in each are for a specific direction
playerObject.north.X = 0;
playerObject.north.Y = 0;
//This bool is used to check for when an enemy is beside and no longer beside the player.
besidePlayer = true;
}
//Check below player
if (playerObject.PlayerGridPosition.Y + 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
{
playerObject.south.X = 0;
playerObject.south.Y = 0;
besidePlayer = true;
}
//Check to right of player
if (playerObject.PlayerGridPosition.Y == gridPosition.Y && playerObject.PlayerGridPosition.X + 1 == gridPosition.X)
{
playerObject.east.X = 0;
playerObject.east.Y = 0;
besidePlayer = true;
}
//This if statement just checks to see if any of the enemies are within a squares space of the player, if they are not then the besidePlayer bool is set to false
else if (playerObject.PlayerGridPosition.Y != gridPosition.Y && playerObject.PlayerGridPosition.X + 1 != gridPosition.X && playerObject.PlayerGridPosition.Y - 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X && playerObject.PlayerGridPosition.Y + 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X)
{
besidePlayer = false;
}
//When an enemy is no longer beside the player then we can reset all the North, South, East and West vector velues back to their original values.
if (besidePlayer == false)
{
playerObject.north.X = 0;
playerObject.north.Y = -1;
playerObject.south.X = 0;
playerObject.south.Y = 1;
playerObject.east.X = 1;
playerObject.east.Y = 0;
}
}
下一段代码用于在二维数组中设置值并创建关卡布局。这也是我创建敌人对象并设置玩家对象位置和网格位置的地方,使用它们在网格上特定 "number" 的行,列来选择绘制它们的位置。
注意:行和列会翻转,否则水平面会向侧面绘制。
public void LoadLevels(int level)
{
//More levels will be added in later.
if(level == 1)
{
//Here I set the values inside the 2D array "grid". It is a 20x20 array.
//0 = Floor Square, 1 = Wall square, 2 = Enemy Square, 3 = Player Square
grid = new int[maxRows, maxCols]
{
{1,1,1,1,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,0,0,0,0,1},
{1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1},
{1,0,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,0,1},
{1,0,1,2,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,0,0,0,1,1,1,0,1,1,1,1},
{1,0,1,0,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1},
{1,1,1,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,1,1,0,1},
{1,1,1,1,1,1,1,1,0,0,0,3,0,0,0,2,0,0,0,1},
{1,0,1,0,0,0,0,1,0,1,0,1,0,1,0,1,1,1,0,1},
{1,2,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,2,1,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1},
{1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
{1,0,1,0,1,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1},
{1,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,1,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
//Cycle through the array with a nested if
for (int i = 0; i < maxCols; i++)
{
for (int j = 0; j < maxRows; j++)
{
//If the the value at grid[i, j] is a 2 then create an enemy at that position
if (grid[i, j] == 2)
{
enemyList.Add(new Enemies(Content.Load<Texture2D>("redSquare"), new Vector2(j * squareWidth, i * squareHeight), new Vector2(j, i)));
//Reset the value at this position to 0 so that when the player/enemy moves from this spot, a floor tile will be drawn instead of a blank space.
grid[i, j] = 0;
}
//If the value is 3 then set the players position and grid position to the value based of [i, j] values.
if (grid[i, j] == 3)
{
playerObject.PlayerPosition = new Vector2(i * squareWidth, j * squareHeight);
playerObject.PlayerGridPosition = new Vector2(i, j);
grid[i, j] = 0;
}
}
}
}
if (level == 2)
{
}
}
旁注:您似乎缺少 west 的代码。如果这是无意的,应该很容易解决。
我认为可能发生的情况是,一旦没有一个敌人靠近玩家,它就会让玩家再次向各个方向行走,而不管之前的怪物施加的限制。
如果我是正确的,它会检查屏幕下降位置的每个敌人,并在需要时阻挡玩家。但是,如果任何剩余的敌人不在玩家旁边,则在执行对该敌人的检查并允许玩家再次穿过时,更改将被撤消。这不会发生在最后一个敌人身上,因为他们不再是不相邻的敌人来解除它的阻挡。
尝试更改它,使其在每一帧开始时重置允许的移动,而不是每次它发现怪物不在玩家旁边时。
没有看到完整的代码直接调试很难找到真正的bug在哪里,但是我觉得你的方法太复杂了。
就我个人而言,我会遵循以下logic/pseudocode:
loading () {
load level to array, no need to perform additional modification
}
game_update () {
for each tile within the array {
if ( tile is player ) {
read user's input
temp <- compute the next position of the player
if ( temp is floor ) then { current player position <- temp } // move allowed, update its position
else { the player must stay on its current position } // move rejected
}
if ( tile is enemy ) {
temp <- compute the next position of this enemy
if ( temp is floor ) then { this enemy position <- temp } // move allowed, update its position
else { this enemy must stay on its current position } // move rejected
}
}
}
game_draw () {
for each tile within the array {
draw the current tile
}
}
这样一来,任何非法的举动都会在发生之前被阻止。
However I can't use this method when checking collision between the player and enemy as I only use the value inside the 2D array(2 and 3) to choose where to originally draw the enemy and player squares.
也许这暴露了您设计中的缺陷,您应该修复而不是变通。我没有足够的代码来准确说明,但您似乎在 Player class 或另一个网格中复制信息。根据您的描述,每个方块包含值 0-1-2-3 的二维网格完全描述了游戏的状态。为什么不将该网格用作所有内容的 single source of truth?