Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How do I make Unity try to reach and maintain a given velocity?

Discussion in 'Scripting' started by Magic-4e, Dec 5, 2020.

  1. Magic-4e

    Magic-4e

    Joined:
    May 9, 2018
    Posts:
    25
    Hello I have bean trying to figure out a way how to reach and maintain any given velocity with Unity's physics engine, but i havent bean able to do so.

    What I want is:
    - Add force when the velocity is lower than the target velocity.
    - Add force in the opposite direction when the velocity is higher than the target velocity.
    - And maintain the velocity when it is exactly equal to the target velocity.

    The condition is that the velocity may be changed by outside forces like: walls, attacks, obstacles, etc.

    To do this I have created the script included in this poste as an attachment.

    But it doesn't exactly work

    Instead the velocity is rapidly changing to slightly above and slightly below the desired velocity.

    Does anybody know I solution?

    Tell me if you want more Info.
     

    Attached Files:

  2. zypherem

    zypherem

    Joined:
    Sep 16, 2020
    Posts:
    50
    Here is the code displayed properly. Will help others help you.

    This is allot to sort through, Ill take a look at it. Might take a bit.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public partial class PlayerController : MonoBehaviour
    4. {
    5.     //*Public
    6.     //Targeted Objects
    7.     /// <summary>The camera stand/parent/folder that is used for left and right rotations</summary>
    8.     public GameObject cameraPivotRL;
    9.  
    10.     /// <summary>The camera stand/parent/folder that is used for gravity related rotations</summary>
    11.     public GameObject cameraGravityPivot;
    12.  
    13.     //Target scripts
    14.     /// <summary>A script that reads inputs</summary>
    15.     public PlayerInputs playerInputs;
    16.  
    17.     //User settings
    18.     //speed settings
    19.     /// <summary>The amount the player accelerates</summary>
    20.     public float accelerationForce;
    21.  
    22.     /// <summary>The amount the player decelerates</summary>
    23.     public float decelerationForce;
    24.  
    25.     /// <summary>The players desired velocity</summary>
    26.     public float desiredVelocity;
    27.  
    28.     /// <summary>How fast the velocity will reach the desired velocity after accelerating or decelerating</summary>
    29.     public float approximationSpeed;
    30.  
    31.  
    32.     //*Private
    33.     //Inputs
    34.     /// <summary>Directional inputs used for Moving the character</summary>
    35.     private Vector2 moveInput;
    36.     /// <summary>Magnitude/lengt of the moveInput Vector</summary>
    37.     private float moveInputMagni;
    38.  
    39.     //Camera pivot R/L Vectors (I use the camera stand so it's not influenced by up/down rotations)
    40.     /// <summary>The forward vector of the camera</summary>
    41.     private Vector3 camF;
    42.     /// <summary>The right vector of the camera</summary>
    43.     private Vector3 camR;
    44.  
    45.     //Gravity pivot Vector
    46.     /// <summary>The vector opposed to the gravity direction</summary>
    47.     private Vector3 gravityPivot;
    48.  
    49.     //Physics
    50.     /// <summary>The Unity component which stores and processes the players physics</summary>
    51.     private Rigidbody body;
    52.     /// <summary>The players current velocity</summary>
    53.     private float bodyVelocityMagnitude;
    54.  
    55.     /// <summary>The portion of the players velocity that is going in the desired direction</summary>
    56.     private float veloInDesiredDirectionMagni;
    57.  
    58.     /// <summary>The velocity the player is predicted to have after applying the acceleration force</summary>
    59.     private float predictedVelocity;
    60.  
    61.     //Debug bools
    62.     private bool isAccelerating = false;
    63.     private float isAcceleratingForce;
    64.     private bool isDecelerating = false;
    65.     private float isDeceleratingForce;
    66.     private bool isMaximizing = false;
    67.     private float isMaximizingForce;
    68.  
    69.     /// <summary>The force that will be applied</summary>
    70.     private float desiredForceMagni;
    71.     /// <summary>The amount of velocity that will be added after force is applied</summary>
    72.     private float addedVeloMagni;
    73.     /// <summary>The velocity from the previous step</summary>
    74.     private float velocityMagniMem;
    75.     /// <summary>The difference betwean the current velocity and the previous velocity</summary>
    76.     private float velocityChange;
    77.  
    78.     /// <summary>a simple bool to keep track of when this code is ran</summary>
    79.     private bool stepCheck = false;
    80.  
    81.     //#### Unity Functions ####
    82.     //## Awake is called when this object is spawned
    83.     private void Awake()
    84.     {
    85.         //Get rigidbody
    86.         body = transform.GetComponent<Rigidbody>();a
    87.  
    88.         //Get input script
    89.         playerInputs = GetComponent<PlayerInputs>();
    90.     }
    91.  
    92.     //## Fixed update is called 50 times per second
    93.     private void FixedUpdate()
    94.     {
    95.         //Reset debug bools
    96.         isAccelerating = false;
    97.         isDecelerating = false;
    98.         isMaximizing = false;
    99.  
    100.         //Draw current velocity
    101.         Debug.DrawRay(transform.position + new Vector3(0, 0.1f, 0), body.velocity * 0.1f, Color.white);
    102.  
    103.         //Measure the current velocity
    104.         bodyVelocityMagnitude = body.velocity.magnitude;
    105.  
    106.         //Get move inputs
    107.         moveInput = playerInputs.moveInput;
    108.  
    109.         //* Main move script block
    110.         if (moveInput.magnitude > 0)
    111.         {
    112.             //Get camera axis
    113.             camF = cameraPivotRL.transform.forward;
    114.             camR = cameraPivotRL.transform.right;
    115.  
    116.             //Get the current gravity direction
    117.             gravityPivot = cameraGravityPivot.transform.up;
    118.  
    119.             //Turn inputs into a direction relative to the camera
    120.             var desiredDirection = (camF * moveInput.y) + (camR * moveInput.x);
    121.  
    122.             //Determine the dirctional force that will be applied
    123.             var desiredForce = desiredDirection * accelerationForce;
    124.  
    125.             //Calculate how much velocity will be added after the force is applied
    126.             var addedVelo = desiredForce.normalized * (desiredForce.magnitude - (body.velocity.magnitude * body.drag)) / body.mass * Time.fixedDeltaTime;
    127.  
    128.             //Calculate how much of the current velocity is going in the desired direction
    129.             var veloInDesiredDirection = body.velocity - (body.velocity - desiredDirection.normalized * Vector3.Dot(body.velocity, desiredDirection.normalized));
    130.  
    131.             //Make the desired velocity shrink/grow based on the move inputs strengt
    132.             var desiredVelocityTemp = desiredVelocity * moveInput.magnitude;
    133.  
    134.             //Predict future velocity and add/subtract force accordingly
    135.             //will velocity in the desired direction be greater or ecual to desired velocity after aplying force?
    136.             if ((addedVelo + veloInDesiredDirection).magnitude >= desiredVelocityTemp && veloInDesiredDirection.magnitude >= desiredVelocityTemp && Vector3.Dot(body.velocity, desiredDirection.normalized) > 0)
    137.             {
    138.                 //will velocity in the desired direction still be greater than desired velocity after aplying negative force?
    139.                 if (((addedVelo * -1) + veloInDesiredDirection).magnitude > desiredVelocityTemp)
    140.                 {
    141.                     //apply force in oposite direction of desired direction
    142.                     body.AddForce(desiredForce * -1, ForceMode.Acceleration);
    143.  
    144.                     //Debug
    145.                     //a function to measure how much the velocity changed after 1 step
    146.                     StepCheck();
    147.  
    148.                     //draw ray that represents the negative force that is applied to the player
    149.                     Debug.DrawRay(transform.position, desiredForce * -1 * 0.005f, Color.cyan);
    150.  
    151.                     //this code is running
    152.                     isDecelerating = true;
    153.  
    154.                     //measure force
    155.                     isDeceleratingForce = desiredForce.magnitude * -1;
    156.                 }
    157.                 else
    158.                 {
    159.                     //calculate force requered to reach and maintain velocity
    160.                     var requiForce = desiredVelocityTemp * 1.4056579f + 20.6009236f;
    161.  
    162.                     //amplify force until desired velocity is reached
    163.                     requiForce *= ((desiredVelocityTemp - (desiredDirection.normalized * Vector3.Dot(body.velocity, desiredDirection.normalized)).magnitude) * approximationSpeed) + 1;
    164.  
    165.                     //apply force needed to reach and maintain desired velocity
    166.                     body.AddForce(desiredForce.normalized * requiForce, ForceMode.Acceleration);
    167.  
    168.                     //Debug
    169.                     //draw ray that represents the negative force that is applied to the player
    170.                     Debug.DrawRay(transform.position, desiredForce.normalized * requiForce * 0.005f, Color.green);
    171.  
    172.                     //this code is running
    173.                     isMaximizing = true;
    174.  
    175.                     //measure force
    176.                     isMaximizingForce = requiForce;
    177.                 }
    178.             }
    179.             else
    180.             {
    181.                 //apply force in desired direction
    182.                 body.AddForce(desiredForce, ForceMode.Acceleration);
    183.  
    184.                 //Debug
    185.                 //a function to measure how much the velocity changed after 1 step
    186.                 StepCheck();
    187.  
    188.                 //draw ray that represents the negative force that is applied to the player
    189.                 Debug.DrawRay(transform.position, desiredForce * 0.005f, Color.red);
    190.  
    191.                 //this code is running
    192.                 isAccelerating = true;
    193.  
    194.                 //measure force
    195.                 isAcceleratingForce = desiredForce.magnitude;
    196.             }
    197.  
    198.             //Code to turn player model
    199.             //if velocity is detected use velocity to turn player model
    200.             if (body.velocity.magnitude > 0.01f)
    201.             {
    202.                 transform.rotation = Quaternion.LookRotation(camF * Vector3.Dot(camF, body.velocity.normalized) + camR * Vector3.Dot(camR, body.velocity.normalized), gravityPivot);
    203.             }
    204.             //if not use inputs
    205.             else if (moveInput.magnitude > 0)
    206.             {
    207.                 transform.rotation = Quaternion.LookRotation((camF * moveInput.y) + (camR * moveInput.x), gravityPivot);
    208.             }
    209.  
    210.             //Debug variables
    211.             //the strengt of the applied force
    212.             desiredForceMagni = desiredForce.magnitude;
    213.  
    214.             //the amount of velocity that is predicted to be added after aplying force
    215.             addedVeloMagni = addedVelo.magnitude;
    216.  
    217.             //the current velocity strengt that is in the same direction as the desired direction
    218.             veloInDesiredDirectionMagni = veloInDesiredDirection.magnitude;
    219.  
    220.             //the velocity streng that is predected player is predicted to have in the desired direction after force is applied
    221.             predictedVelocity = (addedVelo + veloInDesiredDirection).magnitude;
    222.  
    223.             //draw predicted velocity
    224.             Debug.DrawRay(transform.position + new Vector3(0, 0.2f, 0), addedVelo.normalized * predictedVelocity * 0.1f, Color.magenta);
    225.  
    226.             //draw the velocity in the desired direction
    227.             Debug.DrawRay(transform.position + new Vector3(0, 0.1f, 0), veloInDesiredDirection * 0.1f, Color.yellow);
    228.  
    229.             //draw a line betwean the normal velocity and the velocity in the desired direction
    230.             Debug.DrawLine(transform.position + new Vector3(0, 0.1f, 0) + (veloInDesiredDirection * 0.1f), transform.position + new Vector3(0, 0.1f, 0) + (body.velocity * 0.1f), Color.gray);
    231.         }
    232.     }
    233.  
    234.     //#### My Functions ####
    235.  
    236.     private void StepCheck()
    237.     {
    238.         //A function to measure how much the velocity changed after 1 step
    239.         if (stepCheck && velocityMagniMem > 0)
    240.         {
    241.             velocityChange = body.velocity.magnitude - velocityMagniMem;
    242.             stepCheck = false;
    243.         }
    244.         else
    245.         {
    246.             velocityMagniMem = body.velocity.magnitude;
    247.             stepCheck = true;
    248.         }
    249.     }
    250. }
     
    Magic-4e likes this.
  3. Magic-4e

    Magic-4e

    Joined:
    May 9, 2018
    Posts:
    25
    Thank you.

    Tho I was asking this on a different form too.

    They gave me a solution that worked so far:

    Code (CSharp):
    1. //Set a target velocity
    2.  
    3. Vector3 targetVelocity = moveDirection * maxSpeed;
    4.  
    5. //Find the change of velocity needed to reach target
    6.  
    7. Vector3 velocityChange = targetVelocity - rb.velocity;
    8.  
    9. //Convert to acceleration, which is change of velocity over time
    10.  
    11. Vector3 acceleration = velocityChange / Time.fixedDeltaTime;
    12.  
    13. //Clamp it to your maximum acceleration magnitude
    14.  
    15. acceleration = Vector3.ClampMagnitude(acceleration, maxAcceleration);
    16.  
    17. //Then AddForce
    18.  
    19. rb.AddForce(acceleration, ForceMode.Acceleration);
    I am going to work with this now, but if you have a different solution I would like to hear that too. :)
     
    Last edited: Dec 5, 2020
  4. zypherem

    zypherem

    Joined:
    Sep 16, 2020
    Posts:
    50
    Nah not really. At this point I was looking into setting the velocity directly via a lerp/slerp to "Fake" smooth acceleration over time then lock it at a velocity desired once reached. But looks like the other guys went with a similar idea.

    Ultimately without some rocket science engineering skills I think the only way to really lock your char at a very specific set speed would be to directly control the velocity, even if doing this does not create an accurate depictions of physics....
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,336
    This code is called a "P controller," or "proportional controller." It may work fine for you as far as accelerating something always, such as against a universal drag.

    However beware that it won't ever actually reach the target velocity, but it will get real close.

    Where it will fail is if the drag is too high and it cannot reach anywhere near the target velocity.

    Another place the specific code above will fail is if the "acceleration" term goes negative, i.e, something else has driven the object to go faster than your target velocity and acceleration will become braking. The reason it will fail is your clamp only accounts for positive values.

    Another alternative is to just ramp the velocity directly up in the Rigidbody: read the velocity and adjust it by a fraction of its distance from your target speed. The fractional amount you adjust it by will change the "feel" of how close it tracks the speed.

    You can implement a P-D controller, but since you are controlling a speed, which is a derivative of position, I'm not exactly sure whether that would help you or if it would have other odd side effects. Check out readings on P, PD and PID controllers to see more stuff. Control logic is pretty arcane stuff, but there's been lots of work done with it.
     
    Ksanone and Magic-4e like this.
  6. Magic-4e

    Magic-4e

    Joined:
    May 9, 2018
    Posts:
    25
    Ow ok
    Well that wouldn't be what I was looking for.

    I wanted Unity to try to reach the desired velocity, but must still be sensitive to external forces.
    So changing the velocity directly would never work.

    But this guys code uses
    Rigidbody.AddForce
    so it does work like that.

    I think this is the only way than.
    Thank you for responding.

    And I hope someone else find the answer useful.
     
    zypherem likes this.
  7. Magic-4e

    Magic-4e

    Joined:
    May 9, 2018
    Posts:
    25
    The fail states you mentioned don't apply to what I am doing, so it is ok.
    Thanks for the detailed comment tho.

    Edit: ok the external forces bit might a bit, but I think I can work around that.
     
    Kurt-Dekker likes this.