Raycast to align character on MeshCollider normals

Hello, I’m trying to move an object (let’s say a character) on the surface of another object. I know that this subject has been dealt with in a lot of other posts, but I still couldn’t find a fully working solution.

I’ve seen different approaches and tried them all with some good results, but I can’t really be satisfied because there are still some problems and glitches.

So I’ve decided to break the whole thing into smaller tasks, hoping to find the final solution to this.

Here’s what I have so far:

void Update () 
{
    // Input translation
    float inputTranslation = Input.GetAxis("Vertical") * moveSpeed * Time.deltaTime;
    transform.position += transform.forward * inputTranslation;

    RaycastHit hit;

    if (Physics.Raycast (transform.position, -transform.up, out hit, Mathf.Infinity)) 
    {
        // Stick on surface
        transform.position = hit.point;

        // Align to surface normal
        Quaternion fro = Quaternion.FromToRotation(transform.up, hit.normal) * transform.rotation;
        transform.rotation = Quaternion.Lerp(transform.rotation, fro, Time.deltaTime * 5.0f);
    }

    // Inpput heading rotation
    float headingDeltaAngle = Input.GetAxis("Horizontal") * Time.deltaTime * headingRotSpeed;
    Quaternion headingDelta = Quaternion.AngleAxis(headingDeltaAngle, transform.up);

    transform.rotation = headingDelta * transform.rotation;
}

First task: placing the object (character) onto another object’s surface.

Here it looks like pretty much easy stuff: cast a ray downwards from the character, get the hit point and update the character’s position.

Also, I need the caracter to move through user input.

This kinda works, but it depends on the surface of the object: sometimes the characters get stuck and won’t move anymore; other times it will just go through the object’s mesh and pass on the other side (because the raycast gets another hit.point) or even better, it will keep travelling in the outer space…Off course this is not good: I need to find a more generic solution that will work (hopefully) with any arbitrary mesh.

So the first question is: is this a right approach to make the character stick on the object’s surface, while also allowing user input to move it? Is it right to set the position of the character to hit.point or does it conflict with the input translation?

Also, how can I add a little offset between the character’s transform and the object’s surface? I already put the pivot below the character, but I guess I also need to offset the Y position a bit.

Second task: orient the character to the surface normal

This is kinda tricky for me, my vector and quaternion math is lame (to say the least). Anyway, from what I can see and read on Unity docs, Quaternion.FromToRotation rotates the transfom from “fromDirection” to “toDirection”. In this case, it rotates the transform from it’s up direction to the direction of the hit.normal. Seems legit to me.

Then all this stuff is being interpolated with Quaternion.Lerp, going from the current object’s rotation to the desired one (according to the surface normal) in a given amount of time.

I’ve seen different examples to do this (at least 4 different ways), but this one seems to me the one which is working better. Again, it’s not perfect. Is there a way to improve it, or maybe is better to accomplish this task in a different way?

Third task: changing character’s direction with right/left arrow keys

I don’t know if this is the right approach; it seems to work fine but I’m not sure if it is causing any of the problems stated above.

I don’t have a lot of experience moving characters over terrain, but in visualizing what is going on here, I have a few thoughts.

You are moving the character along the forward vector of the character (transform.forward), and you are Raycasting the character’s down (-transform.up). I can visualize a number of situations where this combo will cause you trouble. For example at a sharp transition, the raycast will not hit the ground and therefore walk through the air, or it will drive the character into the ground and the Raycast() will fail because it starts below the surface of the terrain. I think you want to project the forward onto the XZ plane and use that for forward movement, and for Raycasting() you want to use Vector3.down from somewhere above the character.

   float inputTranslation = Input.GetAxis("Vertical") * moveSpeed * Time.deltaTime;
   Vector3 v3 = transform.forward;
   v3.y = 0.0f;
   transform.position += v3.normalized * inputTranslation;

And for the Raycast():

if (Physics.Raycast (transform.position + Vector3.up * some_dist, Vector3.down, out hit, Mathf.Infinity)) 

Note that unless you need to raycast against all the objects in the scene, you can use Collider.Raycast() instead of Physics.Raycast().

If you want to position the character above the terrain a bit more, then use the normal:

transform.position = hit.point + hit.normal * some_distance;

As for aligning with the normal, you might want to try Quaternion.RotateTowards() in place of the Lerp() for a different kind of movement.