0
\$\begingroup\$

Until now I've been developing my player character using a Character Controller, but have since decided to switch to rigidbody due to the controller's limitations. In order for my movement code to run as expected I need to know when the player is colliding with anything, and the normal of the surface it's colliding with. This is easy with a Character Controller, but is there a simple way to do it with a rigidbody?

Here is a stripped down version of the script I'm using for collisions.

 // Used in other scripts (Necessary)
    public GameObject ground = null;
    public Vector3 groundNormal;
    public bool onGround;

    // Used internally
    public bool isColliding;
    public float slopeLimit;

    // Character Controller
    public CharacterController controller;

    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    void Update()
    {
        // Check if the player is colliding with anything (Character Controller)
        if (controller.collisionFlags == CollisionFlags.None)
        {
            isColliding = false;
        }
        else
        {
            isColliding = true;
        }

        // Check if the player is standing on the ground
        if (isColliding && groundNormal != Vector3.zero)
        {
            onGround = true;
        }
        else
        {
            onGround = false;
        }
    }

    void OnControllerColliderHit(ControllerColliderHit hit)
    {
        // Check for floor
        if (Vector3.Angle(transform.up, hit.normal) <= slopeLimit)
        {
            // Store floor object data
            ground = hit.gameObject;
            // Store ground surface normal
            groundNormal = Vector3.Normalize(hit.normal);
        }
        else
        {
            // reset variables
            ground = null;
            groundNormal = Vector3.zero;
        }
    }

slopeLimit is a static value. Additionally, getting the ground object isn't actually necessary atm, but I plan on using it later.

Edit: Currently I'm using controller.collisionFlags to detect when the player is colliding with something and haven't been able to find a similar alternative. Setting the isColliding bool to true using Rigidbody.OnCollisionStay would be simple, but setting it to false without use of another void function seems difficult. is that possible at all?

\$\endgroup\$
  • \$\begingroup\$ Presumably you tried handling the OnCollisionEnter (or Stay/Exit) message and reading this info out of the Collision structure it passes you? Did you have any particular difficulty getting what you need that way? \$\endgroup\$ – DMGregory Jun 4 '20 at 0:28
  • \$\begingroup\$ It's getting that collision detection into bool form that I'm having difficulty with, since I couldn't find a function similar enough to 'collisionFlags'. And if there is a funtion to find the normal of the collided surface, I can't find it or figure out the syntax \$\endgroup\$ – Quikflash Jun 4 '20 at 2:12
  • \$\begingroup\$ collision.contacts[0].normal, no? Want to edit your question to describe the bool problem in more detail? \$\endgroup\$ – DMGregory Jun 4 '20 at 2:15
  • \$\begingroup\$ Unity's site said collision.contacts produces garbage data so I avoided using it, but I couldn't figure out the syntax for the linked alternative, ContactPoint.normal. I'll try it though. As for the bool problem, yeah I'll do that. \$\endgroup\$ – Quikflash Jun 4 '20 at 2:35
  • \$\begingroup\$ You can use collision.GetContact(0).normal to avoid allocation, if that's what you're talking about. \$\endgroup\$ – DMGregory Jun 4 '20 at 2:47
0
\$\begingroup\$

You can get your is-colliding boolean just by counting collisions entered and exited:

int _collisionCount;
public bool IsColliding { get { return _collisionCount > 0; } }

void OnCollisionEnter(Collision collision) {
    _collisionCount++;
}

void OnCollisionExit(Collision collision) {
    _collisionCount--;
}

There are lots of solutions to ground checks, but here's one I like to use:

public LayerMask groundLayers; // Populate this with which physics layers count as "ground".

bool IsOnGround(out Vector3 groundNormal) {
    groundNormal = Vector3.zero;

    if ( !IsColliding ) return false; // If we're not colliding with anything, we're not grounded.

    // Physics "contacts" can actually have some minor separation, so let's add a margin.
    float margin = Physics.defaultContactOffset;

    // Start your check from a little above the bottom of your capsule.
    // I like to set up my colliders so "foot position" is always transform.position,
    // but your setup may vary. I like to make my ground check radius 1 margin smaller
    // than my capsule radius, to make sure my check never starts already in-contact
    // with an obstacle.
    Vector3 checkOrigin = GetFootPosition() 
                        + new Vector3(0, groundCheckRadius + margin, 0);

    // Check for a collision under our feet.
    if ( Physics.SphereCast(
             checkOrigin,        // Start just above the bottom of our capsule,
             groundCheckRadius,  // with a sphere slightly smaller than our capsule.
             Vector3.down,       // Fire it downwards,
             out RaycastHit hit, // and record what it hits,
             2 * margin,         // going just far enough to catch a touching contact,
             groundLayers        // with anything marked "ground".
    ) == false) return false;    // If we didn't hit anything, we're not grounded.

    if ( Vector3.Angle(transform.up, hit.normal) > slopeLimit)
        return false; // Too steep to stand on!

    // Otherwise, we are grounded. Extract the ground normal and return true.
    groundNormal = hit.normal;
    return true;
}
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.