简单的平台游戏碰撞算法出现故障

本文关键字:故障 算法 碰撞 平台 游戏 简单 | 更新日期: 2023-09-27 18:17:11

我有一个小故障与这个简单的碰撞算法。我正在编写codingmadeeasy的rpg游戏教程,这种碰撞与我在之前的平台游戏中所做的算法碰撞非常相似。我在之前的游戏中也遇到过这个问题,只是不记得怎么修复了。我有一张地图,它有很多贴图,有些被标记为实体。他们的更新方法检查与玩家的碰撞。如果贴图是实心的,它应该让玩家停下来,并将他移动到一个有效的位置。

问题是,当我向上或向下移动时,粘在瓷砖上,并按左或右移动键。
例如:

我按下键,我在实心贴图的顶部,我按左键。在那一刻,玩家被重新定位到贴图的右边。

由于碰撞方法中的if顺序,如果我向左或向右移动并向上或向下按,则不会发生这种情况。

public void Update(GameTime gameTime, ref Player player)
{
    if (State != TileState.Solid) return;
    Rectangle tileRect = 
        new Rectangle((int)Position.X, (int)Position.Y,
            SourceRect.Width, SourceRect.Height);
    Rectangle playerRect = 
        new Rectangle((int)player.Sprite.position.X, (int)player.Sprite.position.Y,
            player.Sprite.SourceRect.Width, player.Sprite.SourceRect.Height);
    HandleCollisionWithTile(player, playerRect, tileRect);
}
/// <summary>
/// Repositions the player at the current position after collision with tile
/// </summary>
/// <param name="player">the player intersecting with the tile</param>
/// <param name="playerRect">the rectangle of the player</param>
/// <param name="tileRect">the rectangle of the tile</param>
private static void HandleCollisionWithTile(Player player, Rectangle playerRect, Rectangle tileRect)
{
    // Make sure there's even collosion
    if (!playerRect.Intersects(tileRect)) return;
    // Left
    if (player.Velocity.X < 0)
        player.Sprite.position.X = tileRect.Right;
    // Right
    else if (player.Velocity.X > 0)
        player.Sprite.position.X = tileRect.Left - player.Sprite.SourceRect.Width;
    // Up
    else if (player.Velocity.Y < 0)
        player.Sprite.position.Y = tileRect.Bottom;
    // Down
    else if (player.Velocity.Y > 0)
        player.Sprite.position.Y = tileRect.Top - player.Sprite.SourceRect.Height;
    // Reset velocity
    player.Velocity = Vector2.Zero;
}

显然,如果我将前两个If与最后一个If交换,如果我从侧面向上或向下按,就会发生这种情况。

我到底错过了什么?

简单的平台游戏碰撞算法出现故障

正如您所注意到的,这个问题是由于检查碰撞条件的顺序造成的。特别是,当你的玩家精灵看起来在贴图上方时,你的代码会检测到这个精灵是相交的,因为第一次检查是水平碰撞,所以玩家精灵的位置会移动到贴图精灵的右侧(实际上是在上方和右侧,所以你只需要调整检测到的碰撞轴的坐标)。

在我看来,这暴露了代码中的一个基本缺陷:虽然有人可能会争辩说,通过将玩家精灵放置在瓷砖精灵的顶部,解决了垂直方向上发生的碰撞,现在玩家精灵不应该与瓷砖精灵处于碰撞状态,相反,你的代码将这种情况检测为碰撞。

如果没有更好的代码示例,很难知道最好的修复方法是什么。但一个显而易见的快速解决方案是,确保当你解决碰撞时,你不会将玩家精灵置于与贴图精灵相交的位置。例如:
private static void HandleCollisionWithTile(Player player, Rectangle playerRect, Rectangle tileRect)
{
    // Make sure there's even collosion
    if (!playerRect.Intersects(tileRect)) return;
    // Left
    if (player.Velocity.X < 0)
        player.Sprite.position.X = tileRect.Right + 1;
    // Right
    else if (player.Velocity.X > 0)
        player.Sprite.position.X = tileRect.Left - player.Sprite.SourceRect.Width - 1;
    // Up
    else if (player.Velocity.Y < 0)
        player.Sprite.position.Y = tileRect.Bottom + 1;
    // Down
    else if (player.Velocity.Y > 0)
        player.Sprite.position.Y = tileRect.Top - player.Sprite.SourceRect.Height - 1;
    // Reset velocity
    player.Velocity = Vector2.Zero;
}

换句话说,在贴图周围留出1个像素的空白,让玩家精灵无法进入。

另一种选择是在检查定义碰撞的交叉点之前将tile矩形"缩小"1个像素(即使用负值调用Inflate())。然后,当碰撞发生时,位置将与之前一样(即没有上面显示的 +1-1调整),但该位置本身不会被检测为碰撞。

有许多其他的方法来处理碰撞检测,但是a)如果没有更多关于你的场景的细节,就很难知道这些方法中是否以及哪一个可能是更好的选择,b)鉴于你当前实现的明显简单的性质,我上面提出的相对简单的修复似乎更有可能得到保证和有用。