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)