OnCollisionEnter2D not called when stand on tile

I’m developing a platform jumping game.
In this game, I use tilemap to build the level. And each tile triggers different effect when player stand on those tile. The problem is when I walk from one tile to another type of tile, the method OnCollisionEnter2D isn’t called, however, When I jump to those tile, that method is called.
And the following is some details for the game.
class PlayerController is attached to player, which is derived from KinematicObject. KinematicObject handling the lower logic of physics world, and leave those game logic like state machine to PlayerController. and I use Rigidbody2D.Cast to detect whether or not player is collide with some tile then do the corresponding things, like stop moving when grounded and bounce back when hit tile.
And TileCollisionEffect.cs is attached to the tilemap object, for triggering effects.
here is some code snippet of KinematicObject.cs and TileCollisionEffect.cs
KinematicObject.cs

protected virtual void FixedUpdate()
{
    if (velocity.y < 0)
    velocity += gravityModifier * Physics2D.gravity * Time.deltaTime;
    else
    velocity += Physics2D.gravity * Time.deltaTime;

    IsGrounded = false;
    var deltaPosition = velocity * Time.deltaTime;

    var moveAlongGround = new Vector2(groundNormal.y, -groundNormal.x);
    var move = moveAlongGround * deltaPosition.x;
    PerformMovement(move, false);

    move = Vector2.up * deltaPosition.y;
    PerformMovement(move, true);

}

void PerformMovement(Vector2 move, bool yMovement)
{
    var distance = move.magnitude;

    if (distance > minMoveDistance)
    {
    var count = body.Cast(move, contactFilter, hitBuffer, distance + shellRadius);
    for (var i = 0; i < count; i++)
    {
        var currentNormal = hitBuffer[i].normal;
        if (currentNormal.y > minGroundNormalY) //minGroundNormalY = 0.65
        {
        IsGrounded = true;
        if (yMovement)
        {
            groundNormal = currentNormal;
            currentNormal.x = 0;
        }
        }
        if (IsGrounded)
        {
        var projection = Vector2.Dot(velocity, currentNormal);
        if (projection < 0)
        {
            velocity = velocity - projection * currentNormal; // zero velocity when hit the ground
        }
        }
        else
        {
        var projection=Vector2.Dot(velocity,currentNormal);
        velocity = velocity - 2*projection*currentNormal; //bounce back if hit tilemap when flying
        }
        var modifiedDistance = hitBuffer[i].distance - shellRadius;
        distance = modifiedDistance < distance ? modifiedDistance : distance;
    }
    }       
    body.position = body.position + move.normalized * distance;
}

TileCollisionEffect.cs

public class TileCollisionEffect : MonoBehaviour
{
    [System.Serializable]
    public class CollisionEvent : UnityEvent<PlayerController> { }

    [System.Serializable]
    public struct TileEffect {
    public TileBase tile;
    public CollisionEvent effect;
    }

    public TileEffect[] effects;
    Dictionary<TileBase, CollisionEvent> _effectMap;

    private void OnEnable() {
    if (_effectMap != null)
        return;

    _effectMap = new Dictionary<TileBase, CollisionEvent>(effects.Length);
    foreach (var entry in effects)
        _effectMap.Add(entry.tile, entry.effect);
    idx = 0;
    }

    private void OnCollisionEnter2D(Collision2D collision) {
    var points=new ContactPoint2D[100];
    var collider = collision.collider;
    int contact_point_num = collider.GetContacts(points);
    Debug.Log("TileCollisionEffect.OnCollisionEnter()"
        +" contact.normal = "+points[0].normal
        +" contact.points.Count = "+contact_point_num
        );
    if(points[0].normal.y < 0 ) // if player is not hit from above, then do nothing
    {
        return;
    }

    var map = GetComponent<Tilemap>();
    var grid = map.layoutGrid;

    var tileWorldPos = points[1].point;
    Vector3 localPosition = grid.transform.InverseTransformPoint(tileWorldPos);
    localPosition = localPosition - (Vector3)points[1].normal*0.5f;
    Vector3Int cell = grid.LocalToCell(localPosition);

    var tile = map.GetTile(cell);

    if(tile == null)
    {
        Debug.Log("TileCollisionEffect.OnCollisionEnter() tile is null");
        return; 
    }

    var playerController = collider.gameObject.GetComponent<PlayerController>();
    if (_effectMap.TryGetValue(tile, out CollisionEvent effect) && effect != null)
    {
        effect.Invoke(playerController);
    }
    }

    public void Orange(PlayerController pc) {
    pc.jump_coef_w =2;
    Debug.Log("Orange");
    }

    public void Green(PlayerController pc) {
    if(pc == null)
    {
        Debug.Log("TileCollisionEffect.Orange() pc is null");
    }
    pc.jump_coef_h =1.5f;
    Debug.Log("Green");
    }
    public void Alice(PlayerController pc) {
    pc.isStandonIce = true;
    Debug.Log("Alice");
    }
}

Pretty sure this direct assignment bypasses normal physics transitions / collisions.

Always use the .MovePosition() on a Rigidbody in order to keep the physics system “in the loop.”

Is that your problem?

I’m afraid is not that problem. Because When I jump from one tile to another tile, the collision method ‘OnCollisionEnter2D’ is called, though, which means collisions did happen. However, when I use MovePosistion(), the player object can not move again, it’s just stuck in his own place.

You get OnCollisionEnter2D when you first touch any 2D Collider. You get OnCollisionStay2D when there are still contacts for that 2D Collider and OnCollisionExit2D when there are no contacts for that 2D Collider.

You don’t get a OnCollisionEnter2D for each contact you get on the same 2D Collider.

This is no different than you moving across a (say) PolygonCollider2D that can have multiple polygon shapes; you don’t get a OnCollisionEnter2D as you touch each one; you get contact points.

If you’re using a single 2D Collider like a TilemapCollider2D then moving between tiles doesn’t result in OnCollisionEnter2D being called per-tile, it’s not a tile-based thing. Instead, you have contacts. The Tilemap provides you with world->tile-position conversion which you can use to convert to contact points to specific tiles you’re in contact with.

NOTE: There are dedicated 2D and Physics sub-forums you can use for this kind of thing where you can get the attention of 2D/Physics devs.

1 Like

Thank you so much for the detailed reply, It works now. I’m just new to unity and the forum as well. Didn’t know there is a sub-forum dedicated to 2D, I will post there next time I get stuck. Thanks again, for the notes.

1 Like