Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

(Help!) Player Can't Jump On Sloped Surfaces

Discussion in 'Scripting' started by LemonMontage420, Jun 10, 2020.

  1. LemonMontage420

    LemonMontage420

    Joined:
    May 28, 2020
    Posts:
    56
    Here's My Code:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.     public class Player_Movement : MonoBehaviour
    6. {
    7.     CharacterController characterCollider;
    8.     private CharacterController m_CharacterController;
    9.     private float m_OriginalHeight;
    10.     public CharacterController controller;
    11.     public float speed = 3f;
    12.     public float gravity = -19.62f;
    13.     public float jumpHeight = 3f;
    14.     public Transform groundCheck;
    15.     public float groundDistance = 0.4f;
    16.     public LayerMask groundMask;
    17.     public float SpeedMultiplier = 3.6f;
    18.     private float m_CrouchHeight = 0.9f;
    19.     Vector3 velocity;
    20.     bool isGrounded;
    21.  
    22.  
    23.  
    24.     void Start()
    25.     {
    26.  
    27.         m_CharacterController = GetComponent<CharacterController>();
    28.         m_OriginalHeight = m_CharacterController.height;
    29.      
    30.  
    31.  
    32.     }
    33.  
    34.     // Update is called once per frame
    35.     void Update()
    36.     {
    37.         RaycastHit hit;
    38.  
    39.         isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
    40.        
    41.         if (isGrounded && velocity.y < 0)
    42.         {
    43.             velocity.y = -2f;
    44.         }
    45.  
    46.  
    47.         float x = Input.GetAxis("Horizontal");
    48.     float z = Input.GetAxis("Vertical");
    49.  
    50.     Vector3 move = transform.right * x + transform.forward * z;
    51.  
    52.     controller.Move(move * speed * Time.deltaTime);
    53.  
    54.         if (Input.GetButtonDown("Jump") && m_CharacterController.height == m_CrouchHeight)
    55.         {
    56.             m_CharacterController.height = m_OriginalHeight;
    57.         }
    58.  
    59.  
    60.         else if (Input.GetButtonDown("Jump") && Physics.Raycast(transform.position, transform.TransformDirection(Vector3.down), out hit, 1, groundMask))
    61.         {
    62.             velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
    63.  
    64.         }
    65.        
    66.     velocity.y += gravity * Time.deltaTime;
    67.     controller.Move(velocity * Time.deltaTime);
    68.         if(m_CharacterController.height == m_OriginalHeight && Input.GetKey(KeyCode.LeftShift) == true && Input.GetKey(KeyCode.W) == true)
    69.         {
    70.             controller.Move(move * SpeedMultiplier * Time.deltaTime);
    71.         }
    72.        
    73.         }
    74.  
    75.    
    76.        
    77. }
    As the title says, I can't get my player to jump on a sloped surface, specifically on a surface that is slanted 14.6 degrees or more. (Excuse my incompetence if the answer is simple, I've been learning how to program for 10 days now).
     
  2. Cyber-Dog

    Cyber-Dog

    Joined:
    Sep 12, 2018
    Posts:
    352
    What happens when you land on a slope, need more detail.
     
  3. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    At a guess, when your character is on a slope above certain angle, the ray cast ground check is failing, which prevents jumping. You could try increasing the groundDistance value to something greater than 0.4f and see if that helps.
     
  4. LemonMontage420

    LemonMontage420

    Joined:
    May 28, 2020
    Posts:
    56
    The variable that ACTUALLY controls the max distance to ground is the "1" on line 60 (since it controls the vector length) but even if I change that, there's NO value that that variable can be where I both, can not do a double-jump on grounds that are flat and can jump once on slopes of up to 45 degrees. So I was thinking something different (other than a raycast) to get the distance when I'm on a slope (and then just have everything sloped greater than 14.6 have a "slope mask" and then reference that layer in the script). THE PROBLEM is that (since I still have no idea what I'm doing), I don't know what to use. Help appreciated, thanks in advance.
     
  5. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    Maybe you could do two raycasts, the first one long enough to hit the ground under all circumstances, then retrieve the RayCastHit.normal from that and do a second cast using that as the direction vector.
     
  6. LemonMontage420

    LemonMontage420

    Joined:
    May 28, 2020
    Posts:
    56
    Nevermind! I solved it! I ended up using a cooldown timer that would trigger if you were standing on something with the slope mask. The code would then use a bool called "start timer" if it detected you were on a sloped surface (using a slope layer mask). Then, if the player pressed "jump", the timer would go from it's default value, (0) and jump to the value I set to the float "cooldown" it would then go back down to zero gradually (using Time.deltaTime) and if the value was ever the value I set to "cooldown", (in this case 0.45 cuz I found it to be the "airtime"), it would rejister a jump. I'll leave the code here in case anyone in the future needs it:
    Code (CSharp):
    1. if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.down), out hit, slopeDistance, slopeMask))
    2.         {
    3.             startTimer = true;
    4.            
    5.         }
    6.         else
    7.         {
    8.             startTimer = false;
    9.             cooldownTimer = 0;
    10.            
    11.            
    12.         }
    13.         if (startTimer == true && cooldownTimer > 0)
    14.         {
    15.             cooldownTimer -= Time.deltaTime;
    16.         }
    17.         if (cooldownTimer < 0)
    18.         {
    19.             cooldownTimer = 0;
    20.         }
    21.         if (cooldownTimer == 0 && Input.GetButtonDown("Jump") && Physics.Raycast(transform.position, transform.TransformDirection(Vector3.down), out hit, slopeDistance, slopeMask))
    22.         {
    23.             cooldownTimer = cooldown;
    24.             velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
    25.         }
    26.        
     
    Munchy2007 likes this.
  7. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,731
    Glad you got it sorted :)
     
  8. NYX128

    NYX128

    Joined:
    Aug 2, 2020
    Posts:
    8
    better use Physics.CheckSphere i guess
     
  9. NYX128

    NYX128

    Joined:
    Aug 2, 2020
    Posts:
    8
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEditor.Experimental.GraphView;
    4. using UnityEngine;
    5.  
    6. public class PlayerController : MonoBehaviour
    7. {
    8.     public CharacterController controller;
    9.     public Transform cam;
    10.  
    11.     public float speed = 6;
    12.     public float gravity = -9.81f;
    13.     public float jumpHeight = 3;
    14.     Vector3 velocity;
    15.     bool isGrounded;
    16.  
    17.  
    18.     public Transform groundCheck;
    19.     public float groundDistance = 0.4f;
    20.     public LayerMask groundMask;
    21.  
    22.     float turnSmoothVelocity = 0.1f;
    23.     public float turnSmoothTime = 0.1f;
    24.     Vector3 PlayerScale;
    25.     Vector3 CrouchScale;
    26.    
    27.  
    28.  
    29.     private void Start()
    30.     {
    31.         Cursor.lockState = CursorLockMode.Locked;
    32.         Cursor.visible = false;
    33.        
    34.         PlayerScale = transform.localScale;
    35.         CrouchScale = new Vector3(0f, transform.localScale.y /2f, 0f);
    36.         GlassScale = Glasses.transform.localScale;
    37.      
    38.     }
    39.     // Update is called once per frame
    40.     void Update()
    41.     {
    42.         Movement();
    43.     }
    44.     void StartCrouch()
    45.     {
    46.         transform.localScale = transform.localScale - CrouchScale;
    47.         speed = speed * 2f;
    48.        
    49.  
    50.  
    51.     }
    52.     void StopCrouch()
    53.     {
    54.         transform.localScale = PlayerScale;
    55.         speed = speed * 0.5f;
    56.        
    57.     }
    58.     void Movement()
    59.     {
    60.  
    61.         //jump
    62.         isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
    63.  
    64.         if (isGrounded && velocity.y < 0)
    65.         {
    66.             velocity.y = -2f;
    67.         }
    68.  
    69.         if (Input.GetButtonDown("Jump") && isGrounded)
    70.         {
    71.             velocity.y = Mathf.Sqrt(jumpHeight * -2 * gravity);
    72.         }
    73.         //gravity
    74.         velocity.y += gravity * Time.deltaTime;
    75.         controller.Move(velocity * Time.deltaTime);
    76.         //walk
    77.         float horizontal = Input.GetAxisRaw("Horizontal");
    78.         float vertical = Input.GetAxisRaw("Vertical");
    79.         Vector3 direction = new Vector3(horizontal, 0f, vertical).normalized;
    80.  
    81.         if (direction.magnitude >= 0.1f)
    82.         {
    83.             float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + cam.eulerAngles.y;
    84.             float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref turnSmoothVelocity, turnSmoothTime);
    85.             transform.rotation = Quaternion.Euler(0f, angle, 0f);
    86.  
    87.             Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward;
    88.             controller.Move(moveDir.normalized * speed * Time.deltaTime);
    89.         }
    90.         if (Input.GetKeyDown(KeyCode.LeftControl))
    91.         {
    92.             StartCrouch();
    93.         }
    94.         if (Input.GetKeyUp(KeyCode.LeftControl))
    95.         {
    96.             StopCrouch();
    97.         }
    98.     }
    99. }
    100.  



    Its messy but i think its good enough to understand
    additionally it has also does crouching if thats something u want
     
    Last edited: Nov 17, 2020
  10. NYX128

    NYX128

    Joined:
    Aug 2, 2020
    Posts:
    8
    extra tip: its best to use cinemachine if ur doin a 3rd person controller
     
  11. LemonMontage420

    LemonMontage420

    Joined:
    May 28, 2020
    Posts:
    56
    This post is pretty old, and I've solved my problem a while ago. The approach used by you works in controlled enviroments, such as distinct incline changes over terrain (mostly used if you're testing on a plane rather than terrain and inclines are the result of other distinct objects such as other planes at a 45 degree angle to form a ramp). I have discovered that, although this is a great technique to use when testing on flat grounds with distinct inclines (as I've made a similar solution to the one you proposed above). This doesn't really scale well once I replace my testing ground for an actual terrain. So I decided to find the exact incline of the ground under the player (using two raycasts and a spherecast)
    and using that information to determine the ground is "flat" (under 10 degrees) or "sloped" (over 10 degrees). And I'm making a first person player experience. But thanks for your reply anyway!
     
  12. A_Marraff

    A_Marraff

    Joined:
    Dec 12, 2015
    Posts:
    5
    This forum helped me come up with my own solution. I change the size of my Physics.CheckSphere depending on whether or not I am on a slope. Smaller when I am below a set angle threshold, and bigger when I am above it. I hope this is of help to someone in the future.

    I have my code in these two scripts:

    -S_GroundCheck
    -S_PlayerJump

    Note: The component for S_GroundCheck is a child of my player controller. It stays at the player's feet (0,0,0).

    • I start this process by declaring these variables in S_GroundCheck. You don't have to use the headers, I just like to for organization in the inspector.
    Code (CSharp):
    1.  
    2.     [Header("Ground Detection")]
    3.     public LayerMask GroundIdentificationLayerMask; //Set to whatever needs to be detected as "Ground." Public so some of my other scripts can use it for their own casts without creating and setting another layermask.
    4.     private bool isGrounded;
    5.     public bool IsGrounded => isGrounded; //Publically accessible IsGrounded variable that cannot modify the state of the private variable isGrounded.
    6.  
    7.     [Header("Cast Size Modification")]
    8.     private float castSize; //Modifies the size of the ray or spherecast depending on user preference.  I use spherecast.
    9.     [SerializeField] private float castSizeSlope; //Inspector set variable
    10.     [SerializeField] private float castSizeNoSlope; //Inspector set variable
    11.  
    12.     [Header("Slope Angle Detection")]
    13.     [SerializeField] private float slopeDefinition; //An angle higher than this number will be considered a slope.
    14.     private float detectedSlopeAngle; //Used to determine whether IsOnSlope() returns true or not.  I can also use this to view what angle the check detects under the player's feet in the debug view of the inspector.
    15.     private RaycastHit slopeHit; //The ray that hits the ground and gives us info about it.
    16.  
    17.  
    • Below in the S_GroundCheck class, I have a public bool for detecting if I'm standing on a slope that will be checked by our code. It is public because I check the bool in my Player Movement script as well to determine speed while on a slope.
    Code (CSharp):
    1.   public bool IsOnSlope()
    2.     {
    3.         if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, 0.5f))
    4.         {
    5.              detectedSlopeAngle = Vector3.Angle(slopeHit.normal, Vector3.up);
    6.              if (detectedSlopeAngle >= slopeDefinition) { return true; }
    7.              else return false;
    8.         }
    9.         else return false;
    10.     }

    • After reading that, the code changes the value of castSize in Update().
      Code (CSharp):
      1.  private void Update()
      2.     {
      3.          if (IsOnSlope() == true) //Modify castSize using our modification variables.
      4.         {
      5.             castSize = castSizeSlope;
      6.         }
      7.         else
      8.         {
      9.             castSize = castSizeNoSlope;
      10.         }
      11.     }

    • Then, that value plugs into my ground check in LateUpdate() like so:
    Code (CSharp):
    1.  void LateUpdate()
    2.     {
    3.                 isGrounded = Physics.CheckSphere(
    4.                 transform.position,
    5.                 castSize, //-> Plugs in here
    6.                 GroundIdentificationLayerMask
    7.             );
    8.  
    9.     }
    • At this point, in S_PlayerJump (or whatever you call it), remember to check for IsGrounded in order to allow your jump to happen. Which would look something like this:
    Code (CSharp):
    1.  
    2. if(inputManagerScript.PlayerHoldingJump() == true) //->Get the input however you're getting it for your project.  I use the new input system.  I check if the player is holding the button because it allows for easy consecutive jumps.
    3. {
    4.      If(groundCheckScript.IsGrounded == true)
    5.       {
    6.      Jump();
    7.      }
    8. }
    Finally, remember to set the inspector values from S_GroundCheck for:
    castSizeSlope (I use 0.2)
    castSizeNoSlope (I use 0.025)
    slopeDefinition (I use 10, because above 10~15 is where my smaller cast size stops detecting the ground)

    Hope that helps! :)
     
    Last edited: May 2, 2022