1
\$\begingroup\$

I'm surprised that this issue is nowhere to be found on these forums... Well at least when I searched. So, on my setup I have created a cube and applied a Rigidbody to the object with the following C# script:

using UnityEngine;
using System.Collections;

public class Movement : MonoBehaviour {
    Rigidbody movement;
    float distToGround;
    bool doubleJump=false;
    void Start() {
        movement = GetComponent<Rigidbody>();//Initialize movement as their rigidbody
        distToGround = GetComponent<Collider>().bounds.extents.y;//Get the distance from the user to the ground
    }
    void Update() {

        /*If they aren't moving the horizontal or vertical axes, cancel out their velocity.*/
        if(Input.GetAxis("Horizontal")==0&&isGrounded()) movement.velocity = new Vector3(0, movement.velocity.y, movement.velocity.z);
        if(Input.GetAxis("Vertical")==0&&isGrounded()) movement.velocity = new Vector3(movement.velocity.x, movement.velocity.y, 0);

        /*Horizontal Movement*/
        if(Input.GetAxis("Horizontal")>0) movement.velocity = new Vector3(5, movement.velocity.y, movement.velocity.z);
        if(Input.GetAxis("Horizontal")<0) movement.velocity = new Vector3(-5, movement.velocity.y, movement.velocity.z);

        /*Vertical Movement*/
        if(Input.GetAxis("Vertical")>0) movement.velocity = new Vector3(movement.velocity.x, movement.velocity.y, 5);
        if(Input.GetAxis("Vertical")<0) movement.velocity = new Vector3(movement.velocity.x, movement.velocity.y, -5);

        /*Jumping and Falling*/
        if(Input.GetButtonDown("Jump")&&isGrounded()) Jump();//For jumping
        if(!isGrounded()) {//If they are in the air
            Gravity();//They're free... free falling!
            if(Input.GetButtonDown("Jump")&&doubleJump) {//If they jumped once
                if(Input.GetAxis("Horizontal")==0&&Input.GetAxis("Vertical")==0) {//And they aren't moving
                    Jump(); doubleJump=false;//Double Jump
                }
                else if(Input.GetAxis("Horizontal")>0) {//Otherwise, if they're trying to dodge right...
                    movement.AddForce(10, 0, 0, ForceMode.Impulse); doubleJump=false;//Dodge Right!
                    print("Dodged!");
                }
            }
        }
        if(isGrounded()) doubleJump=true;//Reset Double Jump bool
    }

    void Gravity() {
        movement.AddForce(movement.velocity.x, -20, movement.velocity.z);//Simply push them down consistently
    }
    void Jump() {
        movement.velocity = Vector3.zero;
        movement.velocity = new Vector3(movement.velocity.x, 15, movement.velocity.z);//Simply push them up consistently
    }
    bool isGrounded() {
        return Physics.Raycast(transform.position, -Vector3.up, distToGround + 0.1f);//returns whether or not the object is grounded
    }
}

Everything works fine in this code except for the code that allows them to dodge mid-air. Whenever I perform a jump successfully and jump again without pressing an arrow key, it double jumps as expected.
Whenever I dodge right, I noticed that the object seemed to "teleport" or "skip" in the direction a bit instead of actually applying the force. The more force I apply, the more it skips.
Why is my code executing it in this way? Is it a glitch with Rigidbody or is it a problem with my code that I'm not realizing?

\$\endgroup\$
4
\$\begingroup\$

This might be the correct expected behaviour for the code you've written. Let's run through some math.

I'll assume for the moment that you're using Unity's default FixedUpdate rate of 50 Hz and that your character has a mass of 1.

If I press right on my stick (on the ground), my character gets an x velocity of 5 m/s.

If I do this in the air, then since the Gravity method applies lateral forces too (contrary to the comment which only mentions "downward" force) then they get a bonus of velocity.x * Time.fixedDeltaTime/movement.mass, for a net x velocity of 5.1 m/s.

If I dodge, then we also apply an impulse of 10 newton seconds, which divided by our mass instantaneously raises the x velocity to 15.1 m/s, almost triple our normal speed, for one step. The very next frame, because doubleJump == false, we don't run the dodge code again, so the velocity gets stomped back to 5.1 by the preceding lines.

Moment                   x Travel since previous frame
----------------------------------------------------
On ground stationary                  0
Start pressing right                  0.1
Jump                                  0.102
Dodge                                 0.302
Frame after dodging                   0.102

So, we have one frame where we jump triple our normal frame-to-frame distance, before resuming our typical speed. If we apply more force, or use a character with less mass, this jump gets bigger, but without any change in x velocity in the subsequent frame. That sounds consistent with your description of teleporting to the right.

The reason it looks like it's not applying the force is that you are overwriting this dodge velocity in the very next frame. In fact, you're doing that in Update() which will sometimes get called multiple times between physics steps, so there's the possibility you might get no dodge at all!

You need to be very judicious about when to set an object's velocity versus applying an incremental change - it's very easy to break expected physics this way.

So, recommendations to get more predictable, intuitive movement:

  • keep your physics manipulation in FixedUpdate, so you don't accidentally double-up or skip physics effects depending on the framerate.
  • Use AddForce in its various flavours, rather than directly setting velocity.
\$\endgroup\$
  • \$\begingroup\$ Alright, so from your post, I found the offending code you referenced. If you don't mind me asking, do you recommend I use ForceMode.Impulse since I want "snappy" controls? \$\endgroup\$ – RandomAnimeGamer Feb 11 '16 at 4:41
  • \$\begingroup\$ Apparently I couldn't edit my original comment, but the "snappy" controls I mentioned are for the GetAxis conditional statement since I'm replacing the velocity mutator with the AddForce() function. \$\endgroup\$ – RandomAnimeGamer Feb 11 '16 at 4:48
  • 1
    \$\begingroup\$ "Impulse" is good if you want "fire and forget" physics changes, like a jump that happens when the button gets pushed, but doesn't continue every frame. You can also use VelocityChange if you want to ignore the mass of the character. For things the player holds, it's not a bad idea to include a little time-based acceleration, but that's up to the style of your game. \$\endgroup\$ – DMGregory Feb 11 '16 at 4:53
  • \$\begingroup\$ Alright, everything's solved on my end now! In terms of the style of my game, it's a fighting game so I would definitely need Impulse for a lot of effects. \$\endgroup\$ – RandomAnimeGamer Feb 11 '16 at 6:46

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.