0
\$\begingroup\$

I've been designing some basic controls for a 2D platformer and managed to get the feeling that I've been looking for when moving and jumping, but something is not quite right, it's not very responsive.

Most of the inputs are detected, but a lot of times, for example, the simple action of pressing some key to jump is completely ignored and the player ends up falling when the key was pressed at the right time. I don't consider that I'm developing on a laptop with low specs at all if that matters.

Looking at some other questions it has always been said that input detection should be inside the Update function and that physics related stuff should be on FixedUpdate, however, it has not always been very clear what exactly should be on which function, as far as I understand it should go something like this...right?


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

public class PlayerController : MonoBehaviour {
    public float speed;
    public float jumpForce;
    public float groundCheckRadius;

    public Transform groundCheck;
    public LayerMask groundMask;

    private float direction;

    private bool facingRight;
    private bool shouldJump;
    private bool isGrounded;
    private bool shouldFall;

    private SpriteRenderer renderer;
    private Rigidbody2D rigidbody;

    void Awake() {
        this.renderer = GetComponent<SpriteRenderer>();
        this.rigidbody = GetComponent<Rigidbody2D>();
    }


    void Start () {
        this.facingRight = true;
        this.shouldJump = false;
        this.isGrounded = false;
        this.shouldFall = false;
    }


    void Update () {
        this.direction = Input.GetAxisRaw("Horizontal");
        this.isGrounded = Physics2D.OverlapCircle(this.groundCheck.position, this.groundCheckRadius, this.groundMask);
        this.shouldJump = Input.GetKeyDown(KeyCode.S) && this.isGrounded;

        if (this.facingRight && this.direction < 0 || !this.facingRight && this.direction > 0)
            this.Flip();

        if (Input.GetKeyUp(KeyCode.S) && this.rigidbody.velocity.y > 0)
            this.shouldFall = true;
    }

    void FixedUpdate() {
        Vector2 velocity = this.rigidbody.velocity;

        if (this.shouldFall) {
            velocity.y = 0f;
            this.shouldFall = false;
        }

        if (this.shouldJump)
            velocity.y = this.jumpForce;

        velocity.x = this.speed * this.direction;

        this.rigidbody.velocity = velocity;
    }

    private void Flip() {
        this.renderer.flipX = this.facingRight;
        this.facingRight = !this.facingRight;
    }
}



I'm not sure if I got the idea of Update vs FixedUpdate correctly because of this lack of responsive jumps, and I'm guessing that if I add some more mechanics, later on, those will react the same or worse.

Something that I noted is that if I move everything to update it now feels right every time I press the jump button, but I'm still not sure if that should be the right approach when building tight controls.

And I still feel lost about what everyone means by "You should manage physics in FixedUpdate", what do you mean by physics here?

\$\endgroup\$
1
  • \$\begingroup\$ Your problem is that Update() and FixedUpdate() methods are called at different rates. As a result flags set in the Update() method can be reset before FixedUpdate() can even read them. \$\endgroup\$ – Exerion May 5 at 12:36
0
\$\begingroup\$

There’s no explicit guarantee about how many times FixedUpdate gets called between calls to Update, so it can get called any number of times, including zero. Your problem may be that shouldJump is getting set to true and then to false again before it gets a chance to be applied. Try instead only setting it to true in Update, and setting it to false in FixedUpdate. You should also check isGrounded in FixedUpdate.

    void Update () {
        if (Input.GetKeyDown(KeyCode.S))
            this.shouldJump = true;
    }

    void FixedUpdate() {
       if (this.shouldJump) {
            if (Physics2D.OverlapCircle(this.groundCheck.position, this.groundCheckRadius, this.groundMask))
                velocity.y = this.jumpForce;
            this.shouldJump = false;
       }
    }
\$\endgroup\$
3
  • \$\begingroup\$ Note that this approach can introduce a frame of latency. The jump input is detected in frame 1's Update, after frame 1's FixedUpdates have run, so the jump doesn't take effect until frame 2. If you check for input in FixedUpdate, you can often act on that input in the same frame it's detected. You just need a little care to make sure each input is handled exactly once. \$\endgroup\$ – DMGregory Jul 8 '18 at 16:38
  • \$\begingroup\$ Trying to handle input in both Update and FixedUpdate as your tweet recommends still presents the same issue, I tried to build the project to see if there was any difference, but the input detection was even worse. \$\endgroup\$ – Fer Vargas Jul 8 '18 at 23:23
  • \$\begingroup\$ I think it might be something to do with check if the player is grounded, since it's only been checked on FixedUpdate, but I don't really like the idea to check both in Update and FixedUpdate. \$\endgroup\$ – Fer Vargas Jul 8 '18 at 23:31
0
\$\begingroup\$

Unfortunately a little input lag is likely to happen when using the Unity Physics engine. This is because physics updates happen after the FixedUpdate functions are called and input updates are processed just before the Update functions. Typically FixedUpdate tries to run 50 times per second while Update tries to run 30 times per second, 60 times per second, or more. As a result you can't expect your FixedUpdate function to always run immediately before or after your Update function. I would suggest you ready this page of documentation for more information on how Unity decides the execution order of functions.

Although I haven't tested it, I think these changes should prevent commands from getting lost during execution, however you will still experience about 0.02 seconds of input lag while you wait for the physics engine. This should behave the same if you were to run everything inside the Update function since Physics updates do not get processed until after FixedUpdate.

// Read input updates and process non-physics updates.
private void Update()
{
    direction = Input.GetAxisRaw("Horizontal");
    
    shouldJump = shouldJump || (Input.GetKeyDown(KeyCode.S) && isGrounded); // jump may not be processed immediately so remember old jump requests

    if ((facingRight && direction < 0) || (!facingRight && direction > 0)) // There was a parenthesis bug here
        Flip();

    if (Input.GetKeyUp(KeyCode.S))
        shouldFall = true;
}

private void FixedUpdate()
{
    isGrounded = Physics2D.OverlapCircle(groundCheck.position, groundCheckRadius, groundMask);

    Vector2 velocity = rigidbody.velocity;

    // be careful with this check if you need to move your character up in the Y for any non-jump related reasons.
    if (shouldFall && rigidbody.velocity.y > 0)
    {
        velocity.y = 0f;
        shouldFall = false;
    }

    if (shouldJump) // always put { } even on a one line if to save yourself future bugs
    {
        velocity.y = jumpForce;
        shouldJump = false;
    }

    velocity.x = speed * direction;

    rigidbody.velocity = velocity; // if you call rigidbody chances are this is a physics update.
}

I believe (can't test to be sure) the main reason you are occasionally losing jump commands is because your old logic over wrote the shouldjump variable every update frame. In the case you have high fps you could expect unity to execute your update logic like this: Update(), Update(), Update(), FixedUpdate(), Update(), Update(), FixedUpdate(), Update(), Update(), Update(), FixedUpdate(), etc.. As a result some of your jumps never had a chance to update the characters velocity. That being said, I am personally a hypocrite and often run physics in the Update function, for simple toy games and experiments it doesn't hurt to do stuff the wrong way once in a while so don't stress best practices like only running physics updates in the FixedUpdate function too much.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.