0
\$\begingroup\$

There's a player and a slope in my scene. I want the player to automatically switch to 'climbing down' animation when he's at the top of the slope and moving down and similarly switch to 'climbing up' when he's at the bottom and moving up. I'm using the default 'character controller'. I've managed to get the slope angle with 'Vector3.Angle(transform.forward, hit.normal)' and the 'climbing up' is working fine since the angle is > 95 and that's the condition I stated. But when 'climbing down' the angle is not uniform and ranges from 30 - 140 and the condition I've stated is < 70 which is not satisfied all the time and hence the animation switches from 'climbing down' to 'climbing up' and vice versa. How do I get the right slope angle to differentiate between up slope and down slope?

\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

You can do it easily using the Dot Product between the slope normal and the transform forward vectors:

using UnityEngine;

public sealed class CharacterMotor : MonoBehaviour
{
    public CharacterController controller;
    public LayerMask collision;

    private void Update()
    {
        // This distance value works well for CharacterController.SlopeLimit = 45F
        const float distance = 0.2F;
        const float maxDistance = distance * 2F;

        var origin = transform.position + Vector3.up * distance;
        var isGroundCollision = Physics.Raycast(
            origin,
            direction: Vector3.down,
            out RaycastHit groundHit,
            maxDistance,
            collision,
            queryTriggerInteraction: QueryTriggerInteraction.Ignore
        );

        if (!isGroundCollision) return;

        var floorAngle = Vector3.Angle(groundHit.normal, Vector3.up);
        var isOverSlope = floorAngle > 0F;

        if (!isOverSlope ) return;

        var dot = Vector3.Dot(groundHit.normal, transform.forward);
        var isRunningUpwardSlope = dot < 0F;
        var isRunningDownwardSlope = dot > 0F;
    }
}
\$\endgroup\$
0
\$\begingroup\$

Because the system you are using is at least partially successful, I'd suggest there are two options here:

The intermittent problem is due to the capsule collider on the Player Controller and (I assume) picking the first collision hit in the list. You could try grabbing all the hits into an array and loop over them to find the one with the lowest, or highest, Y value - then use this with your existing normal check. The nature of the collider's shape can throw up a number of hits, and their order is not always guaranteed.

The second option, and the one I used when I wanted to alter walk speed based on slope, is to fire a ray down from a fixed point in front of the character, and use the difference in height between the player transform's Y and the ray hit's Y to determine the slope. This is more accurate as you are sampling a single point.

\$\endgroup\$
0
\$\begingroup\$

The problem seems to be the inaccuracy observed on 'climbing down'. This can be fixed using a better sampling of raycast result while calculating the slope. One such method is to try a sphere cast with some extra raycast and using statistics (mean, median, mode). This method is very well illustrated in the following posts-


1.http://thehiddensignal.com/unity-angle-of-sloped-ground-under-player/
2. https://roystanross.wordpress.com/2015/05/10/custom-character-controller-in-unity-part-6-ground-detection/

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .