Jumping height is inconsistent. Why?

My cube is jumping inconsitently in terms of height. Sometimes really high, sometimes really low, sometimes in between. It seems pretty random.

The cube is sitting on a flat surface and has the following script:

if (Input.GetKey(KeyCode.Space) && CanJump == true) {
            GetComponent<Rigidbody>().AddForce (transform.up * jumpforce * Time.deltaTime);
        }

Now “CanJump” is a bool that is set to true when the colliders of the cube and the surface meet (so the cube wouldn’t be able to jump in midair).

I tried adding Time.deltaTime but it doesn’t help. Tried playing with the gravity value and the jumpforce value, it doesn’t fix the problem. Tried removing “CanJump” but it’s just making the problem more confusing and doesn’t seem to help.

Any ideas?

1 Like

A jump, like firing a bullet, is a single point of instantaneous force- not a sustained force. As such, when adding the force from a jump, you need to use the last optional parameter of AddForce, which is the ForceMode, and set it to ForceMode.Impulse.

Also, as this “AddForce” event is occurring only once, there’s no reason to multiply the result by the “time it took the last frame to process” (ie: Time.deltaTime). That’s only for things that occur over time, and this isn’t.

3 Likes

I see. But I should probably multiply the forward/backwards/left/right movement by Time.deltaTime, right? Because they are continuous

OK. Is there an example how I can use it exactly?

If you’re in the Update function (which is processed every frame), yes. The goal of Time.deltaTime is to smooth out things that happen over time by constantly multiplying it by the time the last frame took to the process. This means a faster framerate has a lower number (each update not moving things quite as far) and a slower framerate has a higher number (each update moving things a little further).

If you’re in the FixedUpdate function instead, you don’t have to bother, since it automatically takes care of it by nature of only occurring at fixed intervals (once every thirtieth of a second or something). There’s no variance, and it’s not based on framerate, so there’s no necessity to adjust it. However, keep in mind that Time.deltaTime is usually an extremely small value, like .05f at just 20 frames per second, so by multiplying by it you’re getting used to multiplying your “speed” by really small values. Therefore, switching between using it (in Update) or not using it (like in FixedUpdate) means “speed” is having like 20-30x more of an effect in FixedUpdate than you might think. If you want to keep your “speed” values consistent between Update and FixedUpdate methods, you can multiply by “Time.fixedDeltaTime” in FixedUpdate, which is the same thing as Time.deltaTime except it’s not variable from frame to frame (because FixedUpdate is processed at exact intervals).

Same thing as before, just adding the extra optional parameter (which is an enum- you should look up enums if you aren’t familiar with them).

if (Input.GetKey(KeyCode.Space) && CanJump)
{
    GetComponent<Rigidbody>().AddForce (transform.up * jumpforce, ForceMode.Impulse);
}

Use GetKeyDown, not GetKey. GetKey runs every frame.

if (Input.GetKeyDown(KeyCode.Space) && CanJump == true)
{
    GetComponent<Rigidbody>().AddForce (transform.up * jumpforce, ForceMode.Impulse);
}
2 Likes

Good catch. Didn’t even notice that.

Thanks a lot for this explanation, Lysander! I’ve taken notes of that.
I have a somewhat related problem.
I have a rigidbody and collider, but from some reason my cube is descending really really slowly, as though it has a parachute. Very unrealistic. I’m wondering, am I missing something at the code or something?

Looks like you’re applying your own gravity and you need to adjust the numbers?

The numbers used by the physics engine, and emulated more simply in StandardAssets scripts like CharacterController and CharacterMotor and such, assume a character height of about 2 units, I believe, with each “unit” equating to something like a meter. That means that gravity tends to be set up to be roughly 10 units/second squared. If your characters are all scaled far larger, then the default values will have very little effect on them (a box 100 units tall is 100 meters tall, and would take a full second to fall the distance of its own height at terminal velocity).

I would suggest that rather than play with the default gravity numbers and such that you make sure all of your objects are scaled more or less to that standard right away- it will cause you far less pain in the long run, I think. If you’re using rigidbodies though, you can easily adjust the universal gravitational pull in Edit => Project Settings => Physics, under “Gravity” at the top. Just figure out the difference methamatically first based on your average character height, rather than eye-balling it, IMO.

Thank you, I had “drag” set out too high. Yet strangely enough I have another problem on the same vein.

If the cube player is touching the side of the surface, it can jump ridiculously high, but if it stands on the surface it jumps just like I want it to. Any reason how could that happen?

Hanging on the side of the surface

Standing on the surface

Did you change GetKey to GetKeyDown like Stardog suggested? If your character controller doesn’t have a “collision normal” check (to make sure you’re actually standing on ground, rather than just pressed against a wall), you may be able to jump off of walls. Combined with the jump command triggering multiple times in quick succession from GetKey instead of GetKeyDown and it can have that effect, easily.

I can’t say anything definitively without seeing the character controller and how “CanJump” is being handled and such.

Well, I did at first change to GetKeyDown but then I noticed that from some reason the cube doesn’t always jump when I press the space key. When I change it back to GetKey this problem goes away…but then the “jumping too high problem” starts.

So it’s a double-edged sword. If I use GetKeyDown the space key doesn’t always make the cube jump. If I use GetKey then it doesn’t do that problem, but then I get really high jumps if the player is touching the walls of the surface like I mentioned earlier. Anyway, for the record, here is the full script:

using UnityEngine;
using System.Collections;

public class Driving : MonoBehaviour {
    public float speed = 0.1f; //player's moving speed
    public float jumpforce = 40.0f; //player's jumping force
    private bool CanJump; //whether the player can jump or not. true if it's on a surface, false if it's in the air
    public GameObject Deatheffect;


    void OnCollisionEnter (Collision col) {
        if (col.gameObject.name == "Enemy") { //if the player's collider hits an enemy's collider, go to death routine
            StartCoroutine(DelayedDeath());}
        if (col.gameObject.tag == "Surface") {
            CanJump = true;
        }
       
    }

    void OnCollisionExit (Collision col)
    {
        if (col.gameObject.tag == "Surface") {
            CanJump = false;
        }
    }
   
    // Update is called once per frame
    void FixedUpdate ()
    {
        // Player's Movement. Left side is for moving via the UI, right side is moving via keyboard
        if (UpClickClass.UpClick == true || Input.GetKey(KeyCode.W)) {
            GetComponent<Rigidbody>().AddForce (transform.forward * speed);
        }
        if (DownClickClass.DownClick == true || Input.GetKey(KeyCode.S)) {
            GetComponent<Rigidbody>().AddForce (-transform.forward * speed);
        }
        if (RightClickClass.RightClick == true || Input.GetKey(KeyCode.D)) {
            GetComponent<Rigidbody>().AddForce (transform.right * speed);
        }
        if (LeftClickClass.LeftClick == true || Input.GetKey(KeyCode.A)) {
            GetComponent<Rigidbody>().AddForce (-transform.right * speed);
        }
        if (JumpClickClass.JumpClick == true && CanJump == true || Input.GetKeyDown(KeyCode.Space) && CanJump == true) {
            GetComponent<Rigidbody>().AddForce (transform.up * jumpforce, ForceMode.Impulse);
        }
        if (transform.position.y < -10) { //player dies if he gets too low
            StartCoroutine(DelayedDeath());
        }
}
   

    IEnumerator DelayedDeath() //death routine
    {
        GetComponent<Renderer> ().enabled = false;
        GetComponent<Collider> ().enabled = false;
        Instantiate (Deatheffect, transform.position, Quaternion.identity);
        yield return new WaitForSeconds(2.0f);
        Debug.Log("Death");
        Application.LoadLevel (0);
    }
}

Using GetKey instead of GetKeyDown is not the answer to any problem you may be having- it’s a brutish hack and will cause endless problems. You also need to add “canJump = false;” in your jump conditional, so you can’t jump multiple times.

Your OnCollisionEnter method is a bit crude- you’ll detect when you’re touching something, but not whether it’s a wall or floor. You can distinguish between walls and floors in several ways- one is making them different objects with different tags, but even better is doing a “normal check” on the point of collision (the normal is angle of the surface point where contact is occurring). By checking if the normal’s Y value is above a certain threshold, you can make sure you’re touching a surface that’s pointing upward (how “upward” you can determine by the Y value- closer to 1 means closer to “straight up”- .5f means a slant of 45 degrees or less, which is pretty common). The collision parameter will have a member called contacts, which are a list of all of the points of contact between you and the colliding object. Each of those has a member normal which is what you’ll check. As long as the col.tag is “Surface”, iterate through each contact point and check the normals. You only need 1 that meets the criteria.

This code doesn’t really take into account “falling off of edges” either. If your ability to jump is determined entirely by the last collision, then stepping off of a cliff means you can still jump in mid-air. You can solve this issue by using a Raycast in update that fires straight down, constantly, and determines if there’s “ground” underneath you. This will likely replace the OnCollisionEnter method for ground detection entirely- the aforementioned “normal check” can occur on the point where the raycast is hitting instead of the point where another collider hits your collider.

You might want to include the StandardAssets script “CharacterController” and go in and look at how it works. It likely has everything you want, and more.

I do have CanJump = false in OnCollisionExit, so the Player can’t jump multiple times or in midair (or when falling off a cliff for that matter). That’s in fact why I made this boolean to begin with. Maybe you didn’t pay close enough attention to the script?

I appreciate the info with respect to the normals , but I’m not sure it’s the issue. I want the player to be able to jump even when it’s on the side of the surface. My problem is that it jumps too high when it’s on the side of the surface, not whether it can jump or not.

For the record, moving via keyboard is mostly for testing purposes, I have UI arrow keys for moving around and jumping, so GetKey or GetKeyDown is less relevant for me. The UI arrows work more like GetKey than GetKeyDown.

OnCollisionExit won’t occur in this script if you’re pressed against a wall while jumping- it’s not exiting the collision. It’s jumping too high because it’s jumping multiple times in consecutive frames and the force is compounding- the UI arrows working more like GetKey than GetKeyDown is a problem, if that’s the case, because it’s going to do the same thing. A jump is a single instantaneous explosion of force, thus “GetKeyDown”, which is a single frame- if you want it to be a constant compounding force instead, then it’s more like having a jet pack- which is the behaviour you’re seeing right now, albeit briefly.

Oh, you’re right! I didn’t think of it! slaps forehead

OK, I guess I’d just take the y vector and see if there is any positive movement in the y direction then CanJump = false otherwise CanJump = true

That should solve it, right? I’ll try it tomorrow.

Thanks Lysander, you’re a world of help.

I’m thinking that the reason why your GetKeyDown isn’t working all of the time is because, while moving, you’re not always 100% in contact with the ground (nearly imperceptible “bounciness” from friction and raw forces and such). That’s probably why people normally go the downward-raycast route with this. Put in a debug.log in OnCollisionExit and see how many times it fires just walking around normally- and each time it fires, it’s turning the ability to jump off for a bare fraction of a second until you come into contact with the ground again.

Anyways, the y velocity check is fine if you want to limit the character to only being able to jump off of walls and such when moving downward (perfectly viable- I think Metroid 3 did this), but a more interesting and realistic solution might be alter the AddForce so that it’s influenced by the normals you’re jumping off of (like N+). If you use the downward-raycast approach with this, you can deal with jumping in two entirely different ways: one if the raycast says that you’re on solid ground, and another if you’re in an OnCollisionStay with a “Surface”. In the latter case, the averaged normals could be used to shift the direction of the AddForce when jumping, launching you in the opposite direction so that keeping contact while jumping is impossible. On the other hand, jumping while on a flat surface, but while pressing against a wall, would allow you to make a second jump at any time- but a third wouldn’t work because you’d be pushed off from the wall.

Just a thought.

Although the “normals” idea is interesting to me and I’d sure love to learn it, I have no idea how to even approach it. Unless you have any good references/tutorials for me that I can read/watch?

By the more, and more importantly, how do I obtain the vector.y ?

I was thinking:

float JumpVector = transform.position.y.magnitude;

But that just produces any error… any ideas?

You have to get the current velocity from the rigidbody and check that for having a positive or negative y value. A negative y value would means that you have a velocity that’s “falling”, which should work as a patch for the problem you’re having.

To really fix it, here’s the raycast solution.
Here’s a (general idea) solution involving normals.

The “normals” route requires a lot of fine-tuning really to find the right balance, but since you’re iterating over the normals anyways it becomes pretty trivial to store the current average so that when a jump occurs, you can adjust the force added by the slope of the ground you’re standing on. Of course, there’s nothing stopping you from using parts of both solutions, either :slight_smile:

You can find a lot of character controller scripts on the wiki btw.

Thanks a bunch, Lysander… I just did

if (Input.GetKey(KeyCode.Space) && GetComponent().velocity.y == 0)

That seems to have solved it all :slight_smile:

I appreciate the link and the wiki reference to the character controller…will probably use it in the future ^^

1 Like