#region Physics Variables
private float previousBottom;
Vector2 velocity;
// Horizontal movement
private const float MoveAcceleration = 14000.0f;
private const float MaxMoveSpeed = 500.0f;
private const float GroundDragFactor = 0.58f;
private const float AirDragFactor = 0.65f;
// Vertical movement
private const float MaxJumpTime = 0.35f;
private const float JumpLaunchVelocity = -4000.0f;
private const float GravityAcceleration = 3500.0f;
private const float MaxFallSpeed = 600.0f;
private const float JumpControlPower = 0.14f;
bool isOnGround;
private float movement;
private bool isJumping;
private bool wasJumping;
private float jumpTime;
private Rectangle localBounds;
#endregion
#region Physics
public void ApplyPhysics(GameTime gameTime)
{
float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
Vector2 previousPosition = Position;
// Base velocity is a combination of horizontal movement control and
// acceleration downward due to gravity.
velocity.X += movement * MoveAcceleration * elapsed;
velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);
velocity.Y = DoJump(velocity.Y, gameTime);
// Apply pseudo-drag horizontally.
if (IsOnGround)
velocity.X *= GroundDragFactor;
else
velocity.X *= AirDragFactor;
// Prevent the player from running faster than his top speed.
velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);
// Apply velocity.
Position += velocity * elapsed;
Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));
// If the player is now colliding with the level, separate them.
HandleCollisions();
// If the collision stopped us from moving, reset the velocity to zero.
if (Position.X == previousPosition.X)
velocity.X = 0;
if (Position.Y == previousPosition.Y)
velocity.Y = 0;
}
private float DoJump(float velocityY, GameTime gameTime)
{
// If the player wants to jump
if (isJumping)
{
// Begin or continue a jump
if ((!wasJumping && IsOnGround) || jumpTime > 0.0f)
{
jumpTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
}
// If we are in the ascent of the jump
if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
{
// Fully override the vertical velocity with a power curve that gives players more control over the top of the jump
velocityY = JumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));
}
else
{
// Reached the apex of the jump
jumpTime = 0.0f;
}
}
else
{
// Continues not jumping or cancels a jump in progress
jumpTime = 0.0f;
}
wasJumping = isJumping;
return velocityY;
}
private void HandleCollisions()
{
// Get the player's bounding rectangle and find neighboring tiles.
Rectangle bounds = BoundingRectangle;
int leftTile = (int)Math.Floor((float)(bounds.Left) / 16);
int rightTile = (int)Math.Ceiling(((float)(bounds.Right) / 16)) - 1;
int topTile = (int)Math.Floor((float)(bounds.Top) / 16);
int bottomTile = (int)Math.Ceiling(((float)(bounds.Bottom) / 16)) - 1;
// Reset flag to search for ground collision.
isOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
// If this tile is collidable,
Tiles.Collision collision = Map.GetCollision(x, y);
if (collision != Tiles.Collision.None)
{
//int _x = (int)((x * 16) / 16);
//int _y = (int)((y * 16) / 16);
int _x = x,
_y = y;
// Determine collision depth (with direction) and magnitude.
Rectangle tileBounds = Map.GetBounds(_x, _y);
Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
if (depth != Vector2.Zero)
{
float absDepthX = Math.Abs(depth.X);
float absDepthY = Math.Abs(depth.Y);
// Resolve the collision along the shallow axis.
if (absDepthY < absDepthX || collision == Tiles.Collision.Platform)
{
// If we crossed the top of a tile, we are on the ground.
if (previousBottom <= tileBounds.Top)
isOnGround = true;
// Ignore platforms, unless we are on the ground.
if (collision == Tiles.Collision.Solid || IsOnGround)
{
// Resolve the collision along the Y axis.
Position = new Vector2(Position.X, Position.Y + depth.Y);
// Perform further collisions with the new bounds.
bounds = BoundingRectangle;
}
}
else if (collision == Tiles.Collision.Solid) // Ignore platforms.
{
// Resolve the collision along the X axis.
Position = new Vector2(Position.X + depth.X, Position.Y);
// Perform further collisions with the new bounds.
bounds = BoundingRectangle;
}
}
}
}
}
// Save the new bounds bottom.
previousBottom = bounds.Bottom;
}
#endregion