3
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ You might want to look into the dot product for this. Taking the dot product of Vector3.up and contact.normal gives you a number if it's dot<0 it's in the opposite direction. dot>0 is same direction. btw contact.normal gives you the perpendicular vector of the face you're hitting. \$\endgroup\$ – Sidar Nov 10 '17 at 8:02
  • \$\begingroup\$ Doing this based on contact direction might get tricky, depending on the shape of your player collider. Imagine going up a flight of stairs or a ramp - those should count as a floor, but generally you'll collide with them a little off to the rising side rather than directly below your center of mass. If you add tolerance for this, then you risk detecting some inclined walls/beveled ledges as floors - especially if you're intending to add wall climbing and might encounter organically shaped surfaces at times. \$\endgroup\$ – DMGregory Nov 10 '17 at 13:07
2
\$\begingroup\$

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.

C#

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
}
else{
//Can't Jump
}
\$\endgroup\$
  • \$\begingroup\$ "Several offered using mutliple colliders, but I would prefer to keep the number of objects, colliders or otherwise, at a minimum" - it sounds like OP would prefer an option that doesn't require the floor to be a separate object from the walls / other level collision geometry. \$\endgroup\$ – DMGregory Nov 10 '17 at 13:08
  • \$\begingroup\$ @DMGregory While that may be the case, having an extra collider that is essential as the floor, IS keeping it to a minimum in order to be functional. Without that, there would be no way to differentiate the floor from other geometry, and using my exact example, that would mean theoretically, a player could stick themselves to a wall - it would register as being on the floor - and they could continuously jump, reaching the ceiling and beyond. Equally if they can carry objects, they could hold an object and continue to jump. As such, the floor must be separate. \$\endgroup\$ – cmprogram Nov 10 '17 at 13:12
1
\$\begingroup\$

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)
     return;

   // 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.

\$\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.