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 eitherColliding
orNotColliding
). -
Since actors and other objects can move, I wrote a
WorldSystem
that requires aTransform2
and aCollider
and updates theCollider
bounds x and y position based on its currentTransform2
position.Collider
also stores the last valid position before a collision, which is set in theMovementSystem
. -
There is the
CollisionSystem
that (for now) just iterates over each object with aCollider
and does a simpleRectangleF.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’sTransform2
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!