So I’m writing a player class and using floats to store the positon and velocity (Vector2) as well as using it when drawing. When it comes to collision I get the player bounds using this code:
public Rectangle Bounds => new Rectangle((int)_pos.X, (int)_pos.Y, 12, 23);
to test it against solid blocks.
The issue comes when I apply gravity to the player and the player moves in <1 increments per frame eg. first frame 0, second frame 0.25, etc. This causes the player to jitter when above a block due to him falling down little by little until the collision code (which uses ints) pushes it back up.
One solution I came across is to use the Bounds I calculated earlier for drawing, instead of using the position directly. However there is another problem (because the problems never stop coming)… Now the jitter occurs behind the scenes causing the ground detection to not work. How can I make the float positioning and int collision system work in harmony??
The player class' file:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
public class Player
{
public Rectangle Bounds
=> new Rectangle((int)_pos.X, (int)_pos.Y, 16, 16);
Vector2 _prevPos;
Vector2 _pos;
Vector2 _vel;
World _world;
const float GRAVITY = 500;
const float MOVE_SPEED = 100;
const float JUMP_FORCE = 275;
const float MAX_FALL_SPEED = 350;
public Player(Vector2 position)
{
_pos = position;
_world = Main.World;
}
public void Update(float dt)
{
// Gravity
_vel.Y += GRAVITY * dt;
_vel.Y = MathHelper.Clamp(_vel.Y, -MAX_FALL_SPEED, MAX_FALL_SPEED);
// Horizontal movement
if (Input.IsKeyHeld(Keys.A))
_vel.X = -MOVE_SPEED;
else if (Input.IsKeyHeld(Keys.D))
_vel.X = MOVE_SPEED;
else
_vel.X = 0f;
// Jumping
if (Input.IsKeyDown(Keys.Space))
_vel.Y = -JUMP_FORCE;
// Calculate position
_prevPos = _pos;
_pos += _vel * dt;
HandleCollisions();
System.Console.WriteLine("Velocity: {0}", _vel);
}
void HandleCollisions()
{
Rectangle bounds = Bounds;
Rectangle prevBounds = new Rectangle((int)_prevPos.X, (int)_prevPos.Y, bounds.Width, bounds.Height);
// Only check blocks around player
int left = bounds.Left / TileData.TILE_SIZE;
int right = bounds.Right / TileData.TILE_SIZE;
int top = bounds.Top / TileData.TILE_SIZE;
int bottom = bounds.Bottom / TileData.TILE_SIZE;
for (int x = left; x <= right; x++)
{
for (int y = top; y <= bottom; y++)
{
// Not a solid block
if (_world.GetBlock(x, y) == -1)
continue;
Rectangle blockBounds = new Rectangle(x * TileData.TILE_SIZE, y * TileData.TILE_SIZE, TileData.TILE_SIZE, TileData.TILE_SIZE);
// Resolve collision on x axis
if (bounds.Intersects(blockBounds))
{
// Previously to left of block
if (prevBounds.Right <= blockBounds.Left)
{
_pos.X = blockBounds.Left - bounds.Width;
_vel.X = 0f;
}
// Previoulsy to right of block
else if (prevBounds.Left >= blockBounds.Right)
{
_pos.X = blockBounds.Right;
_vel.X = 0f;
}
}
// Perform further collision using new bounds
bounds = Bounds;
// Resolve collision on y axis
if (bounds.Intersects(blockBounds))
{
// Previously above block
if (prevBounds.Bottom <= blockBounds.Top)
{
_pos.Y = blockBounds.Top - bounds.Height;
_vel.Y = 0f;
// _isGrounded = true; Doesn't work due to float and int mismatch
}
// Previoulsy below block
else if (prevBounds.Top >= blockBounds.Bottom)
{
_pos.Y = blockBounds.Bottom;
_vel.Y = 0f;
}
}
}
}
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.FillRectangle(Bounds, Color.White);
}
}