6
\$\begingroup\$

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).

\$\endgroup\$
4
\$\begingroup\$

To be physical, you need to calculate friction based on the magnitude of your velocity. You can simply remove the if checks when calculating friction and store a "friction constant" instead of a "friction force." You are already essentially doing this because m_fFriction is a double and not a vector, you are just thinking about it wrong. The friction constant can be any positive float and is how physicists think about friction anyway. You would then replace

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.

with

Vec2D<float> friction = -curVel * m_fFriction;

and continue on like you do for the rest of the function. The friction force is ALWAYS in the opposite direction of velocity, so this is physically accurate.

\$\endgroup\$
0
\$\begingroup\$

It looks like you're using acceleration in a weird way in your engine, so I can't give you a 100% fool-proof answer you can directly implement into your code, but I can take a shot at it.

You'd want to apply your friction to your current speed, not your characters acceleration. Try to change your code to this instead:

if( curVel.x > 0.0f )
    curVel.x -= m_fFriction;
else if( curVel.x < 0.0f )
    curVel.x += m_fFriction;

if( curVel.y > 0.0f )
    curVel.y -= m_fFriction;
else if( curVel.y < 0.0f )
    curVel.y += m_fFriction;

Now as I said previously I'm not sure how your physics engine works, but if you want it to work according to real-world physics the basic structure should be something like this (java pseudo code, but you should get the gist):

Vector2 acceleration = new Vector2(0, 0); //Create a new vector to hold acc.
acceleration.x = (object.mass^-1)*(forceDueToInput.x - forceDueToFriction + otherForcesDueToStuff.x) + gravity.x; 
//This equation is the same as dividing all those forces by object.mass.
acceleration.y = (object.mass^-1)*(forceDueToInput.y - forceDueToFriction + otherForcesDueToStuff.y) + gravity.y;
//Same as above but for y axis. 
//If you have unspecified mass in your game, just use object.mass = 1.

and then of course you can calculate speed using this acceleration.

Vector2 speed = new Vector2(0, 0);
speed.x += deltaTime * acceleration.x;
speed.y += deltaTime * acceleration.y;
//deltaTime = amount of time the object has traveled since last calculation.
//If you're not using real-time ticks you can leave this as 1;

If you want to use a joystick you can calculate what the x and y composants of your force should be by doing this:

Vector2 force = new Vector2(0, 0);
force.x = forceDueToInput * cos(alpha);
force.y = forceDueToInput * sin(alpha);
//alpha = the angle of your force in degrees.

Hopefully this helps at least a little bit. Feel free to comment if you want additional details about anything or you don't understand.

\$\endgroup\$
  • \$\begingroup\$ For your first suggestion, doing that would produce the same problem as I described in the original post. For your second suggestion, I assume you mean doing something like this in my case: gist.github.com/anonymous/ff4980337eb2643dbad5#file-bleh-cpp but since m_fFriction is a float value and not a vector, it wouldn't work properly since we're not reducing the acceleration vector's magnitude, but simply constantly subtracting from its x/y value (meaning it will always try to push the player up and to the left). \$\endgroup\$ – DonutVikingChap Jan 17 '16 at 0:27
  • \$\begingroup\$ The only two forces that are applicable in my case is the input force (pushing the player in their desired direction) and "friction" (the force slowing the player down). Other forces (gravity & normal force) are omitted since they are superfluous in a top-down perspective where you can't jump. Since mass is also unspecified, the acceleration is essentially equal to the resultant force. To reiterate: my main problem is calculating the angle/magnitude of the friction vector, since simply making it parallel to the velocity vector didn't produce the result I was looking for. \$\endgroup\$ – DonutVikingChap Jan 17 '16 at 0:48
  • \$\begingroup\$ @DonutVikingChap Not quite. My other suggestion would mean you would have to recreate the base of your physics engine to work with forces (like Box2d, for example) instead of accelerations and speeds. And you're right about friction, and a simple fix for this would be to use an if-loop to ignore friction when you're not moving, or clamp forceDueToFriction so it can't be bigger than all of the other forces combined.(Previous was accidental post) \$\endgroup\$ – Charanor Jan 17 '16 at 0:52
  • \$\begingroup\$ @DonutVikingChap can you try making the friction vector negative of your speed (the direction you're currently moving in, not your acceleration vector) vector and see if that fixes your problem? \$\endgroup\$ – Charanor Jan 17 '16 at 0:58
  • \$\begingroup\$ That's what I'm already doing. \$\endgroup\$ – DonutVikingChap Jan 17 '16 at 0:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.