I'm currently working on a 2D game using an ECS with a character that's able to move left and right and make a small jump. I'm using a velocity component to control the player's movement, which is updated like this during every game tick:
private const float MaximumVerticalVelocity = 15f;
private const float VerticalAcceleration = 1f;
private const float MaximumHorizontalVelocity = 10f;
private const float HorizontalAcceleration = 1f;
private void move(GameTime gameTime, int entityID) {
float speed = HorizontalAcceleration;
var hitbox = (RectangleF) _colliderMapper.Get(entityID).Bounds;
var keyInputs = _inputMapper.Get(entityID);
var velocity = _velocityMapper.Get(entityID);
var position = _positionMapper.Get(entityID);
updateVelocity(velocity, 0, VerticalAcceleration);
if (Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Jump]) &&
hitbox.Bottom < Game.Main.MapHeight && hitbox.Bottom > 0 && (
!Game.Main.TileIsBlank(position.X, hitbox.Bottom) ||
!Game.Main.TileIsBlank(hitbox.Right - 1, hitbox.Bottom)
)) {
velocity.DirY = -11f;
}
if (Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Sprint])) {
speed *= 2;
}
bool leftDown = Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Left]);
bool rightDown = Keyboard.GetState().IsKeyDown(keyInputs[PlayerAction.Right]);
if (leftDown && !(rightDown && velocity.DirX <= 0)) {
updateVelocity(velocity, -speed, 0);
}
else if (!leftDown && velocity.DirX <= 0) {
updateVelocity(velocity, speed, 0, 0, MaximumVerticalVelocity);
}
if (rightDown && !(leftDown && velocity.DirX >= 0)) {
updateVelocity(velocity, speed, 0);
}
else if (!rightDown && velocity.DirX >= 0) {
updateVelocity(velocity, -speed, 0, 0, MaximumVerticalVelocity);
}
if (!leftDown && !rightDown && velocity.DirX != 0) {
if (velocity.DirX < 0) {
updateVelocity(velocity, speed, 0, 0, MaximumVerticalVelocity);
}
else {
updateVelocity(velocity, -speed, 0, 0, MaximumVerticalVelocity);
}
}
position.X += velocity.DirX;
position.Y += velocity.DirY;
}
private void updateVelocity(Velocity velocity, float x, float y, float xLimit, float yLimit) {
if ((x >= 0 && velocity.DirX + x < xLimit) || (x < 0 && velocity.DirX + x > -xLimit)) {
velocity.DirX += x;
}
else {
if (x >= 0) {
velocity.DirX = xLimit;
}
else {
velocity.DirX = -xLimit;
}
}
if ((y >= 0 && velocity.DirY + y < yLimit) || (y < 0 && velocity.DirY + y > -yLimit)) {
velocity.DirY += y;
}
else {
if (y >= 0) {
velocity.DirY = yLimit;
}
else {
velocity.DirY = -yLimit;
}
}
}
private void updateVelocity(Velocity velocity, float x, float y) =>
updateVelocity(velocity, x, y, MaximumHorizontalVelocity, MaximumVerticalVelocity);
The player and every tile are in a collision system provided by the framework (MonoGame.Extended). All of them have rectangular hitboxes. This is the current code I'm using to resolve collisions when the player collides with a tile:
private void onCollision(int entityID, object sender, CollisionEventArgs args) {
if (args.Other is StaticCollider) {
var velocity = _velocityMapper.Get(entityID);
var position = _positionMapper.Get(entityID);
var collider = _colliderMapper.Get(entityID);
var intersection = collider.RectBounds.Intersection((RectangleF) args.Other.Bounds);
var otherBounds = (RectangleF) args.Other.Bounds;
if (intersection.Height > intersection.Width) {
if (collider.RectBounds.X < otherBounds.Position.X) {
position.X -= intersection.Width;
}
else {
position.X += intersection.Width;
}
velocity.DirX = 0;
}
else {
if (collider.RectBounds.Y < otherBounds.Y) {
position.Y -= intersection.Height;
}
else {
position.Y += intersection.Height;
}
velocity.DirY = 0;
}
collider.RectBounds.X = position.X;
collider.RectBounds.Y = position.Y;
}
}
The issue is that when the player jumps and lands on the tile in such a way that the width of the intersection is shorter than the height, the player is pushed sideways rather than upwards. (shown here and here) What do I do in this situation?