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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Inconsistent Jump Functionality

Discussion in '2D' started by sieks-, Dec 1, 2019.

  1. sieks-

    sieks-

    Joined:
    Aug 29, 2019
    Posts:
    17
    Hi all,

    I know questions of this nature have popped up before on the forums, however I'm finding most people's solutions are for those using AddForce and Rigidbody2D's built in velocity to move their characters around.
    I am handling things very similarly to a Unity Learn tutorial where custom gravity and collision detection is used.

    Attached is a segement of my code to a 2d side scroller controller which is having issues with jumping; I am having problems getting consistent jumping physics. The height of the object appears to be inconsistent. Also, without using Input.GetKey (rather than GetKeyDown), the jumping sometimes doesn't occur at all.

    This leads me to believe that my object is sinking into the ground a little or something - the height of the jump is inconsistent because for the few frames where the object is stuck in the ground, the velocity reduces too quickly for the whole jump to be taken into effect. But I just can't figure out why my object would be sinking in the first place...

    The code is based largely on https://learn.unity.com/tutorial/li...character-controller#5c7f8528edbc2a002053b68e << this tutorial. I've tried to move away from direct velocity manipulation and start with acceleration, as this would provide more control over the feel of character movement, however the translation hasn't been as straight forward as I thought it would be. When I run the PhysicsObject and PlayerPlatformerController scripts from the tutorial, things are behaving perfectly.

    Any help would be greatly appreciated.

    Code (CSharp):
    1.  //move and rotate the object
    2.     void FixedUpdate()
    3.     {
    4.         externalAcceleration = Vector2.zero;
    5.         inputAcceleration = Vector2.zero;
    6.  
    7.         SetRotation();
    8.  
    9.         //GetInput();
    10.         ComputeVelocity();
    11.  
    12.  
    13.         finalMove = MoveCast(deltaPosition.x * moveAlongGround, false);
    14.  
    15.         SetPosition(finalMove);
    16.  
    17.         grounded = false;
    18.  
    19.         finalMove = MoveCast(deltaPosition.y * Vector2.up, true);
    20.         SetPosition(finalMove);
    21.     }
    22.  
    23.     //get the input
    24.     void Update()
    25.     {
    26.         GetInput();
    27.     }
    28.  
    29.     //get the raw input in {x}
    30.     private void GetInput()
    31.     {
    32.         inputVector.x = InputAccelModifier * Input.GetAxis("Horizontal");
    33.  
    34.         //externalAcceleration = Vector2.zero;
    35.         //inputAcceleration = Vector2.zero;
    36.  
    37.  
    38.         if (Input.GetKey(KeyCode.Space))
    39.         {
    40.             jump = true;
    41.         }
    42.  
    43.         if (Input.GetKeyDown(KeyCode.LeftShift))
    44.         {
    45.             sideBoost = true;
    46.         }
    47.     }
    48.  
    49.     //modify the input acceleration, add gravity and other external forces
    50.     private void ComputeVelocity()
    51.     {
    52.  
    53.         moveAlongGround = new Vector2(groundNormal.y, -groundNormal.x);
    54.  
    55.  
    56.         if (inputVector.x == 0)
    57.         {
    58.  
    59.             //drag relative to the ground normal
    60.             if ((grounded)) //&& (groundNormal.y > minDragNormalY) && (groundNormal.y < minGroundNormalY))
    61.             {
    62.                 externalAcceleration.x -= GroundDrag * totalVelocity.x;
    63.                 inputAcceleration.x = -GroundDrag * totalVelocity.x;
    64.             }
    65.             else
    66.             {
    67.                 //externalAcceleration.x -= AirDrag * totalVelocity.x;
    68.             }
    69.         }
    70.         else
    71.         {
    72.             inputAcceleration.x += inputVector.x;
    73.         }
    74.  
    75.  
    76.         if (jump)
    77.         {
    78.             if (grounded)
    79.                 {
    80.                 externalVelocity += JumpModifier * groundNormal;
    81.             }
    82.             //externalAcceleration += JumpModifier * groundNormal;
    83.  
    84.             jump = false;
    85.         }
    86.  
    87.         if (sideBoost)
    88.         {
    89.             externalAcceleration.x += SideBoost;
    90.             sideBoost = false;
    91.         }
    92.  
    93.         externalAcceleration.y -= GravityModifier;
    94.  
    95.  
    96.         inputVelocity += inputAcceleration * Time.deltaTime;
    97.         inputVelocity = inputVelocity.magnitude > MaxInputVelocity ? Vector2.ClampMagnitude(inputVelocity, MaxInputVelocity) : inputVelocity;
    98.  
    99.  
    100.  
    101.         externalVelocity += externalAcceleration * Time.deltaTime;
    102.         externalVelocity = externalVelocity.magnitude > MaxExternalVelocity ? Vector2.ClampMagnitude(externalVelocity, MaxExternalVelocity) : externalVelocity;
    103.  
    104.  
    105.         totalVelocity = inputVelocity + externalVelocity;
    106.         deltaPosition = (inputVelocity + externalVelocity) * Time.deltaTime;
    107.  
    108.  
    109.     }
    110.  
    111.     //casts to avoid collision - returns the length to move in the input direction
    112.     private Vector2 MoveCast(Vector2 move, bool yMovement)
    113.     {
    114.         float distance = move.magnitude;
    115.  
    116.         int hitCount = rbody.Cast(move, hitBuffer, distance + ShellRadius);
    117.  
    118.         for (int i = 0; i < hitCount; i++)
    119.         {
    120.             Vector2 currentNormal = hitBuffer[i].normal;
    121.  
    122.  
    123.             if (currentNormal.y > minGroundNormalY)
    124.             {
    125.                 grounded = true;
    126.                 if (yMovement)
    127.                 {
    128.                     grounded = true;
    129.                     groundNormal = currentNormal;
    130.                 }
    131.             }
    132.  
    133.  
    134.             float projection = Vector2.Dot(totalVelocity, currentNormal);
    135.             if (projection < 0)
    136.             {
    137.                 //VV turning these on can cause a direction to
    138.                 //'lose' its function; holding down right does nothing
    139.                 //and the character does not move right
    140.                 //inputVelocity = inputVelocity - projection * currentNormal;
    141.                 //externalVelocity = externalVelocity - projection * currentNormal;
    142.             }
    143.  
    144.             float modifiedDistance = hitBuffer[i].distance - ShellRadius;
    145.             move = modifiedDistance < distance ? move.normalized * modifiedDistance : move.normalized * distance;
    146.         }
    147.  
    148.         return move;
    149.     }
    150.  
    151.     //actually move the rigidbody
    152.     private void SetPosition(Vector2 move)
    153.     {
    154.  
    155.         if (move.magnitude > minMoveDistance)
    156.         {
    157.             rbody.position += move;
    158.         }
    159.  
    160.     }
    I know that it is usually said that you should avoid using Rigidbody2D.position or its direct transform to modify the object's position, however this is the approach used within an official Unity tutorial.
    I did try to use MovePosition before, but it took me a little bit of confused debugging to realize that MovePosition cannot be used in this way since it has problems being called twice within the same FixedUpdate.
     
  2. MisterSkitz

    MisterSkitz

    Joined:
    Sep 2, 2015
    Posts:
    833
    GetKey() is for holding the button down. As soon as you release, the button ceases to fire. GetKeyDown() fires one time once the button is pressed and it stops firing; however, it would be more consistent because it isn't pressure sensitive.

    GetKeyUp() fires once when the key is released.

    So I think you using GetKey() may be the issue here.
     
  3. sieks-

    sieks-

    Joined:
    Aug 29, 2019
    Posts:
    17
    Yes, I know the difference between the two. As I stated, that only changes the flavour of the problem. When I use GetKeyDown, sometimes no jump at all occurs, in addition to having inconsistent height (I only am using GetKey here temporarily as I'm designing/debugging and trying things out).
     
  4. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @sieks-

    I would do it like @MisterSkitz said - use GetKeyDown. And also do this in Update and set jump state.

    Unless you reset the jump detected state each Update, KeyDown should work without missed key presses, as Update usually happens many times before next FixedUpdate call.

    When you detect the jump state being true (be it in Update or FixedUpdate) only then add jump force or set velocity of rb once. Don't add to these after. While jumping, reduce vertical velocity as long as you are not grounded or terminal velocity is not reached.

    When you are grounded again, reset the jump state.

    This should work IMO, and jump height should be consistent. However I didn't quite get what you are doing with your external velocity variable, it looks to be doing quite a lot.
     
    Last edited: Dec 2, 2019
  5. sieks-

    sieks-

    Joined:
    Aug 29, 2019
    Posts:
    17
    @eses

    Unless I am misunderstanding something, I believe this is what I am doing. As you mentioned, Update can run multiple times in between FixedUpdate calls, therefore a GetKeyDown AND GetKeyUp could be triggered before FixedUpdate is ran, and using your approach, a jump could be missed. This is why I only 'give the keys' to the Boolean determining jump state to the FixedUpdate code:

    Code (CSharp):
    1.         if (jump)
    2.         {
    3.             if (grounded)
    4.             {
    5.                 externalVelocity += JumpModifier * groundNormal;
    6.             }
    7.             //externalAcceleration += JumpModifier * groundNormal;
    8.            
    9.            jump = false;    //can only be turned off here, after being read once
    10.         }
    Only this portion has the ability to turn off the toggle. So it should only run once. My point is that, with either method, I am still getting missed jumps and/or reduced height jumps.

    The external velocity variable is there because I am separating effects like gravity and boosting from actual input acceleration. I ran into some issues where a jump was not responding to an increase in jump power because I had a 'master velocity' which had one max speed, so instead now I have a higher MaxExternalSpeed and MaxInternal speed to keep the two separate.
     
  6. sieks-

    sieks-

    Joined:
    Aug 29, 2019
    Posts:
    17
    Just as an update,

    I am running this on a different machine than I usually do so I don't know if this will work later on today, but I have changed my jump to a larger value (20 instead of 8), and the jump appears to be consistent...
     
  7. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @sieks-

    "therefore a GetKeyDown AND GetKeyUp could be triggered before FixedUpdate is ran, and using your approach, a jump could be missed"


    No. You probably didn't read carefully what I wrote and missed my point. When you get the key press, you set the state. Only when you register / react to key being pressed you reset that "jump was pressed" bool or whatever you use and set the jump velocity. That way you get the key press in Update, and you can react to it later no matter how many frames has passed.

    Your issue of not every jump registers is a sign of jump bool resetting before you use it or getting input in FixedUpdate.
     
    MisterSkitz likes this.
  8. sieks-

    sieks-

    Joined:
    Aug 29, 2019
    Posts:
    17
    @eses

    This is impossible, refer to my code snippet I posted last:

    Code (CSharp):
    1.         if (jump)
    2.         {
    3.             if (grounded)
    4.             {
    5.                 externalVelocity += JumpModifier * groundNormal;
    6.             }
    7.             //externalAcceleration += JumpModifier * groundNormal;
    8.          
    9.            jump = false;    //can only be turned off here, after being read once
    10.         }
    This is run within FixedUpdate. The ONLY place where the jump is set to false. The issue seems to be either grounded being false at the wrong moments, or like I said, that my object is sinking a little bit into the ground. But with my MoveCast code I don't see how this sinking could be possible.
     
  9. sieks-

    sieks-

    Joined:
    Aug 29, 2019
    Posts:
    17
    So yeah, the code is fine, apparently the only issue was having my jump value set too low. I can't seem to force inconsistency with a higher value. I still feel uneasy not knowing the cause, but I do have a solution at least.

    Thank you guys so much for taking the time to respond, I really appreciate it.