0
\$\begingroup\$

I'm trying to make an algorithm that will automatically generate a CharacterController component (on a parent-GameObject) which should automatically resize to whatever the current mesh size is.

To explain: A capsule object acts as the "parent", this object has a CharacterController on it, and all together, the CharacterController radius / height should act like a bounding box around the current mesh / FBX I'm loading, no matter what shape or size.

And then after the size is correct, I simply move the mesh inside the middle of the parent gameobject, and parent it to it. This all works, in fact, but when the mesh is of a larger size, things start to get weird.

Also, I have various hats that are included in the FBX file that are to be shown / hidden, so I don't necessarily want to include that in the full bounding box / radius (since the hats a=have a wide brim, if the radius fits them, then the collision will be dependent on the hat).

Another factor involved is that I need to have a decent skin width for the character controller for it to work right, but I don't want that skin with the push the mesh off the ground, or otherwise be noticeable at all (like when running into walls, I don't want the skin width the make it look like there's an invisible boundary around him).

But also, not all meshes imported will have clothes attached, so in my 3D software I named the body mesh "solid" and in the algorithm I check if the solid mesh exists, and if so, use it for the radius, and if not, use the entire mesh.

I'm currently able to get a bounding box encapsulating all of the child meshes, and here is that function:

public Bounds LocalBounds(GameObject gb)
    {
        Quaternion currentRotation = gb.transform.rotation;
        gb.transform.rotation = Quaternion.Euler(0f, 0f, 0f);
        Bounds bounds = new Bounds(gb.transform.position, Vector3.zero);
        foreach (Renderer renderer in gb.GetComponentsInChildren<Renderer>())
        {
            bounds.Encapsulate(renderer.bounds);
        }
        Vector3 localCenter = bounds.center - gb.transform.position;
        bounds.center = localCenter;
        //  Debug.Log("The local bounds of this model is " + bounds);
        gb.transform.rotation = currentRotation;
        return bounds;
    }

And here is the rest of my algorithm for attempting to calculate the radius / height of the character controller:

void MakeParentCapsule()
    {
        parentGameObject = GameObject.CreatePrimitive(PrimitiveType.Capsule);



        var controller = parentGameObject.AddComponent<CharacterController>();

        var capsuleCollider = parentGameObject.GetComponent<CapsuleCollider>();

        controller.slopeLimit = 85;
        float skinWidth = 0.1f;
        float radius = 0.3f;

        controller.skinWidth = skinWidth;

        var center = controller.center;

        controller.center = center;
        capsuleCollider.radius = controller.radius;
        controller.radius -= skinWidth * 2;
        if (controller.radius <= 0)
        {
            controller.radius += radius;
        }


        Vector3 capsuleSize = controller.bounds.size;
        GameObject radiusRef = null;
        var solidAttempt = gameObject.transform.FindDeepChild("solid");
        if (solidAttempt != null)
        {
            Debug.Log("found a solid!");
            radiusRef = solidAttempt.gameObject;
        } else
        {
            Debug.Log("NO solid found");
        }

        var body = LocalBounds(gameObject);
        var scaleFactor = body.size.y / capsuleSize.y;
        controller.skinWidth *= scaleFactor;

        var boundsToUse = radiusRef != null ? LocalBounds(radiusRef): body;
        controller.radius = Mathf.Max(boundsToUse.extents.x, boundsToUse.extents.z);
    //    controller.radius *= 0.5f;
        controller.radius -= controller.skinWidth * 2;

        parentGameObject.transform.localScale = parentGameObject.transform.localScale * scaleFactor;

        controller.height -= skinWidth * 4 * scaleFactor;
        capsuleCollider.height -= skinWidth;

        parentGameObject.transform.position = gameObject.transform.position;

        Vector3 tempPos = parentGameObject.transform.position;
        tempPos.y += capsuleSize.y / 2 * scaleFactor - skinWidth * 1.5f;
        parentGameObject.transform.position = tempPos;

        gameObject.transform.SetParent(parentGameObject.transform);
        parentGameObject.GetComponent<Renderer>().enabled = false;
    }

I was experimenting a lot with modifying the radius to detract from the skin width (and so too the height), and it works with a basic small model, but when I simply increase the size (in the 3D editor) things start to not line up anymore.

SO: does anyone know of a simpler / working algorithm that can accurately create a CharacterController whose radius and height will match any mesh exactly?

\$\endgroup\$
  • \$\begingroup\$ Can you show us the specific symptoms you observe with larger meshes, and detail what the "largeness" threshold is that makes these errors apparent? The more precisely we can define the problem, the better and faster we can find solutions. \$\endgroup\$ – DMGregory Feb 1 at 12:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.