I'm making a top-down view 2D RPG game, meaning the player can move in any direction on a 2D plane, and I'm trying to implement a somewhat realistic non-orthogonal acceleration/friction physics model for movement.
For instance, if the player's acceleration value is "100", holding the right arrow key will change the player's "input force" vector to (100, 0). If holding both right + down, the force will be roughly
(71, 71) since 712 + 712 ≈ 1002. This part is working correctly right now.
The function I'm currently using to update the position looks something like this (Note: I'm not a physics expert):
void GameEntity::updateSpritePos( const float timeStep )
{
// Store old velocity.
const Vec2D<float> curVel( m_v2Velocity );
// Get force.
Vec2D<float> force( m_v2InputForce );
// Calculate friction.
Vec2D<float> friction;
if( curVel.getLength() < m_fFriction * timeStep ) // Make sure the friction doesn't overextend.
friction = ( -curVel / timeStep );
else
friction = -curVel.getNormalized() * m_fFriction; // getNormalized() returns the unit vector.
// Apply friction.
force += friction;
// Calculate acceleration.
const Vec2D<float> accel( force ); // Since we're not taking mass into account, acceleration = resultant force.
// Calculate new velocity based on acceleration.
const Vec2D<float> newVel( curVel + ( accel * timeStep ) );
// Calculate how much to move using "improved Euler" integration.
const Vec2D<float> avgVel( ( curVel + newVel ) * 0.5f );
const Vec2D<float> moveAmount( avgVel * timeStep );
// Update variables.
m_c2Position += moveAmount;
m_v2Velocity = newVel;
m_v2LastMoved = moveAmount; // Storing this for collision resolution.
}
Where m_fFriction
is a constant float value that can be anywhere above 0.
The problem is the way that friction is being applied: If walking in a given direction and then simply letting go of the key, you will glide for the appropriate distance.
But if letting go of the key while also accelerating in a perpendicular direction at the same time, you will glide much further in the original direction. This is not the behavior I want, I want the player to always glide the same distance.
Since I'm currently doing this:
friction = -curVel.getNormalized() * m_fFriction;
Friction will always be applied in the opposite direction of the current velocity. I'm not sure if this is correct.
I could solve it by doing something like this:
if( curVel.x > 0.0f )
friction.x = -m_fFriction;
else if( curVel.x < 0.0f )
friction.x = m_fFriction;
if( curVel.y > 0.0f )
friction.y = -m_fFriction;
else if( curVel.y < 0.0f )
friction.y = m_fFriction;
But then it doesn't work right when walking in a non-orthogonal direction (i.e. diagonally). This is especially bad since I also want to implement joystick support in the future where the force will be applied in the exact direction you're holding the stick.
Is there an easy way to fix this that I have overlooked, or am I getting into deep water here?
Note: I don't want to change my code to use a fixed timestep
for a number of reasons, and I don't want to use damping (e.g. vel *= 0.99timeStep) since this doesn't create linear deceleration (unless there's a way to make it do that).