2
\$\begingroup\$

OK so I learned that rb.MoveRotation is different from rb.transform.Rotate(). But I am having trouble using it and hoping you can help me.

I usually use transform.translate(Vector3.forward) (after using transform.Rotate(x,y,z)) , so that I know that will always make object move in the direction its facing. But this isnt working when I use rb.MovePosition. The object rotates when i remove the MovePosition line, but then for some reason doesn't when its there. I've tried multiple things as you'll see in the code.

I cant understand also why, if I use the bottom commented attempt in my code, and then press D in game, it will go in the direction its facing and then get stuck. I've commented in my code to try to explain whats happening.

What is the correct way to move the position of the player now I am using MoveRotation? I want to base my movement of the direction that the object is facing and not in world coordinates.

Thanks

    void HandleKeys()
{
    if (Input.GetKey(KeyCode.A))
    {
        if (!facingLeft)
        {
            rb.MoveRotation(Quaternion.Euler(0,-90,0));
            facingLeft = true;
        }
        //rb.transform.Translate(Vector3.forward * speed * Time.deltaTime); // if I remove this line, the character does turn and face left . But when its in he doesn't and both keys make him move right (and face right)
        // also tried:
        //rb.MovePosition(transform.position += Vector3.forward * speed * Time.deltaTime); // this results in it going Vector3.forward in world coords
        //**rb.MovePosition(transform.position += rb.transform.forward * speed * Time.deltaTime); // this makes him go RIGHT>?? and the objects facing RIGHT why????
    }
    if (Input.GetKey(KeyCode.D))
    {
        if (facingLeft)
        {
            rb.MoveRotation(Quaternion.Euler(0f, 90f, 0f));
            facingLeft = false;
        }
        rb.transform.Translate(Vector3.forward * speed * Time.deltaTime); // if i leave this in, and the one marked * above, then it works but only once, so if I press A then, D he heads right but wont turn round, if I press D first he just goes right
    }
    if (Input.GetKeyDown(KeyCode.Space) && IsGrounded())
    {
        rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
    }

}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Caution: The trick shown here of using input in FixedUpdate worked at the time it was written in 2017 — Unity at the time had fresh input available from the very beginning of the frame, contrary to the documentation and common wisdom.

As of my last tests however, it does not work in Unity 2019 — input is not updated until after the FixedUpdate step, so this trick to compress perceived latency no longer works.

Test the version you're using to be sure.


As I mentioned in chat, I haven't been able to reproduce this issue. Just uncommenting the first rb.transform.Translate line let the object rotate & move as expected.

That said, I'd be tempted to rejig things a little like this:

bool pressReleaseHandled;   

private void FixedUpdate() {
    // I tucked the movement code into FixedUpdate,
    // so it happens reliably before each Physics step.
    // This keeps our simulation consistent under
    // varying framerates, and can reduce visible
    // latency when using physics-based movement.
    HandleHold();
    HandlePressRelease(true);        
}

void Update () {
    // If our framerate is faster than the physics rate,
    // we might not get a physics step every frame, so
    // this call ensures we don't miss any fleeting
    // button down / button up events.
    HandlePressRelease(false);
}

void HandlePressRelease(bool isFixedStep) {
    // This pattern ensures we handle button down/up events
    // exactly once in the frame that they happen. Either in
    // the first physics update of this frame, or in the regular
    // Update() as a fallback if physics doesn't step this frame.

    // This can reduce perceived latency compared to jumping in Update
    // (since we're jumping with physics, we don't get to see the rise
    // until a physics step happens, and those come before Update)
    if (pressReleaseHandled == false) {            
        if (Input.GetButtonDown("Jump") && IsGrounded()) {
            rb.AddForce(Vector3.up * jumpImpulse, ForceMode.Impulse);
        }
    }
    pressReleaseHandled = isFixedStep;
}

void HandleHold() {
    // Held inputs are safe to process in every timestep.
    // They're valid for every time step in the frame, not
    // just instantaneously in one time step.

    // This lets us get a pure -1, 0, 1 value from the A & D keys
    float horizontal = Input.GetAxisRaw("Horizontal");

    // If we're facing opposite the direction of input, turn.
    if(transform.forward.x * horizontal < 0f)
        rb.MoveRotation(Quaternion.Euler(0, 90f * Mathf.Sign(horizontal), 0));

    // Move in the desired direction at our configured speed.
    // We set only the x component, so we don't interfere with jumping/falling.
    Vector3 velocity = rb.velocity;
    velocity.x = horizontal * speed;
    rb.velocity = velocity;
}

What's happening here:

  • I changed the GetKey methods to GetButton / GetAxis versions

    This lets players remap your controls, and means you can tweak your default button assignments in one place - the Input Manager - rather than having them distributed across multiple files.

    Treating left/right movement as an axis also lets us exploit some symmetry and simplify the code, rather than duplicating similar logic inside two if blocks.

    I used the Raw version of the axis method to get pure -1/0/1 values, without any gradations in-between, so the results would match the code you already had. If you take out the Raw part, Unity will apply some smoothing so the keys behave more like an analog stick, passing briefly through intermediate values when you change direction suddenly.

  • I changed the horizontal movement to use velocity

    While it's true we should be careful when setting the velocity, because this can inadvertently overrule other physics effects on the object, the original version using Translate/MovePosition has similar issues. Both say "move this way now" without asking if there are any other motions to take into account. ;)

    So, if we're confident that we really want to move this way, doing it with velocity isn't strictly more harmful, and it gets us a couple of wins:

    • Objects we push into along the way will be knocked away in correct proportion to our speed, rather than nudged out of intersection by the collision resolver (which might impart too little or too much speed, depending on the depth of the overlap)

    • We can enable rigidbody interpolation to smooth the result between physics steps, so we don't see a judder as we might with Rigidbody.MovePosition


    This does have a caveat: moving with velocity, your object will experience friction, which will make its net speed slightly lower than your speed constant (bump it up by a small fraction to compensate) and can impart a torque which can make your object tumble (lock rotation on the rigidbody to prevent this)

    If you want to go further with the physics-based motion, you can try accelerating the object left & right, rather than setting the velocity directly. This can give a pleasant juicy feel when the acceleration is tuned well, and can play nicer with competing influences on the object (eg. if I'm skidding backward after getting hit by something, I have to exert a few frames of acceleration to overcome the backward velocity before I can proceed forward). You can also vary the max acceleration based on the terrain (whee, ice levels!) or character state (eg. a knockback stun or fast reversal of direction when running).

    Here's a method you can use to accelerate toward a particular velocity horizontally while respecting a given maximum acceleration:

     void AccelerateHorizontally(float targetXVelocity, float maxAccel) {
         float change = targetXVelocity - rb.velocity.x;
    
         // If the delta-V is more than we're allowed in a single time step,
         // clamp to the max we're allowed, in the same direction.
         if(Mathf.Abs(change/Time.deltaTime) > maxAccel) {
             change = maxAccel * Time.deltaTime * Mathf.Sign(change);
         }
    
         rb.AddForce(change * Vector3.right, ForceMode.VelocityChange);
      }
    
  • I distributed the updates between Update and FixedUpdate

    Here's what it looks like when we do physics-based movement in Update:

    FU = FixedUpdate
    PS = Physics Step
    U = Update
    D = Display (new rendered frame visible to the player)
    
    Frame 1                          Frame 2
    --------------------------  | ---------------------------------|
    FU   PS   FU  PS   U        D   FU   PS   FU   PS   U          D
                       ^                 ^         ^               ^     
    Input modifies velocity      Velocity moves object    Player sees result
    

    Because the effects of our physics nudges don't get integrated until the next physics step, we get at least one rendered frame of latency before we can see the result.

    Compare this to doing it in FixedUpdate: (on frames where we get at least one physics step, that is. On frames where we don't, the two methods are equivalent)

    Frame 1                         
    --------------------------  | 
    FU   PS   FU  PS   U        D 
    ^    ^        ^             ^
    |   Velocity moves object   |    
    Input modifies velocity    Player sees result
    

    Now we can see the results of our input as early as the same frame it was issued. Our worst-case latency hasn't changed, but our best case has improved (and by tuning the physics update rate, we can ensure we're usually or always in the best case)

    Another nice perk is that, since our movement logic is now running at a fixed timestep, our simulation stays consistent & fair even when running at different rendered framerates. This can help maintain the same feel on different devices, and prevent certain classes of timing bugs.

    There's a common belief that input isn't usable in FixedUpdate, so you'll see some experienced Unity users consider code like I wrote above to be heresy. But while that might have been true in older versions of Unity, it doesn't seem to hold today. Test it and see. ;)

    The caveat here is that not every display frame gets a physics step if our rendering framerate is higher than the physics frequency, which is why I check input in Update to ensure we don't skip any momentary button down/up events if our timing is unlucky. (I kind of wish Unity had a guaranteed-to-run-every frame EarlyUpdate that precedes physics, to simplify cases like this)

\$\endgroup\$
5
  • \$\begingroup\$ Thank you so much!! This is really kind of you to take the time to show me exactly your best practice way of doing these things. I will redo it and make sure I understand every word you've mentioned. I wish there was a better way to reward you I really do you've helped me massively AGAIN! Very odd that my code "worked" for you and not for me but Im not gonna waste any time with that, just straight on to using your way now as you explained why so well. \$\endgroup\$ Dec 14, 2017 at 12:33
  • \$\begingroup\$ BTW - I've taken everything you've written on-board, and will make sure I keep using this practice for movement codes. I just wanted to confirm that you agree changing to a high gravity force in this type of game would be a good way to go? Other than that the two bits current blowing my mind are how to change it to use the Accel function you gave AND the Math Sign (which id never heard of until now!) But I can research both those on my own from here I think. Thanks again dude!! \$\endgroup\$ Dec 14, 2017 at 13:33
  • 1
    \$\begingroup\$ Don't take anyone's gamedev advice as gospel, especially mine. ;) There are millions of ways to make a game, and we each develop our own quirky preferences that are some mix of useful ideas and personal biases. So, take what you find useful, and don't be afraid to experiment and change, mix and match. There's nothing inherently wrong with increasing the default gravity value, and doing so can help you get consistently snappy fall rates across the board without a lot of script/multiplier micromanagement. That doesn't rule out also using scripted forces on the player to tune their rise/fall. :) \$\endgroup\$
    – DMGregory
    Dec 14, 2017 at 13:37
  • \$\begingroup\$ o7 understood 100%. And I have the accel code plugged in too now :) you're a true pal honestly! All the very best \$\endgroup\$ Dec 14, 2017 at 13:39
  • \$\begingroup\$ Hells yeah it feels awesome now! \$\endgroup\$ Dec 14, 2017 at 13:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .