Groundcheck fails when walking across two different tilemaps?

DescriptiveRealBrontosaurus

The ground and coinblocks are identical with the same tags, but the coinblocks exist in a different tilemap (I haven’t programmed in their behavior yet).
For some reason, the “Is Grounded” flag gets lost when moving between tilemaps, and only gets restored after triggering “OnCollisionExit2D” or standing between the two tiles.
I’m very new to Unity and am very amateur in general, so if putting the coinblocks into a different tilemap is wrong, please tell me so.

The GroundCheck is a Box Collider underneath the player sprite.
The code for it is as follows:

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

public class Grounded : MonoBehaviour
{
    GameObject Player;
    public Animator animator;
  
    // Start is called before the first frame update
    void Start()
    {
        Player = gameObject.transform.parent.gameObject;
    }

    // Update is called once per frame
    void Update()
    {
      
    }
  
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.tag == "Ground"){
            Player.GetComponent<Move2D>().isGrounded = true;
            animator.SetBool("IsJumping", false);
        }
    }
  
    private void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.collider.tag == "Ground"){
            Player.GetComponent<Move2D>().isGrounded = false;          
        }
    }
  
}

I can sort of understand why this is happening, but why it gets restored when standing between the blocks is beyond me.

I’ve been wracking my brain for an elegant solution, but am having a lot of trouble.
Can anyone help?

Unfortunately Enter & Exit events are not extremely reliable for state like this, because you can get errant enter/exit calls when in high-precision physics situations, like bordering two colliders, or in high-speed transitions, where perhaps you exit the old collider first, then enter the other, or vice versa. Sometimes the physics system resolves in unexpected ways.

I think that “Grounded” is a state that is important enough to require per-frame precision, so I would recommend using Update or FixedUpdate to check if your player is currently touching the ground.

I think a straightforward way to check if your specific GroundCheck collider is touching the ground is to use Collider2D.IsTouchingLayers. This would mean your ground objects would need to be on their own layer, not just tagged Ground. (This also means you can have multiple different layers that count as surfaces to be “grounded” on, without needing to tag them “Ground”).

You can also use Collider2D.OverlapCollider and then loop through the results to check for “Ground” tags.

That way you can manually do the check and be 100% certain about the results, rather than inferring state based on events after the physics step.

1 Like

Thank you very much for the detailed answer! That makes a lot of sense, I’ll give that a shot.

1 Like

So, I fixed that bug I think by removing the OnCollisionEnter/Exit stuff and adding the following code, but in typical fashion a new mystery arises.

void FixedUpdate()
    {
        if (GetComponent<BoxCollider2D>().IsTouchingLayers(LayerMask.NameToLayer("Ground")))
        {
            Player.GetComponent<Move2D>().isGrounded = true;
            animator.SetBool("IsJumping", false);
        }
        if (!(GetComponent<BoxCollider2D>().IsTouchingLayers(LayerMask.NameToLayer("Ground"))))
        {
            Player.GetComponent<Move2D>().isGrounded = false;
            animator.SetBool("IsJumping", true);
        }
  
    }

Here’s the new conundrum: From my understanding, I should only be able to jump off of the TileMapColliders in the “Ground” layer, but I seem to be able to jump off of any collider in any layer. Even an enemy with a Box Collider 2D(“enemy” is nothing more than a Box Collider 2D, Rigidbody 2D and an animation at this point).

I’m guessing it has something to do with “LayerMask.NameToLayer(“Ground”)” not being quite correct.

I appreciate your help greatly, thanks again for pointing me in the right direction.

Note: You should try to grab things like components and layers once only and store them in the class upon start-up (because they don’t change) as opposed to repeatedly acquiring them. This costs time, especially when done several times in the same function. It also makes the code less verbose and easier to read. :slight_smile:

So “LayerMask.NameToLayer” returns a single layer as an integer. “IsTouchingLayers” requires a layer-mask i.e. can accept multiple layers as a bit-mask so layer 6 would be “(1 << 6)” and layers 4 and 6 would be “(1 << 4) | (1 << 6)”.

Try:

void FixedUpdate()
{
    var collider = GetComponent<BoxCollider2D>();
    var playerMove2D = Player.GetComponent<Move2D>();
    var groundLayerMask = 1 << LayerMask.NameToLayer("Ground");

    playerMove2D.isGrounded = collider.IsTouchingLayers(groundLayerMask);
 
    animator.SetBool("IsJumping", !playerMove2D.isGrounded);
}

The previous explanation is correct about Enter/Exit however I’d like to point out that the callbacks are not errant in any way, they are just unordered so there’s nothing stopping getting a callback that you exit one collider followed by a callback for entering on another. You logic doesn’t take that into account. You could’ve kepts a counter, incrementing enters for the ground-layer and decrementing exits for the ground-layer and only when this is not zero are you grounded however the suggestion to use queries above is correct and is far easier in many cases. The “IsTouchingLayers” is particularly good for this. Note that it also has an overload for a ContactFilter2D which allows you to narrow down the contacts by layer, even collision direction so it ignores ground above you for instance.

2 Likes

Thank you so much, that was very informative and cleared up a bunch of misconceptions of mine.

I greatly appreciate the help

Three years later and you helped me fix the exact same bug! Thank you!