OnCollisionStay/Exit problem [Solved]

Hi,

I use OnCollisionStay and OncollisionExit to check if my player is on the ground. However, if my player goes from 1 collider, to another, without ‘‘being on no collider’’, than IsGround = false, but it should be true. I also tried to use tags but it didn’t work. Here is my script:

    public bool IsGrounded;

    void OnCollisionStay (Collision col)
    {
        IsGrounded = true;
    }

    void OnCollisionExit (Collision col)
    {
        if (col.gameObject.tag.Equals ("Trigger"))
        {
            IsGrounded = true;
        }
        else
        {
            IsGrounded = false;
        }

        if (IsGrounded == false)
        {
            Destroy(GameObject.FindWithTag("Player"));
        }
    }

(Only the grey cube is tagged with “Trigger”)

The white and grey cube both have a collider.
Ofcourse I can make the white cube collider longer so it overlaps the grey cube, and that works, but that is not an option for me.

So, the change between the cubes makes IsGrounded false. Is there a better way to check if the player is grounded or how can I fix it otherwise?

Hi, if you do the destroy in Update() instead of OnCollisionExit() there’s a chance you’ll have the grounded flag true from picking up the second collider.

However, you could have cases with a racing condition causing the flag to stay false.

To go arround it, you could instead store in a list all the colliders your ball touches at any given time in OnCollisionEnter, remove them from the list in OnCollisionExit, and check in Update wether you’ve got at list one ground collider in list or not.

Additionnally, this method has a major flaw that on rare occasion you wont get the OnCollisionExit, it happens sometimes when there is a lag spike and you can’t do anything to prevent it (as far as I know). So what you need to do is check if your player still collides with colliders in list, if your objects are axis aligned then it’s a really simple and efficient bounds.contains() check.

You could also consider raycasting/spherecasting to find out if your ball is grounded.

Try this instead. It is a general method I use to check for ground.

bool grounded;

void FixedUpdate(){
    //whatever you are doing for movement
    grounded = false;
}
void OnCollisionStay(Collision col){
    if(col.contacts[0].normal.y >= 0.6f){
        grounded = true;
    }
}

FixedUpdate is used since it is consistently called before the collision and trigger functions have done their job. This means that the grounded boolean will have been altered, by OnCollisionStay in this case, the next time it re-enters FixedUpdate.

The if condition inside of OnCollisionStay checks for the angle of what is being collided with and makes sure it is below a certain angle of elevation.

4 Likes

Error:

I don’t really understand what you mean with this, can you give an example?

Worrisome, you did not have the know-how to correct yourself. In anycase, OnCollisionEnter, OnCollisionExit, and OnCollisionStay take a Collision as the parameter inside the bracket, not a Collider which was my mistake, and has been changed accordingly in my original reply.

I meant having a list of colliders in your class:

List<Collider> _touchingColliders = new List<Collider>();

And feed it in OnCollisionEnter and OnCollisionExit:

void OnCollisionEnter(Collision col)
{
     _touchingCollider.Add(col.collider);
}

void OnCollisionExit(Collision col)
{
     _touchingCollider.Remove(col.collider);
}

Then in Update() check if you have someting in the list, meaning the object is grounded:

void Update()
{
      isGrounded = _touchingColliders.Count > 0;
}

The reason I like this approach is because with the proper setup you can get rid of OnCollisionEnter and Exit and replace it with OnTriggerEnter and OnTriggerExit which don’t allocate memory, it’s probably not a concern for you just yet so don’t worry too much about it though.

2 Likes

ill try it tomorrow

If I put

if (grounded == false)
        {
            Destroy(GameObject.FindWithTag("Player"));
        }

under OnCollisionStay, nothing happend.
If I put it under Update, the player instantly destroys.
With grounded I don’t mean going up, but falling down, so falling from the cube.

What am I doing wrong??

using UnityEngine;
using System.Collections;
using System;

public class PlayerMovement : MonoBehaviour {

    public float speed = 15.0f;
    public Terrain terrain;
    public Rigidbody rb;

    private float timer;
    public Vector3 dir;

    public bool IsGrounded;

    public bool grounded;



    void OnCollisionExit(Collision col)
    {
        _touchingCollider.Remove(col.collider);
    }


    void Start ()
    {
        transform.position = new Vector3(232f,22.5f,248f);
        dir = Vector3.left;
        rb = GetComponent<Rigidbody>();
        List<Collider> _touchingColliders = new List<Collider>();
    }


    void Update ()
    {

        timer += 1.0f * Time.deltaTime;

        float amountToMove = speed * Time.deltaTime;

        if (timer >= 3.1f)
        {
            transform.Translate(dir * amountToMove);   
        }

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        if (Input.GetMouseButtonDown(0))
        {
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                if(hit.collider.gameObject.name == "MoveLeft")
   
                {   
                    dir = Vector3.forward;
                }

                if(hit.collider.gameObject.name == "MoveRight")

                {   
                    dir = Vector3.back;
                }

                if(hit.collider.gameObject.name == "MoveUp")

                {   
                    dir = Vector3.right;
                }

                if(hit.collider.gameObject.name == "MoveDown")

                {   
                    dir = Vector3.left;
                }
               
                isGrounded = _touchingColliders.Count > 0;

            }
        }
    }

    void OnCollisionEnter(Collision col)
    {
        _touchingCollider.Add(col.collider);

        if (col.gameObject.tag.Equals ("Terrain"))
        {
            Destroy(GameObject.FindWithTag("Player"));
        }
    }
}

Line 31 is where you declared your touchingColliders list so it can be only used within that method.
In order to use this list/variable in whole script declare it outside of the method - like you did with the other variables.

I tried that before, but than I get this error

You need to add this to top of your script:

using System.Collections.Generic;

Keep getting errors

You’ll just have to learn scripting, in order fix simple issues like these…

Anyway, I fixed all errors in your script…

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public class PlayerMovement : MonoBehaviour
{

    public float speed = 15.0f;
    public Terrain terrain;
    public Rigidbody rb;

    private float timer;
    public Vector3 dir;

    public bool IsGrounded;

    public bool grounded;

    List<Collider> _touchingColliders = new List<Collider>();


    void OnCollisionExit(Collision col)
    {
        _touchingColliders.Remove(col.collider);
    }


    void Start()
    {
        transform.position = new Vector3(232f, 22.5f, 248f);
        dir = Vector3.left;
        rb = GetComponent<Rigidbody>();
    }


    void Update()
    {

        timer += 1.0f * Time.deltaTime;

        float amountToMove = speed * Time.deltaTime;

        if (timer >= 3.1f)
        {
            transform.Translate(dir * amountToMove);
        }

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;

        if (Input.GetMouseButtonDown(0))
        {
            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                if (hit.collider.gameObject.name == "MoveLeft")
                {
                    dir = Vector3.forward;
                }

                if (hit.collider.gameObject.name == "MoveRight")
                {
                    dir = Vector3.back;
                }

                if (hit.collider.gameObject.name == "MoveUp")
                {
                    dir = Vector3.right;
                }

                if (hit.collider.gameObject.name == "MoveDown")
                {
                    dir = Vector3.left;
                }

                IsGrounded = _touchingColliders.Count > 0;

            }
        }
    }

    void OnCollisionEnter(Collision col)
    {
        _touchingColliders.Add(col.collider);

        if (col.gameObject.tag.Equals("Terrain"))
        {
            Destroy(GameObject.FindWithTag("Player"));
        }
    }
}

Yes, I still have to learn a lot. I don’t see the problem of the error sometimes.

Out of nowhere I came up in one of the most simple idea that easily fix my problem. I don’t know how I didn’t came up with this earlier.
This is all what I needed :3

    void Update()

    {
        if (transform.position.y <= 22.45f || transform.position.y >= 22.55f)
            {
            Destroy(GameObject.FindWithTag("Player"));
            }
}

I thought too complicated.
Still thanks for the help!