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

Question Rigidbody.AddForce Vector addition problem

Discussion in 'Scripting' started by RoeeHerzovich, Feb 9, 2021.

  1. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    I want to AddForce to a Rigidbody, it can both move and jump.
    However, when I add the force separately it works. But when I sum the vectors and add them together it acts strangely...


    Code (CSharp):
    1.             Vector3 offset = transform.TransformDirection(new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))).normalized * CurrentSpeed * Time.fixedDeltaTime;
    2.  
    3.             if (CanJump && isJump)
    4.             {
    5.                 Rb.AddForce(CalculateJump(), ForceMode.VelocityChange);
    6.                 IsGrounded = false;
    7.             }
    8.             Rb.AddForce(offset, ForceMode.VelocityChange);
    9.  
    10.  
    This one works, adding them on different lines. However:


    Code (CSharp):
    1.             Vector3 offset = transform.TransformDirection(new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))).normalized * CurrentSpeed * Time.fixedDeltaTime;
    2.  
    3.             if (CanJump && isJump)
    4.             {
    5.                 offset += CalculateJump();
    6. ForceMode.VelocityChange);
    7.                 IsGrounded = false;
    8.             }
    9.  
    10.             Rb.AddForce(offset, ForceMode.VelocityChange);
    11.  
    12.  
    This one makes the movement normal however the jump is more like teleportation and goes 3 times more up than it should...

    Also, if I do:


    Code (CSharp):
    1.             Vector3 offset = transform.TransformDirection(new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))).normalized;
    2.  
    3.             Rb.AddForce(offset * CurrentSpeed * Time.fixedDeltaTime, ForceMode.VelocityChange);
    4.  
    5.             if (CanJump && isJump)
    6.             {
    7.                 Rb.AddForce(CalculateJump(), ForceMode.VelocityChange);
    8.                 IsGrounded = false;
    9.             }
    It makes the movement way quicker than it should.. altho on debug it's the exact same vector, also by logic it is...

    My question is: why is this happening and how can I make it work by adding the vectors rather than adding force in two lines?

    Also, isJump is a boolean effected by a space input in the Update method and CanJump is a property with a boolean variable(set to true in this case)

    EDIT
    and yes.. this method is called via FixedUpdate...
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    You should be setting
    isJump
    in the
    Update()
    method, if it is keyed off of something like
    Input.GetKeyDown(Keycode.Space);
    , otherwise you could get varying numbers per press, or none at all.

    When you DO set it in Update(), you must also "consume" it in FixedUpdate(). That is, if you see it set, then use it to jump, and set it to false there in FixedUpdate(). Otherwise it might be acted upon more than once.

    Also, never clear it in Update() otherwise again it could be missed by FixedUpdate().

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
  3. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103

    As I said, "isJump is a boolean effected by a space input in the Update method"
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    You did.

    But I still don't see you clearing it in FixedUpdate() when you act on it.

    Which also must mean that you ARE clearing it in Update() otherrwise you'd be endlessly jumping.

    So... given the timing diagrams I can think of two separate ways that this misuse explains your varying jump height.
     
  5. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    When I debugged wheather I jump or not the jumping mechanism only runs once either way... whether I add the velocity or not... so the change of height only comes with the add of velocity, why is that?
     
  6. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    I just did as you said, only update adds the isJump and only FixedUpdate consumes it... same result..

    As I was saying, when I do not add the vectors together the result works(as shown in the first question), but when I add them it is different altho


    Code (CSharp):
    1. // This should do the same as:
    2.  
    3. rb.AddForce(vector1);
    4. rb.AddForce(vector2);
    5.  
    6. // This
    7.  
    8. rb.AddForce(vector1 + vector2);
    9.  
    10. // Yet it clearly doesn't
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    Well, this had to be investigated... could not let this mystery fester a moment longer.

    I have constructed a scene with a script that I believe allows you to explore what you're claiming above.

    I am unable to replicate any variance between pre-combined vectors and those combined internally.

    The script enclosed lets you try any of the four ForceModes so that rules out quirks there. Try 'em all, they all stay rock solid.

    I invite you to explore this and study the decisions going into the force based on ForceMode, and see if you can perhaps track down what your issue is.

    Full package below, this is the script:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class AddForceVectors : MonoBehaviour
    6. {
    7.     [Header("Vectors added together before use?")]
    8.     public bool CombinedBeforeUse;
    9.  
    10.     [Header("Pick yer poison:")]
    11.     public ForceMode forceMode;
    12.  
    13.     Rigidbody rb;
    14.     void Start()
    15.     {
    16.         Physics.gravity = new Vector3( 0, -10, 0);
    17.  
    18.         rb = gameObject.AddComponent<Rigidbody>();
    19.  
    20.         // let's spin it a bit just to know it's alive
    21.         rb.angularVelocity = Vector3.forward * 1.0f;
    22.         rb.angularDrag = 0;
    23.     }
    24.  
    25.     void FixedUpdate()
    26.     {
    27.         // tilted right, half a gravity worth up (we'll have two of 'em)
    28.         Vector3 vectorUpRight = new Vector3( 10, -Physics.gravity.y / 2, 0);
    29.  
    30. // handle all the different possible force modes:
    31. // from the unity docs: https://docs.unity3d.com/ScriptReference/ForceMode.html
    32. //        Force    Add a continuous force to the rigidbody, using its mass.
    33. //        Acceleration    Add a continuous acceleration to the rigidbody, ignoring its mass.
    34. //        Impulse    Add an instant force impulse to the rigidbody, using its mass.
    35. //        VelocityChange    Add an instant velocity change to the rigidbody, ignoring its mass.
    36.  
    37.         // default ForceMode.Acceleration, since we started from the value of
    38.         // gravity above, and gravity is acceleration (Physics101)
    39.         bool scaleToMass = false;
    40.         bool scaleToTime = false;
    41.         if (forceMode == ForceMode.Force)
    42.         {
    43.             scaleToMass = true;
    44.         }
    45.  
    46.         if (forceMode == ForceMode.Impulse)
    47.         {
    48.             scaleToMass = true;
    49.             scaleToTime = true;
    50.         }
    51.         if (forceMode == ForceMode.VelocityChange)
    52.         {
    53.             scaleToTime = true;
    54.         }
    55.  
    56.         if (scaleToMass)
    57.         {
    58.             vectorUpRight *= rb.mass;
    59.         }
    60.         if (scaleToTime)
    61.         {
    62.             vectorUpRight *= Time.fixedDeltaTime;
    63.         }
    64.  
    65.         // other side is reversed on X
    66.         Vector3 vectorUpLeft = vectorUpRight;
    67.         vectorUpRight.x = -vectorUpRight.x;
    68.  
    69.         if( CombinedBeforeUse)
    70.         {
    71.             rb.AddForce( vectorUpRight + vectorUpLeft, forceMode);
    72.         }
    73.         else
    74.         {
    75.             rb.AddForce( vectorUpRight, forceMode);
    76.             rb.AddForce( vectorUpLeft, forceMode);
    77.         }
    78.     }
    79. }
     

    Attached Files:

    RoeeHerzovich likes this.
  8. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103

    I truly can't explain why it behaves the way it does for me..
    I will share a bigger part of the code and perhaps it could reveal what is wrong..
     
  9. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    This is the working code(GoodFunctionality.PNG):
    Walking is smooth, jumping is smooth and at a proper height, nothing is wrong.
    (float jumpHeight = 5) and when I jump my game object gets approx 5 units higher than it was so perfectly well.

    Just changing this one line makes the jump behave like teleportation(FlawdJump.PNG):
    Walking still works smooth here, however, when I jump it teleports approximately 15 units higher than it was... if I divide the CalculateJump() vector value by 3 it will just teleport 5 units up...

    And this class is a child class of a bigger class, the Move() method is abstract and called by the parent class in FixedUpdate(ParentMethod.PNG):

    JumpCD.Use() is merely a simple cooldown class, I tried removing it to see if it has any effect, it doesn't
    offset.Round() is an extension method that performs System.Math.Round(2); on each of the Vector3 components. Aka:

    (Vector3(1.1234, 6.5421, 8.2345).Round() = Vector3(1.12, 6.54, 8.23))

    Again, doesn't have much of an effect, and tbh since I do not debug it in the GUI I might just remove the rounding altogether as it is not really needed.. Tho in 99% of the cases the values don't have more than 2 decimals anyways... so even after rounding they're identical...(I debugged)

    AdjustSpeed() returns the proper speed depends on whether you sprint, walk, or move at all(the speed is constantly shown in the editor so I can tell it's not the issue, plus the jumping mechanism has nothing to do with that).

    Manager is merely a singleton class with basic hyperparameters such as the gravity value, which in this case is 9.81.


    EDIT

    An interesting result shows that this code(InterestingResult.PNG):
    upon adding the vectors and adding the force right after, then returning, you get a perfect result..
    however, you can see me debugging there, also, as we know, adding vectors.. just work... there should've been no problem calling the Rb.AddForce() afterwards... yet when you call it earlier it does work..

    EDIT
    I feel so dumb... I just looked one second later to see the only differecne between them is the returned offset... I add a force... and still change the transform manually(at first instead of relaying on rigidbody I tried doing it myself)... I feel so dumb lmao the reason it teleports is because the vector is also added to the transform... damn it..

    Thank you so much for the help tho
     

    Attached Files:

    Last edited: Feb 10, 2021
  10. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,920
    Two minor things. One is that I've never seen a real example where FixedUpdate was better than Update for setting velocity. I've heard people say it, and it may have been true for some thing at one point, but I've done lots with setting, checking, limiting, and tweaking velocities and (and rotational velocity) and it's worked great. I don't think I've seen anything _wrong_ with using FixedUpdate, but it's an extra complication.

    The other is that replacing AddForce with velocity+= seems simpler. It's the same thing, you don't need to worry about the wrong mode and it happens instantly. Last I checked, AddForce "saves" your request and only adds to velocity later. The bad thing with that is your code then checks velocity for too-fast or slow or whatever; it seems fine; but only because of the delayed-action of AddForce.
     
  11. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    At first, I did not notice and put it on Update, the movement was funny...
    I added constraints later and removed the Time multiplication because AddForce does it for me, however, I did not constrain the Y-axis the same way because the jumpHeight is not always the same as the walking speed, thus should be constrained differently
     
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,756
    This is only true in the context of what kind of ForceMode you are using for AddForce().

    As you can see in my code above, 2 of the ForceModes do NOT require premultiplying by time, 2 of them do. It's important to understand the four different possibilities of scaling by time or scaling by mass, and the code example I made above handles it properly, as per the embedded documentation.
     
  13. RoeeHerzovich

    RoeeHerzovich

    Joined:
    Apr 12, 2020
    Posts:
    103
    I assume due to VelocityChange being instant it is not using time...

    But when I multiply by Time.fixedDeltaTime it's VERY slow, almost not moving at all