0

在我的项目中,我试图在两个实体对象之间实现离散的 AABB 碰撞。我的代码适用于两个相同大小的对象,但对于两个不同大小的对象,当两个精灵显然没有接触时,就会发生碰撞。当两个精灵碰撞时,它们之间的距离越大,物体大小之间的差异就越大。

为什么会发生这种情况,我该如何更改代码,以便碰撞按预期用于两个不同大小的对象?

相关代码目前的方式是:

Box 结构体表示一个边界框。它还具有检查交叉点的方法。用否定的方式检查交叉点

public struct Box
{
    #region data
    public Vector2 TopLeft { get; set; }
    public Vector2 BottomRight{ get; set; }
    public float Width
    {
        get
        {
            return Math.Abs(BottomRight.X - TopLeft.X);
        }
    }
    public float Height
    {
        get
        {
            return Math.Abs(BottomRight.Y - TopLeft.Y);
        }
    }
    #endregion
    #region c'tor
    public Box(Vector2 tl, Vector2 br) : 
        this()
    {
        this.TopLeft = tl;
        this.BottomRight = br;
    }
    public Box(float top, float bottom, float left, float right) :
        this(new Vector2(left, top), new Vector2(right, bottom))
    {

    }
    public Box(Vector2 tl, float width, float height) :
        this(tl, new Vector2(tl.X + width, tl.Y + height))
    {

    }
    #endregion
    #region methods
    public bool Intersects(Box other)
    {
        return (IntersectsX(other) && IntersectsY(other)) || IsContained(other);
    }
    public bool IntersectsY(Box other)
    {
        return !((TopLeft.Y <= other.TopLeft.Y && BottomRight.Y <= other.TopLeft.Y) || (TopLeft.Y >= other.BottomRight.Y && BottomRight.Y >= other.BottomRight.Y));
    }
    public bool IntersectsX(Box other)
    {
        return !((TopLeft.X <= other.TopLeft.X && BottomRight.X <= other.TopLeft.X) || (TopLeft.X >= other.BottomRight.X && BottomRight.X >= other.BottomRight.X));
    }
    public bool IsContained(Box other)//checks if other is contained in this Box
    {
        return (TopLeft.X > other.TopLeft.X) && (TopLeft.X < other.BottomRight.X) && (TopLeft.Y > other.TopLeft.Y) && (TopLeft.Y < other.BottomRight.Y) &&
               (BottomRight.X > other.TopLeft.X) && (BottomRight.X < other.BottomRight.X) && (BottomRight.Y > other.TopLeft.Y) && (BottomRight.Y < other.BottomRight.Y);
    }
    #endregion
}

具有碰撞器的对象必须实现 IBoundingBoxCollider 接口: public interface IBoundingBoxCollider { #region data Box BoundingBox { get; } bool SolidCollider { 得到;放; } 布尔静态碰撞器 { 获取;放; } 浮动 DistanceTraveledThisFrame { 获取;} #endregion #region 方法 void CheckCollision(IBoundingBoxCollider other); #endregion }

字符由 Character 类表示。此类具有 CheckCollisions 和 UpdateCollider 方法。UpdateCollider 也在类构造函数中被调用。Character 类继承自 AnimatedObject,该类处理动画,该类继承自 DrawableObject,后者处理将精灵绘制到屏幕上。DrawableObject 具有 Position、Rotation 和 Scale 属性。Draw 和 Update 方法处理 Game1 中相应地在 Draw 和 Update 上调用的静态事件。

public virtual void CheckCollision(IBoundingBoxCollider other)
    {
        if (BoundingBox.Intersects(other.BoundingBox))
        {
            //on collision
            float newX = Position.X, newY = Position.Y;
            if (directionLastMoved == Directions.Up || directionLastMoved == Directions.Up_Left || directionLastMoved == Directions.Up_Right)
                newY = other.BoundingBox.BottomRight.Y;
            if (directionLastMoved == Directions.Down || directionLastMoved == Directions.Down_Left || directionLastMoved == Directions.Down_Right)
                newY = other.BoundingBox.TopLeft.Y - BoundingBox.Height;
            if (directionLastMoved == Directions.Left || directionLastMoved == Directions.Up_Left || directionLastMoved == Directions.Down_Left)
                newX = other.BoundingBox.BottomRight.X;
            if (directionLastMoved == Directions.Right || directionLastMoved == Directions.Up_Right || directionLastMoved == Directions.Down_Right)
                newX = other.BoundingBox.TopLeft.X - BoundingBox.Width;
            Vector2 newPos = new Vector2(newX, newY);
            float ratio = DistanceTraveledThisFrame / (DistanceTraveledThisFrame + other.DistanceTraveledThisFrame);
            if(other.StaticCollider || ratio.Equals(float.NaN))
                Position = newPos;
            else
            {
                Vector2 delta = (newPos - Position) * ratio;
                Position += delta;
            }
            UpdateCollider();
        }
    }
    protected override void Draw(GameTime gameTime)
    {
        base.Draw(gameTime);
        UpdateCollider();
    }
    protected virtual void Update(GameTime gameTime)
    {
        lastPosition = Position;
    }
    protected void UpdateCollider()
    {
        Rectangle currentFrameBox = this.animator.CurrentAnimation.CurrentFrame(0).Frame;
        BoundingBox = new Box(Position, currentFrameBox.Width * Scale, currentFrameBox.Height * Scale);
    }

在 Game1 类中有一个 List。列表的每次更新都会被迭代,并且每两个碰撞器都会调用 CheckCollision:

protected override void Update(GameTime gameTime)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        // TODO: Add your update logic here
        foreach (IBoundingBoxCollider collider in colliderList)
        {
            if (collider.StaticCollider)
                continue;
            foreach (IBoundingBoxCollider other in colliderList)
            {
                if (collider != other)
                    collider.CheckCollision(other);
            }
        }
        if (InputEvent != null)
            InputEvent(gameTime, Keyboard.GetState(), Mouse.GetState());
        if (UpdateEvent != null)
            UpdateEvent(gameTime);
        base.Update(gameTime);
    }

编辑1

尝试了 Monset 的解决方案,将 Box 结构替换为 Monset 所做的类,并进行了一些命名更改:

public class Box : Rectangle
{
    #region data
    public Vector2 Position;
    public float Width, Height;
    public Rectangle GetRectangle
    { get { return new Rectangle((int)Position.X, (int)Position.Y, (int)Width, (int)Height); } }
    #endregion
    #region c'tor
    public Box(Vector2 position, float width, float height)
    {
        this.Position = position;
        this.Width = width;
        this.Height = height;
    }
    #endregion
}

它给了我不能从密封类型'Microsoft.Xna.Framework.Rectangle'派生

4

1 回答 1

0

Rectangle使用类而不是 AABB 。这个类有Intersects和等预定义函数Contains,它仍然是一个矩形,你仍然可以改变它的位置、宽度和高度。如果您想Rectangle通过两点定义 a,请创建一个新类(这是 ac#/伪代码):

public class BoundingBox: Rectangle
{
    ...
    public Point TopLeft
    { get{ this.X = value.X; this.Y = value.Y; } }
    public Point BottomRight
    { get{ this.Width = value.X - this.X; this.Height = value.Y - this.Y; } }
    ...
}

如果您仍然不想按照构建 XNA 的人的意图那样简单地执行此操作,请尝试使用谷歌搜索“如何检查两个矩形之间的碰撞”,并在您的Intersects函数中实现这些公式。我不推荐这样做,因为你会浪费时间在你已经拥有的东西上(Rectangle)。把最后一段作为我的经验,因为我尝试了你现在正在尝试的东西,并且到了我看到我的自定义工作与XNA 中包含BoundingBox的类几乎相同的地步。Rectangle

编辑 1:
如评论中所问,为了使用十进制数 ( float) 保存位置,您可以floatRectangle. 这样做将保留更多信息(因为所有内容都以十进制数字存储)并为您Rectangle提供类提供的功能。这看起来像这样(未经测试):

public class BoundingBox: Rectangle
{
    // PUBLIC
    public Vector2 Position;
    public float Width, Height,
    public Rectangle Box
    { get { return new Rectangle((int)Position.X, (int)Position.Y, (int)Width, (int)Height); } }

    // or, if you don't understand get/set, create function
    public Rectange Box()
    { return new Rectangle((int)Position.X, (int)Position.Y, (int)Width, (int)Height); } 
}

如果您是初学者,请不要在这里担心 CPU 或 RAM。这将使用更多的 RAM 空间和 CPU 时间,但是当您的游戏开始滞后时,您会担心这一点。现在,练习-练习-练习。如果你不是初学者,你知道你在做什么。

于 2016-05-27T14:42:08.680 回答