8
\$\begingroup\$

I've noticed that a lot of people seem to have this issue but I've yet to find an actual working solution - when a rigidbody-based character controller (I'm not using Unity's character controller) moves down a sloped surface, they will bounce/bunny hop on the way down instead of staying on the surface. I'm building a 3d platformer and ran into this issue - I've tried a fair amount of things but nothing seems to work cleanly. I managed to find a 2D solution here but I can't seem to get it to work cleanly in 3D. Here is my code:

    void NormalizeSlope()
{
    // Attempt vertical normalization
    if (isGrounded)
    {
        RaycastHit hit;
        if (Physics.Raycast(floorCheck.position, Vector3.down, out hit, groundCheckDistance))
        {
            if(hit.collider != null && Mathf.Abs(hit.normal.x) > 0.1f)
            {
                // Apply the opposite force against the slope force
                // You will need to provide your own slopeFriction to stabalize movement
                rigidbody.velocity = new Vector3(rigidbody.velocity.x - (hit.normal.x * 0.4f), rigidbody.velocity.y, rigidbody.velocity.z); //change 0.6 to y velocity

                //Move Player up or down to compensate for the slope below them
                Vector3 pos = transform.position;
                pos.y += -hit.normal.x * Mathf.Abs(rigidbody.velocity.x) * Time.deltaTime * (rigidbody.velocity.x - hit.normal.x > 0 ? 1 : -1);
                transform.position = pos;
            }
        }
    }
}

With that, I get varying results on surfaces with different slopes. Also, my character jitters. On some slopes, my character even slowly inches their way up the surface. Has anyone else run into this issue? Does anyone have a working solution for this problem?

\$\endgroup\$

4 Answers 4

0
\$\begingroup\$

In general rigidbody physics is appropriate to model non-living things, while a completely different system of bone and animation based physics is usually used on living things and things which behave like living things (like robots or zombies).

For example, if you drop a stone on the side of a hill you can model what will happen using a very simple system, but if you drop a hamster on the side of a hill you would need to model that using a completely different system.

What I'm trying to say is that if you want to model something like walking using rigidbody physics in a way that looks at all realistic then you're going to have to resign yourself to far more than 4 lines of code.

The best algorithm I've seen for modeling walking using rigidbodies involves putting a sphere collider on the front of the foot and another one on the heel. For each step you then keep that part of the foot descending until one collider collides. The knees them move in a sinusoidal pattern driving the feet up and down, and the hips use their own sine wave to drive the knees back and forth.

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

If you don't want your character to jump on the slope, like it is done by physics engine and make him stick to the ground (If I understood your question correctly). I would not use rigidbody gravity in that case and would not move character by adding force or velocity. I would have created own gravity system and just turn it on whenever player decides to jump, but turn it off if the player hits the ground. Then I would raycast the ground in area that character covers and take normals to rotate player to it and move him towards his transform.forward in world space. Also remember to stick your player to the position of the ground he is standing on when he rotates.

If you want your player to slide down, than it would be better to forget about rotation, if it's a simulation of real life sliding and actually use rigidbody and gravity, but then just right after the slope, when the normal changes or what make your player to stick to hit.position where hit is a variable of RaycastHit. It will make your character to stick to any surface when slope changes. But remember that maybe you want it to save it's speed when using velocity or AddForce, then you want to change only y position and also change the velocity on y to 0.

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

Perhaps using Vector3's ProjectOnPlane method could help you.
From the docs: "Projects a vector onto a plane defined by a normal orthogonal to the plane."

By providing your rigidbody's velocity and the normal direction of the hit flooring, you can project your velocity to be parallel to the floor. Take for example:

Vector3 forceDirection = new Vector3(0, 0, 1);

void FixedUpdate()
{
    Vector3 force = forceDirection;

    // if there's a floor beneath us
    if (Physics.Raycast(transform.position, Vector3.down, 2f, out RaycastHit hit))
    {
        // Project our force direction to be parallel to the floor
        force = Vector3.ProjectOnPlane(force, hit.normal);

        // This means the force we're adding is now following the slopes angle
    }
    
    rigidBody.AddForce(force);
}

You can visualize this like drawing an arrow representing your direction vector above your sloped plane. If a light source was placed directly above the arrow, its shadow cast on the plane would be our projected vector

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

This is a very easy solve. All you have to do is apply a downward force if you are in the air. First, you have to detect if you are actually on a slope, assuming you have a "jump" in your game.

bool OnSlope()
{
    if (Physics.Raycast(transform.position, Vector3.down, out 
    hit, /*half of your player's height and add about a sixth of your player height*/))
    {
        float angle = Vector3.Angle(Vector3.up, hit.normal);
        return angle != 0;
    }

    return false;
}

You then want to make a system so that it doesn't put downward force when you jump, only when you are bouncing. I don't know what your jumping mechanics look like, create a boolean that turns true when you jump, and turns false when you touch the floor, but DO NOT make it so that when it is off the ground it turns false, or it ruins the whole point.

void Jump()
{
    bool exitingSlope = true;
}
void Update()
{
    if (!isGrounded)
        exitingSlope = false;
}

(might look something like this depending on the rest of your code) It would definitely be helpful if you put the rest of your code. Now then you want to actually add force:

if (OnSlope() && !exitingSlope)
    { 
        GetComponent<RigidBody>().AddForce(Vector.down * /*I'd recommend 80f to 400f depending on your player speed*/, ForceMode.Force);
    }
\$\endgroup\$

You must log in to answer this question.

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