Best approach for collision system and ECS

Hi,

I have been trying to figure out the best way to do collisions using MonoGame Extended’s ECS. I started off by dissecting the Platformer example, but it did not work when I tried to run it, and it had some features in it that I really didn’t need (e.g. velocity), so I decided to simplify it and write my own.

Here’s my current approach:

  • Each actor (e.g. player, NPC) and static object (such as a tile) receives a Collider component. This stores the current object collider’s bounds and state (enum that is either Colliding or NotColliding).

  • Since actors and other objects can move, I wrote a WorldSystem that requires a Transform2 and a Collider and updates the Collider bounds x and y position based on its current Transform2 position. Collider also stores the last valid position before a collision, which is set in the MovementSystem.

  • There is the CollisionSystem that (for now) just iterates over each object with a Collider and does a simple RectangleF.Intersect to see if we are colliding or not with any other collider (and updates the enum accordingly of the colliding objects). Time complexity is probably awful but I was going to concern myself with that later and make it a bit smarter (will replace with Trinith’s QuadTree mentioned in another collision post).

  • There is the MovementSystem that deals with receiving keyboard input and updating the actor’s Transform2 component. It records the last valid position, and after movement, if a collision occurs, it reset the player back to the last valid position.

Now I am sure there are better ways to do this design, but for now I just wanted some simple collision to work.

I am currently running into an issue with this design in which the colliding entity simply gets stuck and can’t get moved out of, and it appears as if the RectangleF.Interesect isn’t accurate enough (i.e. it collides too late, hence it gets stuck). I added some debug sprites to the GIF below (all colliders for now are just 16x16), red and blue colliders:

CollisionSystem:

public override void Process(GameTime gameTime, int entityId)
{
     Collider collider = _colliderMapper.Get(entityId);
     Bag<Collider> allColliders = _colliderMapper.Components;

     collider.State = ColliderState.NotColliding;

     foreach (Collider otherCollider in allColliders)
     {
         if (collider.Bounds.Intersects(otherCollider.Bounds))
         {
             collider.State = ColliderState.Colliding;
             return;
         }
     }
}

WorldSystem:

public override void Process(GameTime gameTime, int entityId)
{
    Transform2 transform = _transformMapper.Get(entityId);
    Collider collider = _colliderMapper.Get(entityId);

    // The - 8.0f is temporarily there for making sure the collider is centered 
    collider.Bounds = new RectangleF(transform.Position.X - 8.0f, transform.Position.Y, collider.Bounds.Width, collider.Bounds.Height);
}

MovementSystem:

public override void Process(GameTime gameTime, int entityId)
{
      Actor actor = _actorMapper.Get(entityId);
      Transform2 transform = _transformMapper.Get(entityId);
      Collider collider = _colliderMapper.Get(entityId);

      if (!actor.PlayerControllable) return;

      KeyboardStateExtended keyboardState = KeyboardExtended.GetState();
      float speed = actor.MovementSpeed;

      actor.State = State.Idle;
      collider.LastValidPosition = transform.Position;

      if (keyboardState.IsKeyDown(Keys.W))
      {
           actor.State = State.Walking;
           actor.Facing = Facing.Up;
           transform.Position = new Vector2(transform.Position.X, transform.Position.Y - speed);
       }
       // ...deal with A, S, D here...

       // ACTUAL COLLISION HERE:
       if (collider.Policy == CollisionPolicy.Block && collider.State == ColliderState.Colliding)
          transform.Position = collider.LastValidPosition;

        if (actor.State == State.Walking)
           // Play animation etc...
}

Any help, feedback, or ideas would be much appreciated!

RectangleF should have more then enough precision to handle the collision case; however, how you handle the collision case could be what you’re running into. I took a look through your code and it’s just not standing out to me where you’re handling that case. What do you do to the entity when you detect that it’s overlapping another? I would assume you would shuffle it back along it’s path a bit until it’s no longer overlapping (in some fashion or another), I just dont’ see that case.

By the way, I love your sprites :smiley:

Thanks (I didn’t make the sprites haha!) I figured that RectangleF could not be the issue.

The collision case is handled in the MovementSystem towards the bottom of the function. If a collision is detected, we just reset the transform’s position to the last valid position. This is probably a bit odd and I don’t exactly remember why I thought that would be the best way to do it (and not just before the movement and just return if we are colliding) - I am starting to think that I might be overcomplicating aspects of this :smile:

// save valid transform
...
// handle movement
...
// reset if colliding (yucky)
if (collider.Policy == CollisionPolicy.Block && collider.State == ColliderState.Colliding)
          transform.Position = collider.LastValidPosition;

I have some suspicion the problem may arise because of the order the Systems are running i.e. collision detection occurs AFTER we update the transform that could already be in an invalid state, hence why it gets stuck.

Ah yes, there it is. I was looking for something like that and just glazed over it.

I thought about mentioning order of updates, but in the gif you posted, the goat didn’t look like it moved. So you somehow got your character into the goat’s collision box and then it couldn’t get out.

If everything’s moving at once, you’d definitely want to fully resolve one object before moving on to the next. That said, you look like you’re doing this. Presumably, you calculate the new position and check for collisions. At this point the player should go from not intersecting with the goat to intersecting with the goat, meaning the last valid position is sound.

You just have to make sure you fully resolve this before moving on to the next entity, otherwise yea you could totally end up with two entities moving, then checking to see if they should move back to a position that’s no longer valid.

Still though, with a stationary goat in your image, I’m actually not sure how you did this :smiley:

Is that the Harvest Moon sprites?

1 Like