【问题标题】:SdlDotNet Platformer Game: Collision Rectangle A (player) ntersectswith Collision Rectangle B (level)SdlDotNet 平台游戏:碰撞矩形 A(玩家)与碰撞矩形 B(关卡)相交
【发布时间】:2017-08-08 18:52:40
【问题描述】:

我正在努力解决我的玩家或敌人的碰撞矩形与关卡/墙壁之间的碰撞。

关卡是一维数组。

这画得很好:一个带有平台的屏幕和一个围绕关卡的盒子,所以玩家和敌人不应该离开竞技场。

在我的 Game_Manager 类(处理游戏、更新和碰撞)中绘制关卡、玩家和一个敌人。效果很好,但是玩家和敌人可以离开该区域,因为碰撞检测不对:

在我的 Game_Manager 类中,玩家检测到敌人并在它们碰撞时重置其位置:

          if(player.PlayerCollRect.IntersectsWith(enemy.enemyCollRect))
        {
            player.playerPosition = playerStartPosition;
        }

再次:效果很好。

但是:我似乎无法正确理解与一维数组(我的关卡)的冲突。 而且我似乎在获取关卡中每个块/瓷砖的碰撞矩形时遇到了问题 - 我的玩家可以出去,我的敌人也可以。

敌人是粉红色的,玩家是绿色的。 红色块现在不使用: 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);
    }
}

-- 编辑-- 好吧,让我换一种说法: 如何检查我的玩家是否在我的关卡中与我的 tileArray 中的图块接触/碰撞/相交? 我有一个 tileArray,如果有碰撞,我想检查与任何 1 的瓷砖的碰撞(见上文)。

【问题讨论】:

    标签: c# arrays sdl collision-detection rectangles


    【解决方案1】:

    在你的管理器类中应该有一个循环不断更新游戏,这意味着关卡、玩家、敌人、射弹……只是在游戏中可能发生任何变化的任何东西。我知道你使用了你使用的库中的厚事件。

    与其在管理器中创建和更新所有内容,不如在其中执行属于该级别的所有内容。因此,例如在您的经理中,而不是:

    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 的关卡类中执行此操作。这使得与其交互和更新属于该关卡的所有内容变得容易。

    所以在你的级别课程中:

    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();
        }
    }
    

    从这一点开始,精灵与关卡及其中的所有内容进行交互变得更加容易。然后在你的经理类中,你可以这样做,并像这样保持干净:

    Level level = new Level();
    
    private void Events_Tick(object sender, TickEventArgs e){
        level.Update();
    }
    

    因此,为了检查碰撞,我们必须将精灵的坐标与图块的坐标进行比较。在这种情况下,由于您使用库中的函数:“InteractWith”,因此您必须创建碰撞矩形,因为它不适用于您的自定义对象“Tile”。这意味着我们必须从 'tileArray' 中的所有图块中获取坐标,并将它们放在矩形中。我们将包含矩形的新数组称为“tileCollisionArray”。

    在你的关卡课程中这样:

    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 类中创建 getter,以便我们可以访问这些坐标和尺寸。!!!

    所以现在我们可以使用这个 InteractWith 函数,这使得碰撞变得更加容易。接下来我们必须确保精灵可以与瓷砖碰撞,所以它们需要以某种方式链接。

    同样有多种方法可以做到这一点,但由于我们可能在关卡中有多个精灵会与图块发生碰撞,因此最佳做法是检查精灵本身是否存在碰撞。根据 DRY,最好有一个抽象类 Sprite 来继承玩家和敌人。因此,当我在抽象超类中的方法中检查碰撞时,所有子类也将具有检查碰撞的能力。

    是这样的:

    public abstract class Sprite {
        public void CheckCollisionSurrounding(Rectangle[,] collisionObjects) { ... }
    }
    
    public class Player: Sprite { ... }
    public class Enemy: Sprite { ... }
    

    这样我们就可以在关卡类中做下一步了:

    public abstract class Level {
        ...
        public void Update() {
            player.Update();
            player.CheckCollisionSurrounding(tileCollisionArray);
            enemy.Update();
            enemy.CheckCollisionSurrounding(tileCollisionArray);
        }
        ...
    }
    

    这就是精灵如何访问碰撞对象的方法,接下来是如何检查碰撞是否为真。我们已经在 Sprite 类中创建了 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,则将 tile 添加到 tileArray。因此,所有包含 0 的字节都不再使用。

    【讨论】:

      【解决方案2】:

      假设关卡不移动,只有玩家移动,你实际上可以通过玩家 X 和 Y 坐标进行碰撞。这使得制作一个基本上无限大小的关卡成为可能,而不会因为必须测试每个图块的碰撞而导致延迟。

      您所做的是仅在紧邻玩家的图块中测试碰撞。由于您的玩家与每个方块一样大,因此有九个瓷砖可以测试碰撞(三个在玩家上方,三个与玩家处于同一水平,三个在玩家下方)。

      我不知道您的播放器 X 和 Y 是如何存储的,但假设它位于 xPos 和 yPos 下的 Player 类中。对于所有瓦片,瓦片宽度为 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,如果是,那么你的玩家需要回到他们的起始位置。

      我希望这已经解释得足够清楚了!

      【讨论】:

      • 这不是我想要的方式。这个想法看起来不错,我看到了逻辑,但奇怪的是我不希望它检测碰撞:当我跳跃时,我可以通过任何平台而无需任何位置重置。当我稍后跳跃并进入一个空白空间(没有平台,没有碰撞)时,检测到碰撞并且我的玩家的位置被重置。
      【解决方案3】:

      感谢您的意见。我用过Rectangle1.InteractsWith(Reactangle2),效果很好。 我的播放器也在不断下降,当它与Rectangle2 交互时,它得到了Position.Y 坐标,因此进入了矩形。

      谢谢!

      【讨论】:

        猜你喜欢
        • 2023-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-28
        • 2010-09-28
        • 1970-01-01
        • 2014-03-18
        • 1970-01-01
        相关资源
        最近更新 更多