0
\$\begingroup\$

I'm working on an 3D insect/spider controller that will walk on any time of surface. Currently my character scan the environment around him by shooting a very big number of spherecasts and based on hit points data and some weights multiplication for precision improvements, it calculate a new rotation for the player. the new position direction is calculated based on camera forward projected on character's up dir and the actual speed is reduced based on difference between new rotation and old rotation of the character( the more we rotate, the less we'll travel).

Currently this solution works good but I would like to upgrade the controller to do more: I want to have a defined space, a disk, around player position and i want the controller to makes sure no matter where and how is travelling, this disk will always be outside any surface collider. The bottom image is describing the situations that I want to avoid and what would be a better approach for the character (it is 2d but my controller walk in a 3d space and the blue line represent the disk I'm talking about).

First scenario describe my actual controller behaviour, second show what I want to achieve

I'm not really sure how to do it correctly and I feel there exist a good mathematically formula for this but I'm not sure where to look.

Any help will be appreciated.

\$\endgroup\$
3
  • \$\begingroup\$ Are you using a framework / engine like Unity, Unreal, & so forth or a custom physics engine? \$\endgroup\$
    – Pikalek
    Commented Jan 16, 2023 at 3:27
  • \$\begingroup\$ I am using Unity Engine \$\endgroup\$
    – Vasi
    Commented Jan 16, 2023 at 8:09
  • 1
    \$\begingroup\$ I've gone ahead & edited to add the unity tag. Including the framework tag helps people with those expertise find your question which in turn makes it more likely to get answers. \$\endgroup\$
    – Pikalek
    Commented Jan 16, 2023 at 15:01

1 Answer 1

1
\$\begingroup\$

While not really a perfect solution, you can sort of do this with the Physics.Overlap[colliderType] and ComputePenetration functions. This gives you the shortest distance (and direction) that the collider you use for the overlap needs to be moved to stop overlapping.

It can cause weird behaviour when moving really fast or dealing with very narrow walls and such, as you are only testing the final position of the collider, not "the movement" (as would happen if you were doing a cast of some sort), but it's often good enough.

Below is some code that I'm using in my own project, mostly based off the example in the ComputePenetration docs.

public static void DoNotGoIntoWalls(Collider collider, Transform object_transform, int layerMask){
    //It is very important to correct for rotations and scales, since all the collider properties are local space
    //and the physics functions require global space
    int numberOfCollisions=-1;
    if(collider is CapsuleCollider){
        CapsuleCollider capsule = (CapsuleCollider) collider;
        Vector3 height;
        if(capsule.direction == 0){
            height = collider.transform.localToWorldMatrix.MultiplyVector(new Vector3(capsule.height/2f - capsule.radius, 0, 0));
        }
        else if( capsule.direction ==1 ){
            height = collider.transform.localToWorldMatrix.MultiplyVector(new Vector3(0, capsule.height/2f - capsule.radius, 0));
        }
        else{
            height = collider.transform.localToWorldMatrix.MultiplyVector(new Vector3(0, 0, capsule.height/2f - capsule.radius));
        }
        Vector3 p0 = collider.gameObject.transform.localToWorldMatrix.MultiplyPoint(capsule.center) + height;
        Vector3 p1 = collider.gameObject.transform.localToWorldMatrix.MultiplyPoint(capsule.center) - height;
        float radius = collider.gameObject.transform.localToWorldMatrix.MultiplyVector(new Vector3(capsule.radius, 0, 0)).magnitude;
        
        numberOfCollisions = Physics.OverlapCapsuleNonAlloc(p0, p1, radius, colliderList, layerMask);
    }
    else if(collider is BoxCollider){
        BoxCollider box = (BoxCollider) collider;

        Vector3 center = collider.transform.localToWorldMatrix.MultiplyPoint(box.center);
        //express the size of the box in world units
        //the transform matrix is not enough, sadly
        Vector3 ext = collider.transform.localToWorldMatrix.MultiplyVector(box.size*0.5f);

        Quaternion inv = Quaternion.Inverse(collider.transform.rotation);
        ext = inv * ext;
        numberOfCollisions = Physics.OverlapBoxNonAlloc(center, ext, colliderList, collider.transform.rotation, layerMask);
    }
    else if(collider is SphereCollider){
        SphereCollider sphere = (SphereCollider) collider;
        Vector3 center = collider.gameObject.transform.localToWorldMatrix.MultiplyPoint(sphere.center);
        float radius = collider.gameObject.transform.localToWorldMatrix.MultiplyVector(new Vector3(sphere.radius, 0, 0)).magnitude;
        numberOfCollisions = Physics.OverlapSphereNonAlloc(center, radius, colliderList, layerMask);
    }

    for(int i = 0; i<numberOfCollisions; i++){
        Vector3 direction;
        float distance;
        if (colliderList[i]!=collider && Physics.ComputePenetration(collider, collider.transform.position, collider.transform.rotation, 
                colliderList[i], colliderList[i].transform.position, colliderList[i].transform.rotation, out direction, out distance)){
                    object_transform.position += direction*distance;
                }
    }
}
```
\$\endgroup\$

You must log in to answer this question.

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