Using collision.contacts to determine if on a floor

Using collision.contacts to determine if on a floor

I'm trying to make my own first-person character (because I get lost in the sea that is Unity's FP character script), and am trying to make it so it detects when it is standing on a floor. I tried using a playerController, but I've found rigidbody is just more malleable. 
Anyways, so I think how you'd do it is finding the direction of the contact, and if it isn't straight down, then to keep onGround as false. I'm just not entirely sure the scripting behind this, and several searches have turned up nothing.
Several offered using mutliple colliders, but I would prefer to keep the number of objects, colliders or otherwise, at a minimum, considering there will be a large number of them in the scene as is. I also want something that's easily manipulable so that I can add extra features like climbing up a wall or the like, and I think that measuring the angle to the center is about as simple and easily manipulated as it gets.


Answer 1:

I’m assuming you’re referring to the player being detected as touching the ground, and not every object?
Regardless; simply place a collider on your floor.

Then add the following script to your floor.


OnCollisionEnter(Collision col){
if(col.gameObject.tag == "Player"){
col.getComponent<myCharacterScript>().isOnFloor = true;

OnCollisionExit(Collision col){
if(col.gameObject.tag == "Player"){
col.getComponent<myCharacterScript>().isOnFloor = false;

(You can equally use OnCollisionStay to have only one of these, however, it means the script has to check every fraction of a second as opposed to only when the event is called, so it ends up being less efficient.)

Also, the script I am referring to as “myCharacterScript” is simply whatever script you have on your player which is meant to hold whether he/she is touching the floor or not. That script (regardless of whatever else it holds) must have a boolean value “isOnFloor”.

Then you can do whatever you want with detecting it. E.g.

if(isOnFloor == true){
//Can Jump
//Can't Jump

Answer 2:

Let’s assume your player is using an upright capsule as a collider, or otherwise has a sphere shape at the bottom of their collision representation (this can help avoid little snags from small ridges in the level geometry, and has a more intuitive behaviour when going up ramps, rather than cantilevering off them as a box collider would.

Let radius be the radius of this spherical/capsule end, and assume it’s centered on the player’s vertical axis (collider’s local x & z = 0)

// Maximum angle in degrees that should still count as a floor/"underfoot".
public float maxIncline = 45.0f;

bool IsContactUnderneath(ContactPoint contact) {
   // Ignore collisions with ceilings/walls
   if(contact.normal.y <= 0f)

   // Take the contact point into our local space.
   Vector3 local = transform.InverseTransformPoint(contact.point);

   // Flatten & ignore the vertical component.
   // We'll just look at the footprint.
   local.y = 0.0f;

   // Cache this to a variable somewhere 
   // to avoid repeating the trig for every check.
   float threshold = radius * Mathf.Sin(maxIncline * Mathf.Deg2Rad);
   threshold *= threshold;

   return local.sqrMagnitude <= threshold;

This uses the fact that the steeper a flat surface is, the further out on the collider it will make contact – from directly in the center for a perfectly horizontal surface, to all the way out at the perimeter for surfaces approaching vertical.

Image of a capsule resting on two inclined planes

If you only have the one collider, you can instead check that contact.normal.y >= Math.Cos(maxIncline * Mathf.Deg2Rad). Here I’m doing a position-based check just in case you might hit an object with multiple colliders – like a gun barrel or an object you’re carrying – so even if the normal is upright it’s not mis-detected as a floor you’re standing on.