Best character movement physics configuration

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)

if (isAlive)
        {
            rigidBody2D.velocity = new Vector2(moveSpeed, rigidBody2D.velocity.y);
        }

Jumping (Using OnJump of the new InputSystem)

        if (value.isPressed && isOnTheGround && isAlive)
        {
            rigidBody2D.velocity += new Vector2(0f, jumpForce);
        }

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!

This made me curious so I did some testing. See enclosed package.

8462147--1123577--Screenshot-mw4-133084686124709350.png

Script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// @kurtdekker

public class BouncyHeight : MonoBehaviour
{
    // change this for speed
    public float SpeedMultiplier = 1;

    // overall shared "snappiness" of it all
    const float GravityMultiplier = 50;
    const float DesiredJumpHeight = 1.5f;
    const float BaseSpeed = 2;

    Rigidbody2D rb2d;

    float prevX;

    float TotalGravityMagnitude;

    void Start()
    {
        rb2d = GetComponent<Rigidbody2D>();

        rb2d.gravityScale = 0.0f;    // we'll take care of that with a constant force 2d

        prevX = rb2d.position.x;

        ConstantForce2D gravityForce = rb2d.gameObject.AddComponent<ConstantForce2D>();

        // gravity magnitude scales as square of speed
        TotalGravityMagnitude = SpeedMultiplier * SpeedMultiplier * GravityMultiplier;

        gravityForce.force = Vector2.down * TotalGravityMagnitude;
    }

    void FixedUpdate ()
    {
        // auto-jump every 2 units sideways
        float x = rb2d.position.x;

        const float JumpGap = 2;

        int previ = (int)Mathf.Round(prevX / JumpGap);
        int nowi = (int)Mathf.Round(x / JumpGap);
        prevX = x;

        bool jump = false;
        if (previ != nowi)
        {
            jump = true;
        }

        float speed = SpeedMultiplier * BaseSpeed;

        Vector2 velocity = rb2d.velocity;

        velocity.x = speed;

        if (jump)
        {
            // standard jump height calculation from dynamics
            velocity.y += Mathf.Sqrt( 2 * DesiredJumpHeight * TotalGravityMagnitude);
        }

        rb2d.velocity = velocity;

        // breadcrumbs - these have 3d colliders so they don't interact
        var cube = GameObject.CreatePrimitive( PrimitiveType.Cube);
        cube.transform.position = rb2d.position;
        cube.transform.localScale = Vector3.one * 0.1f;
    }
}

8462147–1123574–BouncyHeight.unitypackage (6.56 KB)

1 Like

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!!

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.

1 Like

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

Yeah that makes sense, I ended yo doing that.

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

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:

The speed I used for each one was: 2, 5, 7, 10

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

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.

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.