SdlDotNet 平台游戏:Collision Rectangle A(玩家)与 Collision Rectangle B(关卡)相交
SdlDotNet Platformer Game: Collision Rectangle A (player) ntersectswith Collision Rectangle B (level)
我正在努力解决我的玩家或敌人的碰撞矩形与 level/wall 之间的碰撞问题。
关卡是一维数组。
这画得很好:一个带有平台的屏幕和关卡周围的一个盒子,所以玩家和敌人应该不能离开竞技场。
在我的 Game_Manager class(处理游戏、更新和碰撞)中绘制关卡、玩家和一个敌人。效果很好,但玩家和敌人可以离开该区域,因为碰撞检测不正确:
在我的 Game_Manager class 中,玩家检测到敌人并在他们碰撞时重置其位置:
if(player.PlayerCollRect.IntersectsWith(enemy.enemyCollRect))
{
player.playerPosition = playerStartPosition;
}
同样:效果很好。
但是:我似乎无法正确地与一维数组(我的关卡)发生碰撞。
而且我似乎在获取关卡中每个 block/tile 的碰撞矩形时遇到问题 - 我的玩家可以出去,我的敌人也可以。
敌人是粉红色的,玩家是绿色的。
现在不使用红色块:
See here what my level looks like and whats happening (.gif)
我试过:
player.PlayerCollRect.IntersectsWith(enemy.EnemyCollRect)
我应该尝试与我的瓷砖碰撞吗?还是以我的完整水平?又如何?
我的代码 Level.cs
public abstract class Level
{
protected byte[,] byteArray;
public Tile[,] tileArray;
public Rectangle levelTileColl;
public Level()
{
CreateTileArray();
tileArray = new Tile[byteArray.GetLength(0), byteArray.GetLength(1)];
CreateWorld();
}
protected abstract void CreateTileArray();
private void CreateWorld()
{
for (int r = 0; r < byteArray.GetLength(0); r++)
{
for (int c = 0; c < byteArray.GetLength(1); c++)
{
if(byteArray[r, c] == 1)
{
tileArray[r, c] = new Tile(new Point(c * 40, r * 40));
//tileArray[r, c] = new Tile(new Point(c * tile.TileWidth, r * tile.TileHeight));
}
}
}
}
public void Draw(Surface showTiles)
{
for (int r = 0; r < byteArray.GetLength(0); r++)
{
for (int c = 0; c < byteArray.GetLength(1); c++)
{
if (tileArray[r, c] != null)
tileArray[r, c].Draw(showTiles);
}
}
}
}
Level01.cs的代码:
public class Level01 : Level
{
protected override void CreateTileArray()
{
byteArray = new Byte[,]
{
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
};
}
}
Tile.cs的代码:
public class Tile
{
private Surface tileImage;
private Point tilePosition;
public Rectangle tileColl;
private int tileWidth = 40;
private int tileHeight = 40;
public int TileWidth
{
get { return tileWidth; }
set { tileWidth = value; }
}
public int TileHeight
{
get { return tileHeight; }
}
public Rectangle TileColl
{
get { return tileColl; }
set { tileColl = value; }
}
public Tile(Point position)
{
tileImage = new Surface("tile.png");
tilePosition = position;
tileColl = new Rectangle(tilePosition.X, tilePosition.Y, tileWidth, tileHeight);
}
public void Draw(Surface showTiles)
{
showTiles.Blit(tileImage, tilePosition);
}
}
--编辑--
好吧,让我换个说法:
我如何检查我的玩家是否 touches/collides/intersectswith 我关卡中 tileArray 中的一个图块?
我有一个 tileArray,如果有碰撞,我想检查与任何 1(见上文)的瓷砖的碰撞。
假设关卡不移动,只有玩家移动,实际上可以通过玩家的X、Y坐标进行碰撞。这使得制作基本上无限大小的关卡成为可能,而不会因必须为每个图块测试碰撞而导致延迟。
您要做的是仅在紧邻玩家的方块中测试碰撞。由于您的玩家与每个方块一样大,因此提供了九个方块来测试碰撞(三个在玩家上方,三个与玩家相同,三个在玩家下方)。
我不知道你的播放器 X 和 Y 是如何存储的,但假设它在 xPos 和 yPos 下的播放器 class 中。对于所有瓷砖,瓷砖宽度为 40,瓷砖高度为 40,但如果您想更改它,请将 allWidth 声明为 40,将 allHeight 声明为 40。现在让我们设置碰撞测试:
for (int x = Math.Floor(player.xPos/allWidth) - 1; x < Math.Floor(player.xPos/allWidth) + 2; x++){
for (int y = Math.Floor(player.yPos/allHeight) - 1; y < Math.Floor(player.xPos/allHeight) + 2; y++){
if (byteArray[x, y] > 0)
player.playerPosition = player.StartPosition;
}
}
那我刚才做了什么?
Math.Floor(player.xPos/allWidth):假设你的玩家的 X 为 62。Math.Floor(player.xPos/allWidth) = Math.Floor(62/40) = 1。因此对于 for 循环,这意味着我们正在查看 byteArray[0,y]、byteArray[1,y] 和 byteArray[2,y]。现在假设你的玩家 Y 在 152。然后 Math.Floor(player.yPos/allHeight) = Math.Floor(152/40) = 3。现在有了循环,我们只想碰撞与 byteArray[0,2], byteArray[0,3], byteArray[0,4], byteArray[1,2], byteArray[1,3], byteArray[1,4], byteArray[2,2], byteArray[2,3] 和 byteArray[2,4].
基本上,您的角色的 X 和 Y 通过四舍五入并除以图块大小转换为 byteArray 上的 X 和 Y 位置:0 到 39 之间的玩家 X 为 0,40 到 79 之间的玩家 X 为1、80到119之间的选手X为2,以此类推。然后你的玩家周围的九个方块只不过是数字 - 只需检查相应的 byteArray[x,y] > 0,如果是,那么你的玩家需要回到他们的起始位置。
我希望这个解释足够好!
在你的管理器中 class 应该有一个不断更新游戏的循环,这意味着关卡、玩家、敌人、射弹……任何在游戏过程中可能发生变化的东西.我知道您使用您使用的库中的 Thick 事件。
与其在管理器中创建和更新所有内容,不如在其中执行属于级别的所有内容。因此,例如在您的经理中而不是:
Level level = new Level();
Sprite player = new Player();
Sprite enemy = new Enemy();
Projectile[] projectiles = new... (and then filled with projectiles)
private void Events_Tick(object sender, TickEventArgs e){
player.update();
enemy.update();
projectiles.update();
...
}
由于精灵(让抛射物留在后面)必须与关卡中的图块交互,因此让它们以这种方式交互可能会有点混乱。为了使它更简单和更干净,您应该在存储 tileArray 的级别 class 中执行此操作。这使得与其交互和更新属于关卡的所有内容变得容易。
所以在你的水平class:
public abstract class Level
{
protected byte[,] byteArray;
public Tile[,] tileArray;
Sprite player = new Player();
Sprite enemy = new Enemy();
public Level()
{
CreateTileArray();
tileArray = new Tile[byteArray.GetLength(0), byteArray.GetLength(1)];
CreateWorld();
}
protected abstract void CreateTileArray();
private void CreateWorld() {...}
public void Draw(Surface showTiles) {...}
public void Update() {
player.Update();
enemy.Update();
}
}
从这一点来看,精灵可以更轻松地与关卡及其中的所有内容进行交互。然后在你的经理 class 你可以这样做,并像这样保持干净:
Level level = new Level();
private void Events_Tick(object sender, TickEventArgs e){
level.Update();
}
因此,为了检查碰撞,我们必须将精灵的坐标与图块的坐标进行比较。在这种情况下,由于您使用库中的函数:'InteractWith',您必须创建碰撞矩形,因为它不适用于您的自定义对象 'Tile'。这意味着我们必须从 'tileArray' 中的所有图块中获取坐标并将它们放在矩形中。让我们调用包含矩形的新数组 'tileCollisionArray'.
在你的关卡中这样 class:
public Tile[,] tileArray;
public Rectangle[,] tileCollisionArray;
public Level()
{
CreateTileArray();
tileArray = new Tile[byteArray.GetLength(0), byteArray.GetLength(1)];
tileCollisionArray = new Rectangle[tileArray.GetLength(0), tileArray.GetLength(1)];
CreateWorld();
//here I convert the coördinates from the tiles to the collision rectangles
for (int x = 0; x < tileArray.GetLength(0); x++) {
for (int y = 0; y < tileArray.GetLength(1); y++) {
tileCollisionArray[x,y] = new Rectangle(tileArray[x,y].GetPosX, tileArray[x,y].GetPosY, tileArray[x,y].GetTileWidth, tileArray[x,y].GetTileHeight);
}
}
!!!确保您在 Tile class 中创建吸气剂,以便我们可以访问这些坐标和维度。!!!
所以现在我们可以使用这个 InteractWith 函数,这使得碰撞更容易。接下来我们必须确保 sprite 可以与 tiles 碰撞,所以它们需要以某种方式链接起来。
同样有多种方法可以做到这一点,但由于关卡中可能有多个 sprite 会与图块发生碰撞,因此最佳做法是检查 sprite 本身是否发生碰撞。根据 DRY,有一个抽象的 class Sprite 是很好的,玩家和敌人从中继承。所以当我在抽象 superclass 中的一个方法中检查碰撞时,那么所有的 subclass 也将具有检查碰撞的能力。
是这样的:
public abstract class Sprite {
public void CheckCollisionSurrounding(Rectangle[,] collisionObjects) { ... }
}
public class Player: Sprite { ... }
public class Enemy: Sprite { ... }
以便我们可以在class关卡中进行下一步:
public abstract class Level {
...
public void Update() {
player.Update();
player.CheckCollisionSurrounding(tileCollisionArray);
enemy.Update();
enemy.CheckCollisionSurrounding(tileCollisionArray);
}
...
}
这就是精灵如何访问碰撞对象,接下来是如何检查碰撞是否为真。我们已经在 Sprite class 中创建了函数 CheckCollisionSurrounding,因此我们将在此处检查实际碰撞。
(更好的做法是保护此方法并将其放在精灵本身的更新函数中,但是您需要通过注入或构造函数将 tileCollisionArray 传递给精灵。但是如果上面的方法有效, 这一步应该很简单。)
接下来,也是最后一步,我们可以做:
public abstract class Sprite {
public void CheckCollisionSurrounding(Rectangle[,] collisionObjects) {
for (int x = 0; x < collisionObjects.GetLength(0); x++)
{
for (int y = 0; y < collisionObjects.GetLength(1); y++)
{
if (collisionObjects[x, y] != null) //saves us from possible null reference exceptions
{
//so now we iterate through every collision object so see if the sprite collides with it and call an action to it if it does collide.
if(PlayerCollRect.IntersectsWithTop(collisionObjects[x,y].tileCollRect)) {
//whatever you want to happen, but let's say you want the sprite at the position of the object when colliding with it:
playerPosition.y = collisionObjects[x,y].GetPosY;
}
//and so on for InteractWithBottom, -Right and -Left
//also keep in to account the dimensions of your own sprite which I haven't here. If the sprites hits left side of tile with it's right side, you have to add the width of the sprite to the calculation.
}
}
}
}
}
最后一段代码不是很干净而且很忙,所以如果它以这种方式工作,最好进行一些重构。
通常这是它应该如何工作并使您的代码干净易懂。希望对你有帮助。
编辑:我看到你现在对你的编辑有疑问。您已经注意只与包含 1 的索引进行交互。它在您的 tileArray 中;如果 byteArray 中该索引为 1,则向 tileArray 添加一个 tile。因此,所有包含 0 的字节都不再使用。
感谢您的投入。我用过 Rectangle1.InteractsWith(Reactangle2)
并且效果很好。
此外,我的播放器不断下落,当它与 Rectangle2
交互时,它得到它的 Position.Y
坐标,因此进入矩形。
谢谢!
我正在努力解决我的玩家或敌人的碰撞矩形与 level/wall 之间的碰撞问题。
关卡是一维数组。
这画得很好:一个带有平台的屏幕和关卡周围的一个盒子,所以玩家和敌人应该不能离开竞技场。
在我的 Game_Manager class(处理游戏、更新和碰撞)中绘制关卡、玩家和一个敌人。效果很好,但玩家和敌人可以离开该区域,因为碰撞检测不正确:
在我的 Game_Manager class 中,玩家检测到敌人并在他们碰撞时重置其位置:
if(player.PlayerCollRect.IntersectsWith(enemy.enemyCollRect))
{
player.playerPosition = playerStartPosition;
}
同样:效果很好。
但是:我似乎无法正确地与一维数组(我的关卡)发生碰撞。 而且我似乎在获取关卡中每个 block/tile 的碰撞矩形时遇到问题 - 我的玩家可以出去,我的敌人也可以。
敌人是粉红色的,玩家是绿色的。 现在不使用红色块: See here what my level looks like and whats happening (.gif)
我试过:
player.PlayerCollRect.IntersectsWith(enemy.EnemyCollRect)
我应该尝试与我的瓷砖碰撞吗?还是以我的完整水平?又如何?
我的代码 Level.cs
public abstract class Level
{
protected byte[,] byteArray;
public Tile[,] tileArray;
public Rectangle levelTileColl;
public Level()
{
CreateTileArray();
tileArray = new Tile[byteArray.GetLength(0), byteArray.GetLength(1)];
CreateWorld();
}
protected abstract void CreateTileArray();
private void CreateWorld()
{
for (int r = 0; r < byteArray.GetLength(0); r++)
{
for (int c = 0; c < byteArray.GetLength(1); c++)
{
if(byteArray[r, c] == 1)
{
tileArray[r, c] = new Tile(new Point(c * 40, r * 40));
//tileArray[r, c] = new Tile(new Point(c * tile.TileWidth, r * tile.TileHeight));
}
}
}
}
public void Draw(Surface showTiles)
{
for (int r = 0; r < byteArray.GetLength(0); r++)
{
for (int c = 0; c < byteArray.GetLength(1); c++)
{
if (tileArray[r, c] != null)
tileArray[r, c].Draw(showTiles);
}
}
}
}
Level01.cs的代码:
public class Level01 : Level
{
protected override void CreateTileArray()
{
byteArray = new Byte[,]
{
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
};
}
}
Tile.cs的代码:
public class Tile
{
private Surface tileImage;
private Point tilePosition;
public Rectangle tileColl;
private int tileWidth = 40;
private int tileHeight = 40;
public int TileWidth
{
get { return tileWidth; }
set { tileWidth = value; }
}
public int TileHeight
{
get { return tileHeight; }
}
public Rectangle TileColl
{
get { return tileColl; }
set { tileColl = value; }
}
public Tile(Point position)
{
tileImage = new Surface("tile.png");
tilePosition = position;
tileColl = new Rectangle(tilePosition.X, tilePosition.Y, tileWidth, tileHeight);
}
public void Draw(Surface showTiles)
{
showTiles.Blit(tileImage, tilePosition);
}
}
--编辑-- 好吧,让我换个说法: 我如何检查我的玩家是否 touches/collides/intersectswith 我关卡中 tileArray 中的一个图块? 我有一个 tileArray,如果有碰撞,我想检查与任何 1(见上文)的瓷砖的碰撞。
假设关卡不移动,只有玩家移动,实际上可以通过玩家的X、Y坐标进行碰撞。这使得制作基本上无限大小的关卡成为可能,而不会因必须为每个图块测试碰撞而导致延迟。
您要做的是仅在紧邻玩家的方块中测试碰撞。由于您的玩家与每个方块一样大,因此提供了九个方块来测试碰撞(三个在玩家上方,三个与玩家相同,三个在玩家下方)。
我不知道你的播放器 X 和 Y 是如何存储的,但假设它在 xPos 和 yPos 下的播放器 class 中。对于所有瓷砖,瓷砖宽度为 40,瓷砖高度为 40,但如果您想更改它,请将 allWidth 声明为 40,将 allHeight 声明为 40。现在让我们设置碰撞测试:
for (int x = Math.Floor(player.xPos/allWidth) - 1; x < Math.Floor(player.xPos/allWidth) + 2; x++){
for (int y = Math.Floor(player.yPos/allHeight) - 1; y < Math.Floor(player.xPos/allHeight) + 2; y++){
if (byteArray[x, y] > 0)
player.playerPosition = player.StartPosition;
}
}
那我刚才做了什么?
Math.Floor(player.xPos/allWidth):假设你的玩家的 X 为 62。Math.Floor(player.xPos/allWidth) = Math.Floor(62/40) = 1。因此对于 for 循环,这意味着我们正在查看 byteArray[0,y]、byteArray[1,y] 和 byteArray[2,y]。现在假设你的玩家 Y 在 152。然后 Math.Floor(player.yPos/allHeight) = Math.Floor(152/40) = 3。现在有了循环,我们只想碰撞与 byteArray[0,2], byteArray[0,3], byteArray[0,4], byteArray[1,2], byteArray[1,3], byteArray[1,4], byteArray[2,2], byteArray[2,3] 和 byteArray[2,4].
基本上,您的角色的 X 和 Y 通过四舍五入并除以图块大小转换为 byteArray 上的 X 和 Y 位置:0 到 39 之间的玩家 X 为 0,40 到 79 之间的玩家 X 为1、80到119之间的选手X为2,以此类推。然后你的玩家周围的九个方块只不过是数字 - 只需检查相应的 byteArray[x,y] > 0,如果是,那么你的玩家需要回到他们的起始位置。
我希望这个解释足够好!
在你的管理器中 class 应该有一个不断更新游戏的循环,这意味着关卡、玩家、敌人、射弹……任何在游戏过程中可能发生变化的东西.我知道您使用您使用的库中的 Thick 事件。
与其在管理器中创建和更新所有内容,不如在其中执行属于级别的所有内容。因此,例如在您的经理中而不是:
Level level = new Level();
Sprite player = new Player();
Sprite enemy = new Enemy();
Projectile[] projectiles = new... (and then filled with projectiles)
private void Events_Tick(object sender, TickEventArgs e){
player.update();
enemy.update();
projectiles.update();
...
}
由于精灵(让抛射物留在后面)必须与关卡中的图块交互,因此让它们以这种方式交互可能会有点混乱。为了使它更简单和更干净,您应该在存储 tileArray 的级别 class 中执行此操作。这使得与其交互和更新属于关卡的所有内容变得容易。
所以在你的水平class:
public abstract class Level
{
protected byte[,] byteArray;
public Tile[,] tileArray;
Sprite player = new Player();
Sprite enemy = new Enemy();
public Level()
{
CreateTileArray();
tileArray = new Tile[byteArray.GetLength(0), byteArray.GetLength(1)];
CreateWorld();
}
protected abstract void CreateTileArray();
private void CreateWorld() {...}
public void Draw(Surface showTiles) {...}
public void Update() {
player.Update();
enemy.Update();
}
}
从这一点来看,精灵可以更轻松地与关卡及其中的所有内容进行交互。然后在你的经理 class 你可以这样做,并像这样保持干净:
Level level = new Level();
private void Events_Tick(object sender, TickEventArgs e){
level.Update();
}
因此,为了检查碰撞,我们必须将精灵的坐标与图块的坐标进行比较。在这种情况下,由于您使用库中的函数:'InteractWith',您必须创建碰撞矩形,因为它不适用于您的自定义对象 'Tile'。这意味着我们必须从 'tileArray' 中的所有图块中获取坐标并将它们放在矩形中。让我们调用包含矩形的新数组 'tileCollisionArray'.
在你的关卡中这样 class:
public Tile[,] tileArray;
public Rectangle[,] tileCollisionArray;
public Level()
{
CreateTileArray();
tileArray = new Tile[byteArray.GetLength(0), byteArray.GetLength(1)];
tileCollisionArray = new Rectangle[tileArray.GetLength(0), tileArray.GetLength(1)];
CreateWorld();
//here I convert the coördinates from the tiles to the collision rectangles
for (int x = 0; x < tileArray.GetLength(0); x++) {
for (int y = 0; y < tileArray.GetLength(1); y++) {
tileCollisionArray[x,y] = new Rectangle(tileArray[x,y].GetPosX, tileArray[x,y].GetPosY, tileArray[x,y].GetTileWidth, tileArray[x,y].GetTileHeight);
}
}
!!!确保您在 Tile class 中创建吸气剂,以便我们可以访问这些坐标和维度。!!!
所以现在我们可以使用这个 InteractWith 函数,这使得碰撞更容易。接下来我们必须确保 sprite 可以与 tiles 碰撞,所以它们需要以某种方式链接起来。
同样有多种方法可以做到这一点,但由于关卡中可能有多个 sprite 会与图块发生碰撞,因此最佳做法是检查 sprite 本身是否发生碰撞。根据 DRY,有一个抽象的 class Sprite 是很好的,玩家和敌人从中继承。所以当我在抽象 superclass 中的一个方法中检查碰撞时,那么所有的 subclass 也将具有检查碰撞的能力。
是这样的:
public abstract class Sprite {
public void CheckCollisionSurrounding(Rectangle[,] collisionObjects) { ... }
}
public class Player: Sprite { ... }
public class Enemy: Sprite { ... }
以便我们可以在class关卡中进行下一步:
public abstract class Level {
...
public void Update() {
player.Update();
player.CheckCollisionSurrounding(tileCollisionArray);
enemy.Update();
enemy.CheckCollisionSurrounding(tileCollisionArray);
}
...
}
这就是精灵如何访问碰撞对象,接下来是如何检查碰撞是否为真。我们已经在 Sprite class 中创建了函数 CheckCollisionSurrounding,因此我们将在此处检查实际碰撞。
(更好的做法是保护此方法并将其放在精灵本身的更新函数中,但是您需要通过注入或构造函数将 tileCollisionArray 传递给精灵。但是如果上面的方法有效, 这一步应该很简单。)
接下来,也是最后一步,我们可以做:
public abstract class Sprite {
public void CheckCollisionSurrounding(Rectangle[,] collisionObjects) {
for (int x = 0; x < collisionObjects.GetLength(0); x++)
{
for (int y = 0; y < collisionObjects.GetLength(1); y++)
{
if (collisionObjects[x, y] != null) //saves us from possible null reference exceptions
{
//so now we iterate through every collision object so see if the sprite collides with it and call an action to it if it does collide.
if(PlayerCollRect.IntersectsWithTop(collisionObjects[x,y].tileCollRect)) {
//whatever you want to happen, but let's say you want the sprite at the position of the object when colliding with it:
playerPosition.y = collisionObjects[x,y].GetPosY;
}
//and so on for InteractWithBottom, -Right and -Left
//also keep in to account the dimensions of your own sprite which I haven't here. If the sprites hits left side of tile with it's right side, you have to add the width of the sprite to the calculation.
}
}
}
}
}
最后一段代码不是很干净而且很忙,所以如果它以这种方式工作,最好进行一些重构。
通常这是它应该如何工作并使您的代码干净易懂。希望对你有帮助。
编辑:我看到你现在对你的编辑有疑问。您已经注意只与包含 1 的索引进行交互。它在您的 tileArray 中;如果 byteArray 中该索引为 1,则向 tileArray 添加一个 tile。因此,所有包含 0 的字节都不再使用。
感谢您的投入。我用过 Rectangle1.InteractsWith(Reactangle2)
并且效果很好。
此外,我的播放器不断下落,当它与 Rectangle2
交互时,它得到它的 Position.Y
坐标,因此进入矩形。
谢谢!