# Question How to make my character not walk up steep slopes. and not pass through walls.

Discussion in 'Physics' started by DarkenSoda, Apr 2, 2023.

1. ### DarkenSoda

Joined:
Apr 3, 2020
Posts:
12
Hello everyone.
I've been trying to make 3D movement using RigidBody but I'm facing some issues implementing some checks.
I was trying to implement slope movement to prevent the character from bouncing down slopes and that worked fine.
Then I tried to implement a max slope angle, where the character can't climb steep slopes, while doing so I noticed that Rigidbody.Move() makes my character sometimes pass through walls, and worse pass through the ground.

Tried to do some checks to fix both issues but I can't adjust the movement Vector properly.

I need the movement Vector to be adjusted as to prevent movement in the direction of the slope/wall but can move along side it.
Here's the movement script that works in FixedUpdate()

Code (CSharp):
1. private void FixedUpdate() {
2.         HandleMovement();
3.     }
4.
5.     void HandleMovement() {
6.         Vector2 inputVector = gameInput.GetMovementVectorNormalized();
7.
8.         float moveDistance = moveSpeed * Time.deltaTime;
9.         if (inputVector != Vector2.zero) {
10.             // calculate rotation angle from input vector
11.             float targetRotation = Mathf.Atan2(inputVector.x, inputVector.y) * Mathf.Rad2Deg + Camera.main.transform.eulerAngles.y;
12.             // Rotate character
13.             transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation, ref rotationSpeed, 0.1f);
14.             Vector3 moveDir = Quaternion.Euler(0f, targetRotation, 0f) * Vector3.forward;
15.
17.             moveDir = CheckFrontCollision(moveDir, transform);
18.
19.             // move character in direction
20.             rbody.MovePosition(transform.position + moveDir * moveDistance);
21.         }
22.
23.         isWalking = inputVector != Vector2.zero;
24.     }
and here's both the function that checks wall collision and slope angle

Code (CSharp):
1. public Vector3 CheckFrontCollision(Vector3 velocity, Transform transform) {
2.         float stepHeight = .3f;
3.         float playerHeight = 1f;
5.         float checkDistance = .2f;
6.         // Capsule Cast to check wall collision
7.         bool canMove = !Physics.CapsuleCast(
8.             transform.position + Vector3.up * stepHeight,
9.             transform.position + Vector3.up * playerHeight,
11.             velocity,
12.             checkDistance
13.         );
14.         print(canMove);
15.         // if hitting an object stop forward direction
16.         if(!canMove) velocity.z = 0;    // I believe this is the issue
17.         return velocity;
18.     }
19.
20.     public Vector3 AdjustVelocityToSlope(Vector3 velocity, Transform transform) {
21.         Ray ray = new Ray(transform.position, Vector3.down);
22.         if (Physics.Raycast(ray, out RaycastHit hit, 0.2f)) {
23.             // calculate slope angle
24.             float slopeAngle = Vector3.Angle(hit.normal, transform.up);
25.             // calculate whether we are going up the ramp or down the ramp
26.             float slopeDirection = Vector3.Angle(hit.normal, transform.forward) - 90;
27.             print(slopeAngle);
28.
29.             // if higher than certain angle block movement forward.
30.             if (slopeAngle > 35 && slopeDirection > 0) {
31.                 velocity.z = 0;     // I believe this is the issue
32.                 return velocity;
33.             }
34.
35.             // align move vector with the ramp
36.             Quaternion slopeRotation = Quaternion.FromToRotation(Vector3.up, hit.normal);
37.             Vector3 adjustedVeclocity = slopeRotation * velocity;
39.         }
40.         return velocity;
41.     }
basically it works fine as long as my wall and slop isn't rotated around Y axis.
I guess the issue is that I set the velocity.z to 0 and this makes the character not walk in the global forward direction.

Here's some images to clarify.

Needless to say if I am walking towards the wall head on then the character should not walk in any direction

2. ### SOICGeek

Joined:
Jun 25, 2019
Posts:
77
You may want to give some thought to using the CharacterController component instead of doing everything by hand. It has all of the features you're describing. Here's how to use that instead:
1. First off, remove your Rigidbody component from your player. Also the collider - CharacterController comes with a collider.
2. With your character selected, click Component->Physics->Character Controller. This will add a CharacterController component to your player.
3. When it comes time to move your player, use something like the following:
Code (CSharp):
1. [SerializeField]
2. private CharacterController controller;
3.
4. // alternatively, use GetComponent<CharacterController>() if you know
5. // the CharacterController will always be on the player character
6.
7. private void Update() {
8.     Vector3 movement = whateverMethodYouUseForThat();
9.     controller.Move(movement);
10.     // insert whatever code you want for rotating - that part doesn't
11.     // work any differently for a CharacterController.
12. }
Using CharacterController.Move() respects colliders and slope limits. You will also want to apply gravity to the character. CharacterController doesn't do that for you. I do it by tracking an internal "vertical momentum" variable, which I set to zero when in contact with the ground or adding gravity to it otherwise, and apply it to the Y-axis when calling Move(). That's also useful when you implement jumping and fall damage.

DarkenSoda likes this.
3. ### DarkenSoda

Joined:
Apr 3, 2020
Posts:
12
I did actually change to a characterController yesterday and everything works perfectly now.
I even made it so he can't jump when on high slopes.
Now I have 2 small issues that is kinda just a quality of life:
1) When I hit an inclined wall with an angle it stops movement completely instead of walking along side the wall

2) when I'm on a high slope instead of sliding down the slope, I just remain on the same height.
I want the character to slide when it's standing on inclined surface that is > slopeLimit

Aside from these 2 issues, everything is perfect now

4. ### xeosD

Joined:
Apr 6, 2023
Posts:
8
How did you resolve this in the end? I've just spent two days of coding trying to find a robust way to fix this very problem and no luck so far.

5. ### zulo3d

Joined:
Feb 18, 2023
Posts:
757
Code (CSharp):
1. using UnityEngine;
2.
3. public class SlippyController : MonoBehaviour
4. {
5.     CharacterController cc;
6.     Vector3 playerVelocity;
7.     RaycastHit hit;
8.
9.     float gravity=20f;
10.     float slippy=10f; // How slippery are the slopes?
11.
12.     void Start()
13.     {
14.         cc=GetComponent<CharacterController>();
15.     }
16.
17.     void Update()
18.     {
19.         Vector3 moveDirection=new Vector3(Input.GetAxis("Horizontal"),0,Input.GetAxis("Vertical"));
20.         if (cc.isGrounded)
21.         {
22.             // slide down slopes:
23.             if (Physics.SphereCast(transform.position,0.5f,Vector3.down,out hit,5f)) // raycast to get the hit normal
24.             {
25.                 Vector3 dir=Vector3.ProjectOnPlane(Vector3.down,hit.normal); // slope direction
26.                 playerVelocity+=dir*Vector3.Dot(Vector3.down,dir)*gravity*slippy*Time.deltaTime;
27.             }
28.             playerVelocity+=moveDirection;
29.             playerVelocity*=0.95f;   // basic friction
30.         }
31.         else
32.             playerVelocity.y-=gravity*Time.deltaTime;
33.
34.         cc.Move(playerVelocity*Time.deltaTime);
35.     }
36. }

6. ### DarkenSoda

Joined:
Apr 3, 2020
Posts:
12
I had to implement my own logic for sliding down slopes that's bigger than the limit using Vector3.ProjectOnPlane.
It's a way similar to what zulo3d did above.

Code (CSharp):
1.
2. private void Update() {
3.         isSliding = OnSlope();
4.         isGrounded = Physics.CheckSphere(feet.position, checkSphereRadius, groundLayer);
5.
6.         HandleMovement();
7.         HandleGravity();
8.         HandleSlopeSliding();
9. }
10.
11. private void HandleSlopeSliding() {
12.         if (!isSliding) return;
13.
14.         slidingDirection = Vector3.ProjectOnPlane(Vector3.down, hitInfo.normal);
15.         controller.Move(slidingDirection * slidingSpeed * Time.deltaTime);
16.     }
17.
18. private bool OnSlope() {
19.         if (Physics.SphereCast(body.position, checkSphereRadius, Vector3.down, out hitInfo, 1f, groundLayer)) {
20.             // Slope Angle
21.             float angle = Vector3.Angle(hitInfo.normal, Vector3.up);
22.             if (angle > controller.slopeLimit) {
23.                 return true;
24.             }
25.         }
26.
27.         return false;
28. }
29.