Reputation: 1
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?
Upvotes: 0
Views: 95
Reputation:
The line of code:
if (intersection.Height > intersection.Width)
Only works if the rectangles are:
Done properly his gives the following four collision zones, formed from the blue diagonals:
This is the actual test you are performing (not to scale):
The reason it moves to the right is the order the collision checks occur.
Since the aspect ratio is the same, width / height = 1, for both objects:
// ...
var adj = (float)collider.RectBounds.Width / otherBounds.Height;
if (intersection.Height * adj > intersection.Width) {
// ...
If they are not the same aspect ratio: Add a second adj2
variable:
// ...
var adj = (float)collider.RectBounds.Width / otherBounds.Height;
var adj2 = (float)otherBounds.Height / collider.RectBounds.Width;
if (intersection.Height * adj > intersection.Width * adj2) {
// ...
I will say that your programming style/approach/methodology, ECS, does not scale beyond small games.
In C# put the methods inside of the classes they will be used in or in a base class and derive classes from it, if they are not related use an interface.
Mappers are not needed, the just take up space and time being on the heap.
Function calls are expensive:
If a multiple calls to the same function, Keyboard.GetState()
returns the same value store the value in a local variable.
Minimize the number of casts and dots, .
: like args.Other.Bounds
.
The parameter CollisionEventArgs args
needs to be simplified to: Rectanglef otherBounds
Upvotes: 0