Search Unity

Help with Small Climbing Movement Issue

Discussion in 'Scripting' started by Syviery, May 28, 2017.

  1. Syviery

    Syviery

    Joined:
    Aug 2, 2014
    Posts:
    4
    For the last few hours I have been trying to debug a small bit of my code and have tried a variety of different solutions. At the moment it seems that with my current knowledge base I am unable to solve this. Hopefully, someone else will know a method to solve my issue.

    So, I have a small bit of code that handles the movement when you are currently climbing a wall:
    Code (CSharp):
    1.             //Feed moveDirection with input.
    2.             moveDirection = new Vector3 (Input.GetAxisRaw ("Horizontal"), Input.GetAxisRaw ("Vertical"), 0);
    3.             moveDirection = new Vector3 (Camera.main.transform.TransformDirection (moveDirection).x, Camera.main.transform.TransformDirection (moveDirection).y, 0);
    4.  
    5.             verticalVelocity = 0;
    6.  
    7.             //Multiply it by speed.
    8.             moveDirection *= speed;
    9.  
    10.             //Making the character move
    11.             controller.Move (moveDirection * (Time.deltaTime));
    This code works perfectly when trying to climb up a large cube on either the front or the back. The issue comes up when you attempt to climb up one of the sides. The upwards movement still works perfectly because the upwards direction does not change but unfortunately when I try to climb from side-to-side it instead draws me away from the block.

    If anyone has any suggestions to try I would greatly appreciate it, thank you.
     
  2. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    1,065
    Is this a 2D or 3D game? Although from the looks of your code it probably is a 2D sidescroller. Also, your variable named "controller" suggest you are using the CharacterController, most likely the 2D version. Point remains that I, or anyone else reading, should not need to infer this much information, and even then I doubt I'm right since your mention of moving sideways throws me off.

    Your code can be shortened as well:
    Code (CSharp):
    1. moveDirection = new Vector3(Input.GetAxisRaw("Horizontal"),Input.GetAxisRaw("Vertical"),0);
    2. moveDirection = Camera.main.transform.TransformDirection(moveDirection);
    3.  
    4. verticalVelocity = 0;
    5.  
    6. controller.Move(moveDirection * speed * Time.deltaTime);
    And come to think of it, you have not known how you are making the character climb either. I see the "verticalVelocity" variable but I don't see how it is being used if at all. I'd rather not speculate anymore. I believe we need the following information before any meaningful answers can come about:
    - Camera View
    - Control Scheme
    - Genre maybe, would give a good analogy of how you want things to work
     
  3. Syviery

    Syviery

    Joined:
    Aug 2, 2014
    Posts:
    4
    I apologize for the lack of information, I will give you more information and hopefully we can come to a conclusion. I am making a 3D game, at the moment you can move around in the space and I am working on a small climbing mechanic to allow you to traverse upwards walls which is what this is for. Your assumption that I am using a character controller for the character is also correct. The vertical velocity variable on the other hand is used to let the character move while in mid-air; it is just in there so you do not gain downward speed while climbing on a wall. To cover your other questions... The camera is a third person perspective that can be rotated around the player using the second joystick and the player moves forward dependant on which direction the camera is facing. The control scheme works with both AWSD/space and joystic/X on a controller. I'm not sure what you would like to know in terms of genre since I have not fleshed out much more than some movement ideas I would like to realize but I guess adventure might be accurate as the genre since you will be at the very least navigating and exploring certain spaces.

    Just in case it helps give more context here is the full script:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class player : MonoBehaviour {
    5.     //Variables for changing movement
    6.     public float speed = 2.5F;
    7.     public float jumpSpeed = 8.0F;
    8.     public float gravity = 20.0F;
    9.     private float verticalVelocity = 0;
    10.     public float rotationSpeed = 6F;
    11.  
    12.     //climbing variables
    13.     private bool climbToggle = false;
    14.     private float climbHoldTimer = 0f;
    15.  
    16.     //Movement and rotation variables
    17.     private Vector3 moveDirection = Vector3.zero;
    18.     private Vector3 faceDirection = Vector3.zero;
    19.  
    20.     //Sounds
    21.     public AudioClip jumpSound;
    22.  
    23.     void Update() {
    24.         CharacterController controller = GetComponent<CharacterController>();
    25.  
    26.         //raycast forward distance
    27.         RaycastHit hit;
    28.         Ray climbRay = new Ray (new Vector3 (transform.position.x, transform.position.y - 0.25f, transform.position.z), faceDirection);
    29.  
    30.         if (Physics.Raycast (climbRay, out hit, 1f)) // check if you are close enough to hold on
    31.         {
    32.             //Debug.DrawRay(transform.position, faceDirection * 50, Color.green);
    33.  
    34.             if (controller.isGrounded)
    35.             {
    36.                 //GRAB WALL FROM GROUND
    37.                 if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer < 0.4f) //if the player is pushing up but the timer hasn't reached the limit yet
    38.                 {
    39.                     climbHoldTimer += (1 * Time.deltaTime);
    40.                 }
    41.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") < 0.1 && climbHoldTimer < 0.4f) //if the player releases the key before the timer has reached the limit
    42.                 {
    43.                     climbHoldTimer = 0;
    44.                 }
    45.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer >= 0.4f) //if the player succesfully holds the key
    46.                 {
    47.                     climbHoldTimer = 0;
    48.                     climbToggle = !climbToggle;
    49.                 }
    50.  
    51.                 //RELEASE WALL ON GROUND
    52.                 if (climbToggle == true && Input.GetAxisRaw ("Vertical") < -0.1 && climbHoldTimer < 0.2f) //if the player is pushing down but the timer hasn't reached the limit yet
    53.                 {
    54.                     climbHoldTimer += (1 * Time.deltaTime);
    55.                 }
    56.                 else if (climbToggle == true && Input.GetAxisRaw ("Vertical") > -0.1 && climbHoldTimer < 0.2f) //if the player releases the key before the timer has reached the limit
    57.                 {
    58.                     climbHoldTimer = 0;
    59.                 }
    60.                 else if (climbToggle == true && Input.GetAxisRaw ("Vertical") < -0.1 && climbHoldTimer >= 0.2f) //if the player succesfully holds the key
    61.                 {
    62.                     climbHoldTimer = 0;
    63.                     climbToggle = !climbToggle;
    64.                 }
    65.             }
    66.             else
    67.             {
    68.                 //GRAB WALL IN-AIR
    69.                 if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer < 0.2f) //if the player is pushing up but the timer hasn't reached the limit yet
    70.                 {
    71.                     climbHoldTimer += (1 * Time.deltaTime);
    72.                 }
    73.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") < 0.1 && climbHoldTimer < 0.2f) //if the player releases the key before the timer has reached the limit
    74.                 {
    75.                     climbHoldTimer = 0;
    76.                 }
    77.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer >= 0.2f) //if the player succesfully holds the key
    78.                 {
    79.                     climbHoldTimer = 0;
    80.                     climbToggle = !climbToggle;
    81.                 }
    82.  
    83.                 //RELEASE WALL AT BUTTON PUSH
    84.                 if (Input.GetButtonDown ("Action") && climbToggle == true)
    85.                 {
    86.                     climbToggle = false;
    87.                 }
    88.             }
    89.         }
    90.         else if (Physics.Raycast (climbRay, out hit, 1.25f))
    91.         {
    92.             if (climbToggle == true) {transform.Translate(Vector3.forward * Time.deltaTime);}
    93.         }
    94.         else
    95.         {
    96.             //RELEASE WALL AT EDGES
    97.             if (!controller.isGrounded && climbToggle == true && !Physics.Raycast (climbRay, out hit, 1.5f))
    98.             {
    99.                 climbToggle = false;
    100.             }
    101.         }
    102.  
    103.         //QUICK CHECK FOR IF CLIMBING
    104.         if (climbToggle == false)
    105.         {
    106.             if (controller.isGrounded)
    107.             {
    108.                 verticalVelocity = 0;
    109.                 //transform.localeulerAngles = new Vector3(transform.localeulerAngles.x, Camera.main.transform.localeulerAngles.y, transform.localeulerAngles.z);
    110.  
    111.                 //Jumping
    112.                 if (Input.GetButtonDown ("Jump"))
    113.                 {
    114.                     verticalVelocity = jumpSpeed;
    115.                     if (jumpSound != null)
    116.                     {
    117.                         GetComponent<AudioSource> ().PlayOneShot (jumpSound, 1.0F);
    118.                     }
    119.                 }
    120.  
    121.             }
    122.  
    123.             //Feed moveDirection with input.
    124.             moveDirection = new Vector3 (Input.GetAxisRaw ("Horizontal"), 0, Input.GetAxisRaw ("Vertical"));
    125.             moveDirection = new Vector3 (Camera.main.transform.TransformDirection (moveDirection).x, 0, Camera.main.transform.TransformDirection (moveDirection).z);
    126.  
    127.             //Rotate towards movement direction
    128.             faceDirection = moveDirection;
    129.              
    130.             if (Input.GetAxisRaw ("Horizontal") > 0.1 || Input.GetAxisRaw ("Vertical") > 0.1 || Input.GetAxisRaw ("Horizontal") < -0.1 || Input.GetAxisRaw ("Vertical") < -0.1)
    131.             {
    132.                 transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation (new Vector3 (faceDirection.x, 0, faceDirection.z)), Time.deltaTime * rotationSpeed);
    133.                 moveDirection.y = 2F;
    134.             }
    135.          
    136.             //Multiply it by speed.
    137.             moveDirection *= speed;
    138.  
    139.  
    140.             //Applying gravity to the controller
    141.             verticalVelocity -= gravity * Time.deltaTime;
    142.             moveDirection.y = verticalVelocity;
    143.  
    144.             //Making the character move
    145.             controller.Move (moveDirection * (Time.deltaTime));
    146.         }
    147.         else //NOW WHAT TO DO IF YOU ARE CLIMBING
    148.         {
    149.             //Feed moveDirection with input.
    150.             moveDirection = new Vector3 (Input.GetAxisRaw ("Horizontal"), Input.GetAxisRaw ("Vertical"), 0);
    151.             moveDirection = new Vector3 (Camera.main.transform.TransformDirection (moveDirection).x, Camera.main.transform.TransformDirection (moveDirection).y, 0);
    152.  
    153.  
    154.  
    155.             verticalVelocity = 0;
    156.  
    157.             //Multiply it by speed.
    158.             moveDirection *= speed;
    159.  
    160.             //Making the character move
    161.             controller.Move (moveDirection * (Time.deltaTime));
    162.  
    163.         }
    164.     }
    165. }
     
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    So when you're climbing, I guess holding the stick forward should always move the character up? And right and left is relative to the camera?

    Right now you're making the vertical movement be relative to the camera, and that's all wrong. The y-component of your movement vector should just be the vertical stick's input.

    The horizontal part is worse. You don't want it to be relative to the camera. You probably want it to be perpendicular to the wall.

    So the rule there is to multiply the horizontal input with the character's right vector when the camera's behind the player, and to multiply it with the character's left vector when the camera's in front of the player.
     
  5. Syviery

    Syviery

    Joined:
    Aug 2, 2014
    Posts:
    4
    Thank you Baste, I fixed the horizontal movement at this point as I realized what I would need to do with that. I also took your suggestion and removed any reference to the camera when moving upwards on the wall. I have since moved on to the next issue I am facing. I have successfully made my player rotate to face the wall when climbing using:
    Code (CSharp):
    1. transform.rotation = Quaternion.LookRotation(-hit.normal);
    Unfortunately, as I curve around the cylinder I am testing on, it gets very shakey as you move farther away from the original face you grabbed on to and eventually the player gets "pushed" off of the wall after shaking and falls down. I would appreciate any help and just in-case it could give you more insight into what I am doing here is my updated full player code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class player : MonoBehaviour {
    5.     //Variables for changing movement
    6.     public float speed = 2.5F;
    7.     public float jumpSpeed = 8.0F;
    8.     public float gravity = 20.0F;
    9.     private float verticalVelocity = 0;
    10.     public float rotationSpeed = 6F;
    11.  
    12.     //climbing variables
    13.     private bool climbToggle = false;
    14.     private float climbHoldTimer = 0f;
    15.  
    16.     //Movement and rotation variables
    17.     private Vector3 moveDirection = Vector3.zero;
    18.     private Vector3 faceDirection = Vector3.zero;
    19.  
    20.     //Sounds
    21.     public AudioClip jumpSound;
    22.  
    23.     void Update() {
    24.         CharacterController controller = GetComponent<CharacterController>();
    25.         Debug.DrawRay(transform.position, transform.forward * 50f, Color.green);
    26.  
    27.         //raycast forward distance
    28.         RaycastHit hit;
    29.         Ray climbRay = new Ray (new Vector3 (transform.position.x, transform.position.y - 0.25f, transform.position.z), faceDirection);
    30.  
    31.         if (Physics.Raycast (climbRay, out hit, 0.5F))
    32.         {
    33.             if (climbToggle == true) {transform.Translate(-transform.forward * Time.deltaTime, transform);}
    34.         }
    35.  
    36.         if (Physics.Raycast (climbRay, out hit, 0.6F)) // check if you are close enough to hold on
    37.         {
    38.             if (controller.isGrounded)
    39.             {
    40.  
    41.                 //GRAB WALL FROM GROUND
    42.                 if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer < 0.4f) //if the player is pushing up but the timer hasn't reached the limit yet
    43.                 {
    44.                     climbHoldTimer += (1 * Time.deltaTime);
    45.                 }
    46.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") < 0.1 && climbHoldTimer < 0.4f) //if the player releases the key before the timer has reached the limit
    47.                 {
    48.                     climbHoldTimer = 0;
    49.                 }
    50.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer >= 0.4f) //if the player succesfully holds the key
    51.                 {
    52.                     climbHoldTimer = 0;
    53.                     climbToggle = !climbToggle;
    54.                 }
    55.  
    56.                 //RELEASE WALL ON GROUND
    57.                 if (climbToggle == true && Input.GetAxisRaw ("Vertical") < -0.1 && climbHoldTimer < 0.2f) //if the player is pushing down but the timer hasn't reached the limit yet
    58.                 {
    59.                     climbHoldTimer += (1 * Time.deltaTime);
    60.                 }
    61.                 else if (climbToggle == true && Input.GetAxisRaw ("Vertical") > -0.1 && climbHoldTimer < 0.2f) //if the player releases the key before the timer has reached the limit
    62.                 {
    63.                     climbHoldTimer = 0;
    64.                 }
    65.                 else if (climbToggle == true && Input.GetAxisRaw ("Vertical") < -0.1 && climbHoldTimer >= 0.2f) //if the player succesfully holds the key
    66.                 {
    67.                     climbHoldTimer = 0;
    68.                     climbToggle = !climbToggle;
    69.                 }
    70.             }
    71.             else
    72.             {
    73.                 //GRAB WALL IN-AIR
    74.                 if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer < 0.2f) //if the player is pushing up but the timer hasn't reached the limit yet
    75.                 {
    76.                     climbHoldTimer += (1 * Time.deltaTime);
    77.                 }
    78.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") < 0.1 && climbHoldTimer < 0.2f) //if the player releases the key before the timer has reached the limit
    79.                 {
    80.                     climbHoldTimer = 0;
    81.                 }
    82.                 else if (climbToggle == false && Input.GetAxisRaw ("Vertical") > 0.1 && climbHoldTimer >= 0.2f) //if the player succesfully holds the key
    83.                 {
    84.                     climbHoldTimer = 0;
    85.                     climbToggle = !climbToggle;
    86.                 }
    87.             }
    88.         }
    89.         else if (!Physics.Raycast (climbRay, out hit, 0.6f) && Physics.Raycast (climbRay, out hit, 1f)) // check if you are close enough to hold on
    90.         {
    91.             if (climbToggle == true) {transform.Translate(transform.forward * Time.deltaTime, transform);}
    92.         }
    93.         else
    94.         {
    95.             //RELEASE WALL AT EDGES
    96.             if (climbToggle == true && !Physics.Raycast (climbRay, out hit, 1f))
    97.             {
    98.                 climbToggle = false;
    99.             }
    100.         }
    101.  
    102.         //QUICK CHECK FOR IF CLIMBING
    103.         if (climbToggle == false)
    104.         {
    105.             if (controller.isGrounded)
    106.             {
    107.                 verticalVelocity = 0;
    108.                 //transform.localeulerAngles = new Vector3(transform.localeulerAngles.x, Camera.main.transform.localeulerAngles.y, transform.localeulerAngles.z);
    109.  
    110.                 //Jumping
    111.                 if (Input.GetButtonDown ("Jump"))
    112.                 {
    113.                     verticalVelocity = jumpSpeed;
    114.                     if (jumpSound != null)
    115.                     {
    116.                         GetComponent<AudioSource> ().PlayOneShot (jumpSound, 1.0F);
    117.                     }
    118.                 }
    119.  
    120.             }
    121.  
    122.             //Feed moveDirection with input.
    123.             moveDirection = new Vector3 (Input.GetAxisRaw ("Horizontal"), 0, Input.GetAxisRaw ("Vertical"));
    124.             moveDirection = new Vector3 (Camera.main.transform.TransformDirection (moveDirection).x, 0, Camera.main.transform.TransformDirection (moveDirection).z);
    125.  
    126.             //Rotate towards movement direction
    127.             faceDirection = moveDirection;
    128.  
    129.             if (Input.GetAxisRaw ("Horizontal") > 0.1 || Input.GetAxisRaw ("Vertical") > 0.1 || Input.GetAxisRaw ("Horizontal") < -0.1 || Input.GetAxisRaw ("Vertical") < -0.1)
    130.             {
    131.                 transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation (new Vector3 (faceDirection.x, 0, faceDirection.z)), Time.deltaTime * rotationSpeed);
    132.                 moveDirection.y = 2F;
    133.             }
    134.  
    135.             //Multiply it by speed.
    136.             moveDirection *= speed;
    137.  
    138.  
    139.             //Applying gravity to the controller
    140.             verticalVelocity -= gravity * Time.deltaTime;
    141.             moveDirection.y = verticalVelocity;
    142.  
    143.             //Making the character move
    144.             controller.Move (moveDirection * (Time.deltaTime));
    145.         }
    146.         else //NOW WHAT TO DO IF YOU ARE CLIMBING
    147.         {
    148.             //Feed moveDirection with input.
    149.             moveDirection = new Vector3 (0, Input.GetAxisRaw ("Vertical"), 0);
    150.  
    151.             if (Input.GetAxisRaw ("Horizontal") > 0.1) {transform.Translate(Vector3.right * Time.deltaTime * 2, transform);}
    152.             if (Input.GetAxisRaw ("Horizontal") < -0.1) {transform.Translate(-Vector3.right * Time.deltaTime * 2, transform);}
    153.  
    154.             verticalVelocity = 0;
    155.  
    156.             //RELEASE WALL AT BUTTON PUSH
    157.             if (Input.GetButtonDown ("Action") && climbToggle == true)
    158.             {
    159.                 climbToggle = false;
    160.             }
    161.  
    162.             transform.rotation = Quaternion.LookRotation(-hit.normal);
    163.  
    164.             //Multiply it by speed.
    165.             moveDirection *= speed;
    166.  
    167.             //Making the character move
    168.             controller.Move (moveDirection * (Time.deltaTime));
    169.  
    170.         }
    171.     }
    172. }
    173.  
    174.  
     
  6. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    That is... really hard to read.

    The thing is, the Raycast is so far away and you reuse the same hit variable so many times that it's hard to tell which raycast the hit is from. But, essentially, you're doing this:

    Code (csharp):
    1. if(Physics.Raycast(... out hit ...) {
    2.    ...
    3. } else if (!Physics.Raycast(... out hit ...) && Physics.Raycast(... out hit ...) {
    4.    ...
    5. }
    6.  
    7. ...
    8.  
    9. transform.rotation = Quaternion.LookRotation(-hit.normal);
    So there are three potential sources for what .hit references. If the first raycast hits something, none of the two next are evaluated, and it's from that hit.

    Otherwise, it's from either the second raycast (if it misses, in which case the data is empty), or the third (which may or may not hit something, but it's the last possible check).

    You're also not checking if the hit that's used actually hit something, which is a problem in itself.


    At the bare minimum use a unique RaycastHit variable for each Raycast. You should also consider splitting your movement in (at least) two methods - one ClimbingMovement and one WalkingMovement method. Then it'll be a lot easier to follow what's going on.


    Final comment: raycasting forward and setting your rotation to the inverse of the normal on every frame is probably not a good idea. Say your character's rotated so it's facing the part of the capsule slightly to the left of it. The normal of the capsule there will point behind and slightly to the left of the character. Setting the character's rotation to that will cause the character to face slightly to the right. This means that the character will vibrate, which is what you're seeing.
    You probably only want to change the rotation of the character if it's moved this frame. A more solid solution would be to use two raycasts, one from each side of the character, and rotate it to make the rays be at the same distance. Something like that.
     
  7. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    1,065
    This problem seems out of my hands at this point, but just want to say I would use raycast as a last resort. It's not that raycasts are inherently bad, just that avoiding its use prevents its overuse. It's the overuse which leads to problems. If you need the information of collision, you should be able to get it from the Collision in the collision functions.
    Code (CSharp):
    1. void OnCollisionEnter(Collision col){
    2.     print(col.contacts[0].normal)
    3. }
    This method of determining the surface normal however does not work well when climbing a convex surface like a sphere, capsule, or cylinder(which is a capsule collider when generated anyway), since the surfaces curves away from the character, so it still depends on what your game needs.