Коллайдер AABB не перекрывается со спрайтом, когда два объекта имеют разные размеры
В моем проекте я пытаюсь реализовать дискретные столкновения 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: открытый интерфейс IBoundingBoxCollider { #region data Box BoundingBox { get; } bool SolidCollider { get; задавать; } bool StaticCollider { get; задавать; } float DistanceTraveledThisFrame { get; } #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 есть список. Каждое обновление списка повторяется, и 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);
}
EDIT1
Попробовал решение 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"
1 ответ
Вместо AABB используйте Rectangle
учебный класс. Этот класс имеет предопределенные функции, такие как Intersects
а также Contains
, и это все еще прямоугольник, и вы все еще можете изменить его положение, ширину и высоту. Если вы хотите определить Rectangle
на две точки создайте новый класс (это 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
). Возьмите этот последний абзац как мой опыт с этим, потому что я попробовал то, что вы пытаетесь сейчас, и дошел до того, что я увидел, что мой обычай BoundingBox
работает почти так же, как Rectangle
класс, который включен в XNA.
РЕДАКТИРОВАТЬ 1:
Как указано в комментарии, для сохранения позиции с использованием десятичных чисел (float
), вы можете хранить 3 переменные (положение, ширина и высота) в float
и создать функцию или переменную, которая будет представлять эти переменные в Rectangle
, Это позволит сохранить больше информации (поскольку все хранится в десятичных числах) и даст вам 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); }
}
Если вы новичок, пожалуйста, не беспокойтесь о процессоре или оперативной памяти здесь. Это потребует больше памяти и процессорного времени, но вы начнете беспокоиться об этом, когда ваша игра начнет отставать. А пока практика-практика-практика. Если вы не новичок, вы знаете, что делаете.