Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Limiting player's maximum velocity affecting jumping

Discussion in 'Scripting' started by BinaryOrange, Jan 5, 2014.

  1. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    So, in following the "Roll-A-Ball" series of tutorials, I've been attempting to create my own physics-based game from the tutorials.

    I've modified the basic game so that the player can jump, and it works great. However, I also needed to limit the maximum speed (or velocity) of the player, because if I didn't, the player just ended up flying off the side of the level at ridiculous speeds.

    The solutions I found to limiting the maximum velocity of the player were many - some worked, and some didn't. The solution I found and has worked the best so far is to use Vector3.ClampMagnitude, like so:

    Code (csharp):
    1. // Make sure player isn't rolling too fast
    2. rigidbody.velocity = Vector3.ClampMagnitude(rigidbody.velocity, maxSpeed * Time.fixedDeltaTime);
    The problem, is that since it limits the velocity, this affects the "AddForce()" function, which in turn, as the title suggests, affects this jumping code...
    Code (csharp):
    1.  
    2. if (Input.GetKey(KeyCode.Space)  IsGrounded())
    3. {
    4.       rigidbody.AddForce(Vector3.up * jumpHeight);
    5. }
    I'm also using the "AddForce()" function to move the player with the input axis' described in the tutorials.

    I'm wondering, is it possible to limit the player's maximum velocity, but without affecting the "AddForce()" function? I CAN get the jumps to work just fine, as long as the jumpHeight variable matches the maxSpeed variable. But, the problems with that is that I was planning on using "AddForce()" to make things like wind tunnels with a HUGE amount of speed to push the player through certain parts of the level, and knock them off.

    Here's my whole code:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PlayerController : MonoBehaviour
    5. {
    6.     // Declare public variables
    7.     public GUIText gemText;
    8.     public GUIText winText;
    9.     public int amountOfGems;
    10.     public float speed;
    11.     public float maxSpeed;
    12.     public float jumpHeight;
    13.  
    14.     // Declare private variables
    15.     private int gems;
    16.     private float distanceToGround;
    17.  
    18.     // Initialize all the things!
    19.     void Start()
    20.     {
    21.         distanceToGround = collider.bounds.extents.y;
    22.         gems = 0;
    23.         winText.text = "";
    24.         SetCountText();
    25.     }
    26.  
    27.    
    28.     // Handle input in fixed update
    29.     void FixedUpdate()
    30.     {
    31.         // Get and store input values
    32.         float moveHorizontal = Input.GetAxis("Horizontal");
    33.         float moveVertical   = Input.GetAxis("Vertical");
    34.         Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
    35.  
    36.         // Apply movement
    37.         rigidbody.AddForce(movement * speed * Time.deltaTime);
    38.  
    39.         // Make sure player isn't rolling too fast (NOTE: Also affects jumping velocity, take into consideration when setting jumpHeight!)
    40.         rigidbody.velocity = Vector3.ClampMagnitude(rigidbody.velocity, maxSpeed * Time.fixedDeltaTime);
    41.                
    42.         // Check for jump
    43.         if (Input.GetKey(KeyCode.Space)  IsGrounded())
    44.         {
    45.             rigidbody.AddForce(Vector3.up * jumpHeight);
    46.         }
    47.     }
    48.  
    49.     // Determine if player is on ground or not
    50.     bool IsGrounded()
    51.     {
    52.         return Physics.Raycast(transform.position, -Vector3.up, distanceToGround + 0.1f);
    53.     }
    54.  
    55.     // Pickup gems
    56.     void OnTriggerEnter(Collider other)
    57.     {
    58.         if(other.gameObject.tag == "PickUp")
    59.         {
    60.             other.gameObject.SetActive(false);
    61.             gems += 1;
    62.             SetCountText();
    63.         }
    64.     }
    65.  
    66.     void SetCountText()
    67.     {
    68.         gemText.text = "Gems Collected: " + gems.ToString() +" / " + amountOfGems;
    69.         if (gems >= amountOfGems)
    70.         {
    71.             winText.text = "YOU WIN!";
    72.         }
    73.     }
    74. }
    Thanks to anybody who can offer a solution!
     
    zanouk likes this.
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I know you said you considered many approaches, but I've done stuff very similar to this in the past, and the solution that worked best for me was to have a sort of "air resistance" — a force opposite your direction of motion, that increases with the square of your speed. This is simple and realistic, and quite nicely gives your object a terminal velocity, which you can easily tweak via the constants in that force calculation. It plays nicely with the rest of the physics engine, because it's just one more force — whereas, what you're trying to do here, mucking with the velocity directly, is fighting the physics engine, and in my experience that has always led to unhappiness all around.

    Cheers,
    - Joe
     
  3. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    That's a good idea, but I'm not quite sure how I would implement that. I'm very new to all of these physics calculations. :rolleyes:
     
  4. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    When doing jump physics you set the velocity explicitly when going up with http://docs.unity3d.com/Documentation/ScriptReference/ForceMode.VelocityChange.html

    So it's a constant when going upward. You likely do not need to actually perform this more than once. Applying force over time for a jump is wrong. Your legs do not have jet thrusters which are active in the air. It seems to me you're using so much force, that when it finally does manage to overcome gravity, it simply explodes into space.
     
  5. BinaryOrange

    BinaryOrange

    Joined:
    Jul 27, 2012
    Posts:
    138
    Ah, that would help with making the jumping more consistent! Thanks hippocoder.

    Although, something of note, with my current code, if the player jumps the fall is actually quite slow due to limiting the velocity, and the jump itself is also pretty slow. Hopefully this helps a bit!

    EDIT: Hey, I may have found a solution that's easy!

    I have a function that checks if the player is grounded by casting a ray. I'm using it to check that if the player is grounded, that's when the velocity is limited. Problem was, when I jumped, it would then say the player wasn't grounded, the velocity would become regular, and the jump was VERY unrealistic, since it acted more like a rocket thruster.

    So, here's what I did:
    Code (csharp):
    1. // Handle input in fixed update
    2.     void FixedUpdate()
    3.     {
    4.         // Get and store input values
    5.         float moveHorizontal = Input.GetAxis("Horizontal");
    6.         float moveVertical   = Input.GetAxis("Vertical");
    7.  
    8.         // Make sure player isn't rolling too fast
    9.         if(IsGrounded ())
    10.         {
    11.             movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
    12.             rigidbody.velocity = Vector3.ClampMagnitude(rigidbody.velocity, maxSpeed * Time.fixedDeltaTime);
    13.         }else
    14.         {
    15.             movement = new Vector3(moveHorizontal/8, 0, moveVertical/8);
    16.         }
    17.  
    18.         // Move player
    19.         rigidbody.AddForce(movement * speed * Time.deltaTime);
    20.                
    21.         // Check for jump
    22.         if (Input.GetKey(KeyCode.Space)  IsGrounded())
    23.         {
    24.             rigidbody.AddForce(Vector3.up * jumpHeight, ForceMode.VelocityChange);
    25.         }
    26.     }
    By adjusting the values of moveHorizontal and moveVertical when the player isn't grounded, I can now get a more realistic jump! It works beautifully so far. Also, unexpected benefit is that the marble now officially feels "heavier", so it's even more realistic than I imagined it would be. :)

    However, I'm definitely interested in other ideas!
     
    Last edited: Jan 5, 2014