Trying to wrap my head around collisions

Im trying to learn monogame by making a simple platformer, and currently at the point where i need to handle collisions. I’m currently using TiledCs to draw the map from tsk then assigning the tiles on a specific layer to a TileCollider class. To handle the bounds of the tiles and player im using Monogame.extended using RectangleF.

Currently when i run the game the player will fall and land on the ground but then snap below the tile.
if the player lands on the gem it will constantly bounce.

Here is what the issue looks like


This is with the colliders being drawn

Here is the actual code. Any tips or ways to fix this would greatly be appreciated. if you need more info please let me know so i can provide it.
Giving the tiles a Collider:

    foreach (var property in tile.properties)
                {
                    if (property.name == "HasCollision" && property.value == "true")
                    {
                        var tileBounds = new RectangleF(x, y, _tileWidth, _tileHeight);
                        var tileCollisionActor = new TileColliderWrapper(tile, (RectangleF)tileBounds, ECollisionType.Solid);
                        Globals.CollidersInLevel.Add(tileCollisionActor);
                    }
                }

The Collider Interface:

public interface ICollider
{
    public ECollisionType CollisionType { get; }
    RectangleF Bounds { get; }
}

The Tile Collider class

public class TileColliderWrapper : ICollider
{
    private TiledTile _tile;
    public RectangleF Bounds { get; }
    public ECollisionType CollisionType { get; }

    public TileColliderWrapper(TiledTile tile, RectangleF bounds, ECollisionType collisionType)
    {
        Bounds = bounds;
        _tile = tile;
        CollisionType = collisionType;
    }
}

Then in the player i do a check for each tile in the player bounds. The bounds of the player is held in the Sprite from monogame.extended.

This is the entire player code besides the draw function (not sure if thats needed for help)

 public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            //_velocity.Y += GRAVITY * (float)gameTime.ElapsedGameTime.TotalSeconds;

            HandleInputs(gameTime);
   
        }

        private void HandleInputs(GameTime gameTime)
        {
            var keyboardState = Keyboard.GetState();

            var deltaSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (keyboardState.IsKeyDown(Keys.A)) Velocity.X = -MoveSpeed;
            else if (keyboardState.IsKeyDown(Keys.D)) Velocity.X = MoveSpeed;
            else Velocity.X = 0;

            Velocity.Y += GRAVITY * deltaSeconds;

            if (keyboardState.IsKeyDown(Keys.Space) && _onGround)
            {
                Velocity.Y = -JUMP;
            }

            UpdatePosition(gameTime, deltaSeconds);
        }

        private void UpdatePosition(GameTime gameTime, float deltaSeconds)
        {
            _onGround = false;
            var newPos = this.Transform.Position + (Velocity * deltaSeconds);
            
            foreach (var collider in GetNearestColliders())
            {
                var colliderRect = (RectangleF)collider.Bounds;
                //Check for collision horizontally
                if (Math.Abs(newPos.X - this.Transform.Position.X) > 5f)
                {
                    if (GetBounds().Intersects(colliderRect))
                    {
                        if (newPos.X > this.Transform.Position.X) newPos.X = colliderRect.Left - GetBounds().Width + OFFSET;
                        else newPos.X = colliderRect.Right - OFFSET;
                        continue;
                    }
                }

                //Check for collision vertically 
                if (GetBounds().Intersects(colliderRect))
                {
                    if (Velocity.Y > 0)
                    {
                        newPos.Y = colliderRect.Top - GetBounds().Height;
                        _onGround = true;
                        Velocity.Y = 0;
                    }
                    else
                    {
                        newPos.Y = colliderRect.Bottom;
                        Velocity.Y = 0;
                    }
                }
            }

            this.Transform.Position = newPos;
            Sprite.Update(deltaSeconds);
        }
        
        public List<ICollider> GetNearestColliders()
        {
            return Globals.CollidersInLevel.Where(colliders => GetBounds().Intersects(colliders.Bounds)).ToList();
        }
    }

Here’s what I think is happening:

(ASSUMPTION: Positive velocity is down)

Player lands on ground, spanning two tiles. This code is called:

foreach (var collider in GetNearestColliders())

… and the program begins iterating the two tiles.

Velocity is positive, so this code is called for the first collider:

                if (GetBounds().Intersects(colliderRect))
                {
                    if (Velocity.Y > 0)
                    {
                        newPos.Y = colliderRect.Top - GetBounds().Height;
                        _onGround = true;
                        Velocity.Y = 0;
                    }

Then the program iterates to the second collider. The sprites bounds haven’t changed, so if (GetBounds().Intersects(colliderRect)) once again returns true.

Since velocity was set to zero last iteration, if (Velocity.Y > 0) returns false and this code is called:

                    else
                    {
                        newPos.Y = colliderRect.Bottom;
                        Velocity.Y = 0;
                    }

So now your player is positioned vertically at colliderRect.Bottom.

My guess is that Transform.Position centers around the middle of the sprite, not the foot placement, which is why the sprite appears where it does in your screenshot.


Note: The GetBounds().Intersects will currently always return true, since GetBounds().Intersects returning true is a prerequisite to be included in GetNearestColliders(), and neither object changes bounds within the confines of the loop.