Character Controller and Terrain collisions are expensive?

I have many characters that are controlled with a basic follow script. Let’s assume they are the zombie horde after my primary character. The zombie characters and the main character have character controllers. The problem is the controller.Move call on my zombies is killing my frame rate.

I have tracked it down to be related to the collisions detections between the terrain and the zombie character controller. For example the Follow script on the zombies has a basic gravity implementation to it. i.e.

forward.y -= gravity * Time.deltaTime;

This keeps the zombie on to the ground when running. If I have a rough terrain and 20 or so zombies the framerate is bellow 10fps. The profiler shows 90% of that time in the Move call.

If I use a flat terrain and remove the gravity line in the follow script then I get a super smooth 100 to 200 fps.

(FYI likewise if I make the radius of the Character Controller super small, then the framerate is great too). So I assume the problem is the move call is responding to the collision with the terrain on every update, and this is very expensive when you have many character controllers doing it every update.

Anyone have an idea how to move many characters over a rough terrain and keep them all on the ground without killing the frame rate.

Thanks,

I do this by doing a raycast downwards from each character every 2-4 frames using a coroutine (hence splitting the effort out over many frames). You can also vary this based on how frequently the height is changing (slowing it down when height doesn’t change often for a character).

Example

     float castDelay = 0.05f;

     IEnumerator Start()
     {
           //Random delay
           yield return new WaitForSeconds(Random.value * 0.3f);
           RaycastHit hit;
           Transform cachedTransform = transform;
           while(true)
           { 
                 var position = cachedTransform.position;
                 if(Physics.Raycast(position + Vector3.up, Vector3.down, out hit, 100 /* , mask  */))
                 {
                     position.y = hit.point.y /* - offset */;
                     cachedTransform.position = position;
                 }
                 yield return new WaitForSeconds(castDelay);

           }
     }

Can anyone explain why the Mecanim example seems to work much better. I grabbed the MecAnim Example Scenes tutorial from the asset store which is doing similar things with crowds that follow the main character. I modified their Follow example to use a lot more teddy bears and removed the arena and placed a large rough terrain. This seemed to work just fine at a great frame rate.

I have not used MecAnim before but it looks like the Animator has gravity built in. Even if my Controller.move call in the OnAnimatorMove function has no downward component, the character still sticks to the ground. Meaning if right before the move call I set the y to zero. like this;

forward.y = 0.0f;

controller.Move(forward);

Disable the Animator and the Teddy floats at a single height as expected.

It will take time to convert all of my characters in my project to MecAnim, (right now I am using a script to control my animations), so wanted some confirmation before going that route. Also my project will be delivered on IOS, so want to keep that in mind.