Search Unity

Movement consistency and timesteps/framerate

Discussion in 'Scripting' started by HiddenMonk, Nov 3, 2015.

  1. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    #####################################################
    EDIT - I posted a solution I am currently using below, but I'll post it here so people can see it better (probably isnt the best solution for performance, but its fine in my case). Long story short, depending on how low our framerate is, we will update more times that frame, similar to how fixedupdate works.
    I saw in a post on a different forum somewhere that talked about basically doing what was done above with updating 4 times per frame, except in a smarter way.
    Think of it like what fixedupdate does. When the framerate is low, it updates more times per frame. When our framerate is high, we dont want to be doing our update 4 times per frame since the framerate is already high enough to give nice results. So instead we do something like this...

    Code (CSharp):
    1.         int timesteps = maxUpdateTimestep;
    2.         if(!alwaysUseMaxUpdateTimestep)
    3.         {
    4.             int safePhysicsIterator = Mathf.CeilToInt(Time.deltaTime / (1f / (float)targetPhysicsFramerate)); //If our framerate is low, we run our physics more times for more accuracy.
    5.             timesteps = Mathf.Clamp(safePhysicsIterator, 1, maxUpdateTimestep);
    6.         }
    7.         float deltaTime = Time.deltaTime / (float)timesteps;
    8.         if(deltaTime > 0f)
    9.         {
    10.             for(int i = 0; i < timesteps; i++)
    11.             {
    12.                 UpdateMovementForces(deltaTime);
    13.             }
    14.         }

    Some example code...
    Code (CSharp):
    1.  
    2.     [Range(1,8)]
    3.     public int maxUpdateTimestep = 8; //higher = more movement accuracy, too high can = more loss of velocity(energy) over time (depending on your integration method I guess). 8 seems good for framerates 30+ with targetPhysicsFramerate being 240
    4.     public bool alwaysUseMaxUpdateTimestep; //not recommended as it is wasteful, but an option non the less.
    5.     [Range(30,480)]
    6.     public int targetPhysicsFramerate = 240; //If our framerate is low, we run our physics more times for more accuracy. This value is the desired physics framerate.
    7.  
    8.     Vector3 velocity;
    9.     //My addforce method adds the forces to these instead of velocity directly
    10.     Vector3 currentForces; //ForceModes Impulse and VelocityChange are added to this.
    11.     Vector3 currentForcesWithDeltas; //ForceModes Force and Acceleration goes to this
    12.  
    13.     void LateUpdate()
    14.     {
    15.         UpdateMovement();
    16.     }
    17.  
    18.     void UpdateMovement()
    19.     {
    20.         velocity += currentForces;
    21.  
    22.         int timesteps = maxUpdateTimestep;
    23.         if(!alwaysUseMaxUpdateTimestep)
    24.         {
    25.             int safePhysicsIterator = Mathf.CeilToInt(Time.deltaTime / (1f / (float)targetPhysicsFramerate)); //If our framerate is low, we run our physics more times for more accuracy.
    26.             timesteps = Mathf.Clamp(safePhysicsIterator, 1, maxUpdateTimestep);
    27.         }
    28.  
    29.         float deltaTime = Time.deltaTime / (float)timesteps;
    30.         if(deltaTime > 0f)
    31.         {
    32.             for(int i = 0; i < timesteps; i++)
    33.             {
    34.                 UpdateMovementForces(deltaTime);
    35.             }
    36.         }
    37.  
    38.         currentForces = Vector3.zero;
    39.         currentForcesWithDeltas = Vector3.zero;
    40.     }
    41.  
    42.     void UpdateMovementForces(float deltaTime)
    43.     {
    44.         Vector3 acceleration = velocity + (currentForcesWithDeltas * deltaTime); //Having currentForcesWithDeltas be added here with the subdivided deltaTime seems to give better results in regards to framerate independence.
    45.         acceleration = ApplyDrag(acceleration, drag, deltaTime);
    46.  
    47.         CollisionInfo collisionInfo = new CollisionInfo();
    48.  
    49.         //Symplectic Euler method (Our velocity is pretty much being set beforehand, even though it seems like its being set afterwards)
    50.         collisionInfo = TranslateAndHandleCollisions(acceleration * deltaTime);
    51.  
    52.         velocity = collisionInfo.velocity / deltaTime;
    53.     }

    With maxUpdateTimestep being 8 and targetPhysicsFramerate being 240, the lowest framerate we can get good framerate independent accuracy is at framerate 30 (since 240 / 8 = 30). The targetPhysicsFramerate is kinda like the framerate at which we think our physics will be good at and would want all our lower framerates to try and reach the same accuracy our targetPhysicsFramerate would give. (Im not sure if thats really what it is doing. All in all it might just be some number that is used to determine how many updates we are willing to do per frame.)

    Edit- Its me way in the futuuuurree =), just wanted to say that the system I was using is basically how the Unreal Engine handles its physics. You can read about it here http://www.aclockworkberry.com/unreal-engine-substepping/

    #################################################

    I have been having an issue with creating my custom character controller when it comes to consistency.
    Since I am moving in Update and not in FixedUpdate, I will have to rely on deltaTime for movement being "framerate independent", however, it seems it might not be as framerate independent as I may have thought (at least in regards to inertia and what not).

    My controller mimics a rigidbody in that it uses forces and velocities to move. This is so I can have momentum/inertia, outside forces pushing me, friction, etc...

    To make the issue somewhat simpler to debug, I have been using the code unity gives on the character controller document page (altered a bit)
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent(typeof(CharacterController))]
    5. public class TestConsistency : MonoBehaviour
    6. {
    7.     public enum UpdateType {Update, FixedUpdate}
    8.     public UpdateType updateType = UpdateType.Update;
    9.  
    10.     public float speed = 6.0F;
    11.     public float jumpSpeed = 40.0F;
    12.     public float gravity = 60.0F;
    13.     public float damping = 20f;
    14.  
    15.     Vector3 moveDirection = Vector3.zero;
    16.  
    17.     CharacterController controller;
    18.  
    19.     void Awake()
    20.     {
    21.         if(GetComponent<CapsuleCollider>() != null) Destroy(GetComponent<CapsuleCollider>());
    22.         controller = GetComponent<CharacterController>();
    23.     }
    24.  
    25.     void FixedUpdate() {if(updateType == UpdateType.FixedUpdate) UpdateCharacter();}
    26.     void Update() {if(updateType == UpdateType.Update) UpdateCharacter();}
    27.  
    28.     void UpdateCharacter()
    29.     {
    30.         if(controller.isGrounded)
    31.         {
    32.             moveDirection += transform.TransformDirection(new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"))) * speed * Time.deltaTime;
    33.  
    34.             if(Input.GetButton("Jump"))
    35.             {
    36.                 moveDirection.y = jumpSpeed;
    37.             }
    38.        
    39.         }
    40.  
    41.         moveDirection.y -= ((gravity * Time.deltaTime) * .5f);
    42.  
    43.         //moveDirection *= Mathf.Clamp01(1f - (damping * ExtTime.deltaTime));
    44.         //moveDirection *= Mathf.Pow(damping, ExtTime.deltaTime);
    45.         //moveDirection *= Mathf.Exp(-damping * Time.deltaTime);
    46.         //moveDirection -= (moveDirection * (damping * Time.deltaTime));
    47.         moveDirection *= (1f / (1f + (Time.deltaTime * damping)));
    48.  
    49.         controller.Move(moveDirection * Time.deltaTime);
    50.  
    51.         moveDirection.y -= ((gravity * Time.deltaTime) * .5f);
    52.  
    53.         Debug.DrawRay(transform.position, transform.right, Color.red, 2);
    54.     }
    55. }

    My goal is to have decent consistency with my movements. I have been searching around for framerate independence and what not and am still confused on how things work.
    One of those is how dividing the gravity in half and applying it before you move, and then after you move, somehow magically makes it framerate independent? Before I cut the gravity in half, it was just done before I applied my damping, and as the framerate got lower, the jump got lower. If I moved the code into FixedUpdate though, all was consistent.
    Why does having the gravity in half and in two places make it framerate independent?
    Since my controller script does not handle gravity separately and just takes in velocities and applies them all at once, do I need to make a special case or something? Maybe I just cut all velocities in half, use the first half to move, and then save the next half for the next frame?
    I have also been having issues finding a way to apply a friction or damp to my controller as well (as seen by all the commented out formulas that I found online). The only one of those that seem to be framerate independent is the last uncommented out one, but I am not sure yet if it gives the feel I want (might just need to play with values).
    Another issue which I have not got up to yet would be handling timers. I guess if the time ran out, I would need to grab the remaining time that would have ran if the framerate was higher and calculate how much I would have moved.

    I am just very confused as to how to handle things and am wondering what others do to achieve more consistent movement in a variable timestep. I am not looking for pinpoint accuracy, but good enough to be competitive. I am also fine with the game not being so consistent less than 30fps since thats pretty much unplayable, but I am assuming fixing the inconsistency issues would make it pretty consistent at such low fps anyways.

    I would use FixedUpdate, but I would probably have the timestep at max 120 per second, however, I am worried that the delay between checking for input in update, then waiting to move in fixedupdate + interpolation delays, might be to much for competitive players. Im also worried about situations where a player clicks space to jump, but they are still grounded since fixedupdate is waiting to run 10ms later, and then they click a button to do an action that if you are in the air, all is good, but if you are grounded, you get messed up. I would then need to run all code in fixedupdate or something, which would only add more things to being delayed.
    When seeing what some say about framerate, Ive seen them say 300+ frames feels good and responsive, while lil ol non pro me thought 60fps was pretty good.

    Telling me to just use FixedUpdate or that 10ms delay isnt much (I think it might be) isnt really what I am looking for, but If you feel strongly about it then please do tell with your reasoning. However, surely there should be ways without fixedupdate, otherwise why have a character controller?

    I appreciate any help ^^
     
    Last edited: Sep 4, 2016
  2. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    Use FixedUpdate. lol sorry couldnt resist.

    why do you have gravity defined as 60?
     
  3. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    lol, do you really mean it though or just playing ^^?

    In regards to the gravity, that was just for testing different ranges of values. Since the damp code I was using slowed things down a lot to get a non slide movement, and I wasnt setting the damp lower when in the air due to lazyness, I just increased gravity and jump.
     
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You are applying Time.deltaTime more then once. In line 32, 41 and 49
     
  5. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    Lines 41 and 49 are are just basically how unity was doing it here
    http://docs.unity3d.com/ScriptReference/CharacterController.Move.html
    I added deltatime to 32 since I was using AxisRaw unlike unitys plain Axis. I was getting inconsistency without that deltaTime.
    Also, unitys script gravity is not framerate independent. Only when I cut it in half before and after moving did it work at low framerates.

    The script I posted seems to all be framerate independent, that isnt the issue. What I am unsure of is why cutting the gravity in half fixed things, and if in my real character controller I will need to cut my velocity in half and place before and after I move, or if there are other ways people handle such situations. Or why all the other damping solutions were affected by framerate, but not the last one.
     
  6. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
  7. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    After looking again, I noticed the unity answer I saw this gravity thing from had a link to the site.
    Here it is for those that would like to see
    http://www.niksula.hut.fi/~hkankaan/Homepages/gravity.html
    It states that such a method should be done with all accelerations and not just gravity. I will later try to cut my adding velocity in half and add before moving and after and see if I get more consistent results.
    I still dont know why it works though ;/ ...math
     
  8. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    You don't really notice it with movement is one direction, so the player just pushes forward a tiny bit extra without realising. With gravity you have to hit a specific peak in the jump or it can break gameplay, so at a minimum you just want it for jumping.
     
  9. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    My controller works with just a vector3 velocity, no gravity or stuff like that, so I cant really just take out and focus just on gravity (vector.y doesnt have to be gravity).
    Also, while moving in one direction might not make a huge diff, if I start to move in the other direction, I will now be pushing against my previous velocity due to inertia and what not, so now I believe I do need to handle it the same way as gravity, since its basically the same thing.

    Or does this trick only work with constant values like gravity? Ill have to see when I try it out.

    Edit - So I can get my velocity as I do normally and then do something like
    Code (CSharp):
    1. velocityIWantToAdd *= .5f;
    2.  
    3. currentVelocity += velocityIWantToAdd;
    4. controller.Move(currentVelocity * Time.deltaTime);
    5. currentVelocity += velocityIWantToAdd;
    Which would seem to work for accelerations, however there are issues for things like jumping that are just an instant velocity change. If I want to use this method, I guess I would need to make a special case for things that are instant velocity changes, which is annoying and is making me want to find an alternative answer ;/
    (not fully tested)
     
    Last edited: Nov 4, 2015
  10. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    After a looking all over, I ended up finding this http://lolengine.net/blog/2011/12/14/understanding-motion-in-games
    It talks about something called "Verlet Integration", which is what I think this cut gravity in half thing comes from. However, it states that this method is mainly for constant values such as gravity. It then talked about the Runge-Kutta methods, which led me to researching on the RK4 method.
    I then ended up on this page here http://gafferongames.com/game-physics/integration-basics/ that explains it a bit with code.
    The problem is, that code is handling things in floats and not vector3s. So, I convert the code to c# and just swap floats out for vector3s.
    It runs, but its not giving me framerate independent results like the verlet method was doing. I think this might be due to how I am calculating the acceleration in the Acceleration method.

    Here is the code
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. [RequireComponent(typeof(CharacterController))]
    6. public class TestConsistency : MonoBehaviour
    7. {
    8.     public enum UpdateType {Update, FixedUpdate}
    9.     public UpdateType updateType = UpdateType.Update;
    10.  
    11.     public float speed = 6.0F;
    12.     public float jumpSpeed = 40.0F;
    13.     public float gravity = 60.0F;
    14.  
    15.     Vector3 moveDirection = Vector3.zero;
    16.  
    17.     CharacterController controller;
    18.  
    19.     void Awake()
    20.     {
    21.         if(GetComponent<CapsuleCollider>() != null) Destroy(GetComponent<CapsuleCollider>());
    22.         controller = GetComponent<CharacterController>();
    23.     }
    24.  
    25.     void FixedUpdate() {if(updateType == UpdateType.FixedUpdate) UpdateCharacter();}
    26.     void Update() {if(updateType == UpdateType.Update) UpdateCharacter();}
    27.  
    28.     void UpdateCharacter()
    29.     {
    30.         if(controller.isGrounded)
    31.         {
    32.             moveDirection.y = 0;
    33.  
    34.             moveDirection += transform.TransformDirection(new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"))) * speed * Time.deltaTime;
    35.  
    36.             if(Input.GetButton("Jump"))
    37.             {
    38.                 moveDirection.y = jumpSpeed;
    39.             }
    40.          
    41.         }
    42.  
    43.         moveDirection.y -= (gravity * Time.deltaTime);
    44.  
    45.         State state = IntegrateRK4(new State(transform.position, moveDirection), 0f, Time.deltaTime);
    46.  
    47.         moveDirection = state.v;
    48.         controller.Move(state.x - transform.position);
    49.  
    50.         Debug.DrawRay(transform.position, transform.right, Color.red, 2);
    51.     }
    52.  
    53.     State IntegrateRK4(State state, float t, float dt)
    54.     {
    55.         Derivative a,b,c,d;
    56.  
    57.         a = Evaluate( state, t, 0.0f, new Derivative());
    58.         b = Evaluate( state, t, dt*0.5f, a );
    59.         c = Evaluate( state, t, dt*0.5f, b );
    60.         d = Evaluate( state, t, dt, c );
    61.  
    62.         Vector3 dxdt = 1.0f / 6.0f * ( a.dx + 2.0f*(b.dx + c.dx) + d.dx );
    63.         Vector3 dvdt = 1.0f / 6.0f * ( a.dv + 2.0f*(b.dv + c.dv) + d.dv );
    64.  
    65.         state.x = state.x + dxdt * dt;
    66.         state.v = state.v + dvdt * dt;
    67.  
    68.         return state;
    69.     }
    70.  
    71.     Derivative Evaluate(State initial, float t, float dt, Derivative d)
    72.     {
    73.         State state;
    74.         state.x = initial.x + d.dx*dt;
    75.         state.v = initial.v + d.dv*dt;
    76.  
    77.         Derivative output;
    78.         output.dx = state.v;
    79.         output.dv = Acceleration(state, t+dt);
    80.         return output;
    81.     }
    82.  
    83.     Vector3 Acceleration(State state, float t)
    84.     {
    85.         //const float k = 10;
    86.         //const float b = 1;
    87.         //return -k * state.x - b*state.v;
    88.         return state.v * t;
    89.     }
    90. }
    91.  
    92. public struct State
    93. {
    94.     public Vector3 x;
    95.     public Vector3 v;
    96.  
    97.     public State(Vector3 position, Vector3 velocity)
    98.     {
    99.         this.x = position;
    100.         this.v = velocity;
    101.     }
    102. }
    103.  
    104. public struct Derivative
    105. {
    106.     public Vector3 dx;      // dx/dt = velocity
    107.     public Vector3 dv;      // dv/dt = acceleration
    108.  
    109.     public Derivative(Vector3 dx, Vector3 dv)
    110.     {
    111.         this.dx = dx;
    112.         this.dv = dv;
    113.     }
    114. }

    Anyone have any ideas on whats wrong?
     
  11. HiddenMonk

    HiddenMonk

    Joined:
    Dec 19, 2014
    Posts:
    987
    This is irrelevant to my RK4 question above, but while reading this page
    http://codeflow.org/entries/2010/aug/28/integration-by-example-euler-vs-verlet-vs-runge-kutta/
    I saw the writer talk about "decrease the time step you can get a more accurate Euler integration". I wasnt exactly sure about what he meant, but after playing around I think I understand now.

    Notice in my new code here I have
    Code (CSharp):
    1. float delta = Time.deltaTime / (float)updateTimestep;
    2.  
    3. for(int i = 0; i < updateTimestep; i++)
    4. {
    5.     ........
    6. }
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. [RequireComponent(typeof(CharacterController))]
    6. public class TestConsistency : MonoBehaviour
    7. {
    8.     public enum UpdateType {Update, FixedUpdate}
    9.     public UpdateType updateType = UpdateType.Update;
    10.  
    11.     public int updateTimestep = 16;
    12.  
    13.     public float speed = 6.0F;
    14.     public float jumpSpeed = 40.0F;
    15.     public float gravity = 60.0F;
    16.  
    17.     Vector3 moveDirection = Vector3.zero;
    18.  
    19.     CharacterController controller;
    20.  
    21.     void Awake()
    22.     {
    23.         if(GetComponent<CapsuleCollider>() != null) Destroy(GetComponent<CapsuleCollider>());
    24.         controller = GetComponent<CharacterController>();
    25.     }
    26.  
    27.     void FixedUpdate() {if(updateType == UpdateType.FixedUpdate) UpdateCharacter();}
    28.     void Update() {if(updateType == UpdateType.Update) UpdateCharacter();}
    29.  
    30.     void UpdateCharacter()
    31.     {
    32.         float delta = Time.deltaTime / (float)updateTimestep;
    33.  
    34.         for(int i = 0; i < updateTimestep; i++)
    35.         {
    36.             if(controller.isGrounded)
    37.             {
    38.                 moveDirection.y = 0;
    39.  
    40.                 moveDirection += transform.TransformDirection(new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"))) * speed * delta;
    41.  
    42.                 if(Input.GetButtonDown("Jump"))
    43.                 {
    44.                     moveDirection.y = jumpSpeed;
    45.                 }
    46.  
    47.             }
    48.  
    49.             moveDirection.y -= (gravity * delta);
    50.  
    51.             controller.Move(moveDirection * delta);
    52.  
    53.             Debug.DrawRay(transform.position, transform.right, Color.red, 2);
    54.         }
    55.     }

    I guess the more we divide our deltaTime and iterate over each of our velocity changes, the more accurate results we get.

    Here is a video demonstrating the results.

    It seems that now even at 5 fps, the jump is pretty consistent. In fact, its pretty similar to running it in FixedUpdate as seen in the video.

    Is this method something people generally use? Should I have my character controller run in a loop like this? Are there any drawbacks other then more calculations being done? (which would be fine since this will just be for one object, and we might even be able to save performance by not calling Move inside the loop, but outside it.)

    Id honestly rather use this than the RK4 method since I dont really like using things I dont understand.

    Edit -
    Here is an updated code on how I am going about using this.
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. [RequireComponent(typeof(CharacterController))]
    6. public class TestConsistency : MonoBehaviour
    7. {
    8.     public enum UpdateType {Update, FixedUpdate}
    9.     public UpdateType updateType = UpdateType.Update;
    10.  
    11.     [Range(1,8)]
    12.     public int updateTimestep = 8;
    13.  
    14.     public float speed = 6.0F;
    15.     public float jumpSpeed = 40.0F;
    16.     public float gravity = 60.0F;
    17.     public float friction = .5f;
    18.     public float drag = .5f;
    19.  
    20.     Vector3 moveDirection = Vector3.zero;
    21.  
    22.     CharacterController controller;
    23.  
    24.     void Awake()
    25.     {
    26.         if(GetComponent<CapsuleCollider>() != null) Destroy(GetComponent<CapsuleCollider>());
    27.         controller = GetComponent<CharacterController>();
    28.     }
    29.  
    30.     void FixedUpdate() {if(updateType == UpdateType.FixedUpdate) UpdateCharacter();}
    31.     void Update() {if(updateType == UpdateType.Update) UpdateCharacter();}
    32.  
    33.     void UpdateCharacter()
    34.     {
    35.         Vector3 move = Vector3.zero;
    36.         Vector3 moveDeltas = Vector3.zero;
    37.  
    38.         if(controller.isGrounded)
    39.         {
    40.             moveDirection.y = 0;
    41.  
    42.             moveDeltas += transform.TransformDirection(new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical"))) * speed;
    43.  
    44.             if(Input.GetButtonDown("Jump"))
    45.             {
    46.                 move.y = jumpSpeed;
    47.             }
    48.    
    49.         }
    50.  
    51.         moveDeltas.y -= (gravity);
    52.  
    53.  
    54.         moveDirection += move;
    55.  
    56.         float delta = Time.deltaTime / (float)updateTimestep;
    57.         for(int i = 0; i < updateTimestep; i++)
    58.         {
    59.             Vector3 oldDirection = moveDirection;
    60.             moveDirection += (moveDeltas * delta);
    61.             moveDirection = ApplyFriction(moveDirection, delta);
    62.             moveDirection = ApplyDrag(moveDirection, delta);
    63.  
    64.             //controller.Move(moveDirection * delta); //euler method
    65.             controller.Move((oldDirection + moveDirection) * .5f * delta); //verlet method
    66.  
    67.             Debug.DrawRay(transform.position, transform.right, Color.red, 2);
    68.         }
    69.     }
    70.  
    71.     Vector3 ApplyFriction(Vector3 velocity, float deltaTime)
    72.     {
    73.         return velocity -= (velocity.normalized * (friction * deltaTime));
    74.     }
    75.  
    76.     Vector3 ApplyDrag(Vector3 velocity, float deltaTime)
    77.     {
    78.         return velocity *= Mathf.Exp(-drag * deltaTime);
    79.     }
    80.  

    We are using the verlet method, however from my tests, this verlet method does not seem like like drag and friction. However, at an updateTimestep of 4+ it seems to be just fine and better than euler method.
    It is important not to have the updateTimestep to high though since it seems energy is lost each iteration. So when I had a updateTimestep of 32, things were having trouble moving if the force was to low.

    Edit - This is me later in the future. I am now just using the semi-implicit euler integration which is shown at the bottom of the page here http://kahrstrom.com/gamephysics/2011/08/03/euler-vs-verlet/ , which is probably the euler method anyone is using on accident. It seems to be fine as long as I update it 4 times per frame.
    Im not using euler because its better or anything, but more so because it worked better with how I had things set up.

    Edit- future me again. I saw in a post on a different forum somewhere that talked about basically doing what was done above with updating 4 times per frame, except in a smarter way.
    Think of it like what fixedupdate does. When the framerate is low, it updates more times per frame. When our framerate is high, we dont want to be doing our update 4 times per frame since the framerate is already high enough to give nice results. So instead we do something like this...

    Code (CSharp):
    1.         int timesteps = maxUpdateTimestep;
    2.         if(!alwaysUseMaxUpdateTimestep)
    3.         {
    4.             int safePhysicsIterator = Mathf.CeilToInt(Time.deltaTime / (1f / (float)targetPhysicsFramerate)); //If our framerate is low, we run our physics more times for more accuracy.
    5.             timesteps = Mathf.Clamp(safePhysicsIterator, 1, maxUpdateTimestep);
    6.         }
    7.         float deltaTime = Time.deltaTime / (float)timesteps;
    8.         if(deltaTime > 0f)
    9.         {
    10.             for(int i = 0; i < timesteps; i++)
    11.             {
    12.                 UpdateMovementForces(deltaTime);
    13.             }
    14.         }

    Some example code...
    Code (CSharp):
    1.  
    2.     [Range(1,8)]
    3.     public int maxUpdateTimestep = 8; //higher = more movement accuracy, too high can = more loss of velocity(energy) over time (depending on your integration method I guess). 8 seems good for framerates 30+ with targetPhysicsFramerate being 240
    4.     public bool alwaysUseMaxUpdateTimestep; //not recommended as it is wasteful, but an option non the less.
    5.     [Range(30,480)]
    6.     public int targetPhysicsFramerate = 240; //If our framerate is low, we run our physics more times for more accuracy. This value is the desired physics framerate.
    7.  
    8.     Vector3 velocity;
    9.     //My addforce method adds the forces to these instead of velocity directly
    10.     Vector3 currentForces; //ForceModes Impulse and VelocityChange are added to this.
    11.     Vector3 currentForcesWithDeltas; //ForceModes Force and Acceleration goes to this
    12.  
    13.     void LateUpdate()
    14.     {
    15.         UpdateMovement();
    16.     }
    17.  
    18.     void UpdateMovement()
    19.     {
    20.         velocity += currentForces;
    21.    
    22.         int timesteps = maxUpdateTimestep;
    23.         if(!alwaysUseMaxUpdateTimestep)
    24.         {
    25.             int safePhysicsIterator = Mathf.CeilToInt(Time.deltaTime / (1f / (float)targetPhysicsFramerate)); //If our framerate is low, we run our physics more times for more accuracy.
    26.             timesteps = Mathf.Clamp(safePhysicsIterator, 1, maxUpdateTimestep);
    27.         }
    28.  
    29.         float deltaTime = Time.deltaTime / (float)timesteps;
    30.         if(deltaTime > 0f)
    31.         {
    32.             for(int i = 0; i < timesteps; i++)
    33.             {
    34.                 UpdateMovementForces(deltaTime);
    35.             }
    36.         }
    37.  
    38.         currentForces = Vector3.zero;
    39.         currentForcesWithDeltas = Vector3.zero;
    40.     }
    41.  
    42.     void UpdateMovementForces(float deltaTime)
    43.     {
    44.         Vector3 acceleration = velocity + (currentForcesWithDeltas * deltaTime); //Having currentForcesWithDeltas be added here with the subdivided deltaTime seems to give better results in regards to framerate independence.
    45.         acceleration = ApplyDrag(acceleration, drag, deltaTime);
    46.  
    47.         CollisionInfo collisionInfo = new CollisionInfo();
    48.  
    49.         //Symplectic Euler method (Our velocity is pretty much being set beforehand, even though it seems like its being set afterwards)
    50.         collisionInfo = TranslateAndHandleCollisions(acceleration * deltaTime);
    51.    
    52.         velocity = collisionInfo.velocity / deltaTime;
    53.     }

    With maxUpdateTimestep being 8 and targetPhysicsFramerate being 240, the lowest framerate we can get good framerate independent accuracy is at framerate 30 (since 240 / 8 = 30). The targetPhysicsFramerate is kinda like the framerate at which we think our physics will be good at and would want all our lower framerates to try and reach the same accuracy our targetPhysicsFramerate would give. (Im not sure if thats really what it is doing. All in all it might just be some number that is used to determine how many updates we are willing to do per frame.)
     
    Last edited: Dec 31, 2015
  12. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    It's fine, but more expensive, obviously.