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

Discussion Best character movement physics configuration

Discussion in '2D' started by cespereira, Sep 24, 2022.

  1. cespereira

    cespereira

    Joined:
    Sep 24, 2022
    Posts:
    5
    Hi,

    In the game I'm currently working on, I'm starting with something very simple, a character automatically running forward (x-axis), and the player just controls the jumps. I've accomplished this with scripts similar to the following

    Running (On Update() function)
    Code (CSharp):
    1. if (isAlive)
    2.         {
    3.             rigidBody2D.velocity = new Vector2(moveSpeed, rigidBody2D.velocity.y);
    4.         }
    Jumping (Using OnJump of the new InputSystem)
    Code (CSharp):
    1.         if (value.isPressed && isOnTheGround && isAlive)
    2.         {
    3.             rigidBody2D.velocity += new Vector2(0f, jumpForce);
    4.         }
    Hence, as you see, I'm using velocity for the movement, and for each frame update changing this velocity.

    I've configured the gravity and moveSpeed settings so that the jumps feel good, for a starting speed. Although, on the next phase of the game, I want to increase the speed of the horizontal movement, but by just increasing the 'moveSpeed' variable, the jump feel alters completely, it makes a much bigger jump (higher X-axis distance, longer air time, ..). I want it to have the same fast impulsive jump up and down.

    My question is if there is a better way to do this, instead of just tweaking the gravity + moveSpeed until it feels right for a given speed? Is it even correct to change the gravity mid game? From a physics perspective it would make sense that the gravity remains the same throughout the game, if it's the same 'environment'.

    Thanks!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    37,210
    This made me curious so I did some testing. See enclosed package.

    Screenshot-mw4-133084686124709350.png

    Script:

    Code (csharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. // @kurtdekker
    6.  
    7. public class BouncyHeight : MonoBehaviour
    8. {
    9.     // change this for speed
    10.     public float SpeedMultiplier = 1;
    11.  
    12.     // overall shared "snappiness" of it all
    13.     const float GravityMultiplier = 50;
    14.     const float DesiredJumpHeight = 1.5f;
    15.     const float BaseSpeed = 2;
    16.  
    17.     Rigidbody2D rb2d;
    18.  
    19.     float prevX;
    20.  
    21.     float TotalGravityMagnitude;
    22.  
    23.     void Start()
    24.     {
    25.         rb2d = GetComponent<Rigidbody2D>();
    26.  
    27.         rb2d.gravityScale = 0.0f;    // we'll take care of that with a constant force 2d
    28.  
    29.         prevX = rb2d.position.x;
    30.  
    31.         ConstantForce2D gravityForce = rb2d.gameObject.AddComponent<ConstantForce2D>();
    32.  
    33.         // gravity magnitude scales as square of speed
    34.         TotalGravityMagnitude = SpeedMultiplier * SpeedMultiplier * GravityMultiplier;
    35.  
    36.         gravityForce.force = Vector2.down * TotalGravityMagnitude;
    37.     }
    38.  
    39.     void FixedUpdate ()
    40.     {
    41.         // auto-jump every 2 units sideways
    42.         float x = rb2d.position.x;
    43.  
    44.         const float JumpGap = 2;
    45.  
    46.         int previ = (int)Mathf.Round(prevX / JumpGap);
    47.         int nowi = (int)Mathf.Round(x / JumpGap);
    48.         prevX = x;
    49.  
    50.         bool jump = false;
    51.         if (previ != nowi)
    52.         {
    53.             jump = true;
    54.         }
    55.  
    56.         float speed = SpeedMultiplier * BaseSpeed;
    57.  
    58.         Vector2 velocity = rb2d.velocity;
    59.  
    60.         velocity.x = speed;
    61.  
    62.         if (jump)
    63.         {
    64.             // standard jump height calculation from dynamics
    65.             velocity.y += Mathf.Sqrt( 2 * DesiredJumpHeight * TotalGravityMagnitude);
    66.         }
    67.  
    68.         rb2d.velocity = velocity;
    69.  
    70.         // breadcrumbs - these have 3d colliders so they don't interact
    71.         var cube = GameObject.CreatePrimitive( PrimitiveType.Cube);
    72.         cube.transform.position = rb2d.position;
    73.         cube.transform.localScale = Vector3.one * 0.1f;
    74.     }
    75. }
    76.  
     

    Attached Files:

    cespereira likes this.
  3. cespereira

    cespereira

    Joined:
    Sep 24, 2022
    Posts:
    5
    That's awesome, thank you!

    That was exactly what I was looking to accomplish, regardless of the speed to have the same snappiness to the jump. The idea of the game is to have a continuously running object/player that needs to jump to avoid obstacles, and increase the speed as they progress through the platform. So it was important to keep the same 'feel' for the jump, the difficulty just increases due to the speed.

    The only change I've made to your script is to have the calculation of the gravity magnitude on the FixedUpdate(), instead of just the start, because I will be changing the speed multiplier mid game.

    Just a couple of questions:
    1. For this specific case, we are using FixedUpdate() instead of Update() because we are changing the velocity right? We are changing the physics of the object.

    2. My jump logic is on a different method, that will not be called from the FixedUpdate, because it's the OnJump() function to listen the Input System events. Does that make a difference? Because for the jump we are changing the velocity, and not on the FixedUpdate().

    Thank you again!!
     
  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,795
    Physics runs by default during the "FixedUpdate". Doing work like setting the velocity per-frame during "Update" is pointless because you might do that several times before the simulation runs. Of course, you can run the simulation per-frame if you like and do away with FixedUpdate but that's the reason.

    You always have to read input per-frame because that's how it comes in. You just don't want to be doing work on physics there because of what I said above.

    All in all, it just comes down to understanding the difference of Update and FixedUpdate. If you use the default FixedUpdate which happens at 50Hz but your framerate is 200Hz then you get 4 updates for every single FixedUpdate.
     
    cespereira likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    37,210
    Totally makes sense. It would be a nightmare to design an endless runner level that feels good at ALL speeds if the jump distance or height was changing.

    This is great, the only tweak I would suggest is:

    - read the OnJump and set a boolean in your update

    - check that boolean in FixedUpdate() to do the jump (and clear it when you jump)

    Not sure in your case it would make any discernible difference but physics is always best done in FixedUpdate().

    Two good discussions on Update() vs FixedUpdate() timing:

    https://jacksondunstan.com/articles/4824

    https://johnaustin.io/articles/2019/fix-your-unity-timestep
     
  6. cespereira

    cespereira

    Joined:
    Sep 24, 2022
    Posts:
    5
    Yeah that makes sense, I ended yo doing that.

    Working exactly as I wanted, thanks for all the help!!
     
  7. cespereira

    cespereira

    Joined:
    Sep 24, 2022
    Posts:
    5
    Although @Kurt-Dekker, I noticed that for higher speeds I wasn't getting the expected results. I tested it also on your code sample, and the same happens. Your code works perfectly for lower speeds, but at a certain point, with higher speeds the height start decreasing, as you see here:

    upload_2022-9-25_13-25-38.png

    The speed I used for each one was: 2, 5, 7, 10
     
    Last edited: Sep 25, 2022
  8. cespereira

    cespereira

    Joined:
    Sep 24, 2022
    Posts:
    5
    I think I figured out the issue, with such higher speeds the default rate of the FixedUpdate() calls is not enough, with higher speeds we loose too much precision. If I decrease this timestamp between calls from 0.02 to 0.001 for example, the result if exactly what we're looking for

    upload_2022-9-25_13-49-38.png

    Although, this will come at a high cost of CPU. So I guess I have to test and decide on a balance on CPU cost and the precision I want to achieve.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    37,210
    I think you are correct. Anything that is sampled at discrete intervals can exhibit issues like this as the sample length nears the total event length.