In my project I'm trying to implement discrete AABB collisions between two solid objects. The code I have works fine for two objects of the same size, but for two objects of different sizes the collision happens when the two sprites are clearly not touching. The distance between the two sprites when they are colliding is greater the more difference there is between the sizes of the objects.
Why does that happen and how can I change the code so collisions work as intended for two objects of different sizes?
The relevant code the way it currently is:
The Box struct represents a bounding box. It also has methods for checking intersections. Intersections are checked by the way of negation
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
}
An object with a collider has to implement the IBoundingBoxCollider interface: public interface IBoundingBoxCollider { #region data Box BoundingBox { get; } bool SolidCollider { get; set; } bool StaticCollider { get; set; } float DistanceTraveledThisFrame { get; } #endregion #region methods void CheckCollision(IBoundingBoxCollider other); #endregion }
A character is represented by the Character class. This class has the CheckCollisions and UpdateCollider methods. UpdateCollider is also called in the class constructor. The Character class inherits from AnimatedObject, the class that handles animation, which inherits from DrawableObject, which handles drawing sprites to the screen. DrawableObject has the Position, Rotation and Scale properties. The Draw and Update methods handle static events in Game1 which are called on Draw and Update accordingly.
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);
}
In the Game1 class there is a List. Every update the list is being iterated over and CheckCollision is called for every two colliders:
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
Tried the solution by Monset, replacing the Box struct with the class Monset made, with a few naming changes:
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
}
It gives me cannot derive from sealed type 'Microsoft.Xna.Framework.Rectangle'