检测哪些 Tilemap 单元与 Unity 中的 Collider2D 发生碰撞

Detect which Tilemap cells have collided with a Collider2D in Unity

我有一个Tilemap。这有一个 TilemapCollider2D 组件。上面画了几块瓷砖,每块瓷砖都有自己的精灵碰撞器形状。然而,它们是精灵图块而不是预制件。 (它们不是使用预制笔刷绘制的。)

我还有一个 Collider2D 的游戏对象(在我的例子中是 CircleCollider2D),isTrigger 设置为 true 并且没有附加 Rigidbody2D因为此游戏对象相对于其父对象保持在固定位置。

[编辑:我发现这个对撞机实际上是在使用父游戏对象的 Rigidbody2D。没有任何刚体,根本不会检测到碰撞。]

Collider2D enters/exits 一个瓦片时,如何识别该瓦片的网格坐标(Vector3Int)?

明确地说,我想从 Tilemap 脚本中检测到这一点。即 TilemapCollider2D.OnTriggerEnter2D()TilemapCollider2D.OnCollisionEnter2D().

例如,在下图中,我想收到 OnTriggerEnter2D() 的方块 B、C、D 和 E,并知道它们在网格中的位置。

不确定您的设置,但我建议尝试在字母对象上使用碰撞器,它们是瓦片地图的子项,对吧?

我是这样设置的:

瓷砖地图

-A - 有 colider2d 和 rigidbody2d

-B - 有 colider2d 和 rigidbody2d

-C - 有 colider2d 和 rigidbody2d

-D - 有 colider2d 和 rigidbody2d

Circle - 有 circle collider2d 和 circle script

以下是圆圈脚本,给出了确切的触发对象

private void OnTriggerEnter2D(Collider2D collider)
{
    Debug.Log($"touched {collider.name}");
}

请注意,您至少需要一个刚体才能触发,但如果需要,您可以将字母换成圆圈。

因为这个问题不仅涵盖了 Collider2DTilemapCollider2D 之间的碰撞,还涵盖了 Collider2D 每个方块之间的碰撞 ,它不像检测碰撞器之间的碰撞那么简单。

(Simple detection of collision between the colliders is covered in this question on answers.unity.com.)

tilemap脚本检测每个tile的进出,需要响应OnTriggerEnter2D(), OnTriggerStay2D() & OnTriggerExit2D().

这是我的解决方案,它基于检测 CircleCollider2D 何时与每个图块相交,而不考虑图块内任何碰撞器的几何形状。交叉点是一个近似值(为了提高效率),可能需要适应其他类型的 Collider2D.

概述

OnTriggerEnter2D() 内,获取 CircleCollider2D 的边界框并从中确定它与哪些图块相交。

对于每个图块,获取最接近该图块中心的 CircleCollider2D 的世界位置。如果该世界位置在图块​​内,则存在交叉点。除了根据需要进行处理外,还将此图块的坐标添加到跟踪列表中。

OnTriggerStay2D() 内,执行与 OnTriggerEnter2D() 相同的操作,但从跟踪列表中删除那些不再相交的图块并处理它们的相交出口。

OnTriggerExit2D()内,两个碰撞器分离,因此处理跟踪列表中所有图块的交集出口并清除跟踪列表。

代码示例

using System.Collections;
using System.Collections.Generic;
using System.Linq; // needed for cloning the list with .ToList()
using UnityEngine;
using UnityEngine.Tilemaps; // needed for Tilemap

public class MyTilemapScript : MonoBehaviour
{
    List<Vector3Int> trackedCells;
    Tilemap tilemap;
    GridLayout gridLayout;

    void Awake()
    {
        trackedCells = new List<Vector3Int>();
        tilemap = GetComponent<Tilemap>();
        gridLayout = GetComponentInParent<GridLayout>();
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        // NB: Bounds cannot have zero width in any dimension, including z
        var cellBounds = new BoundsInt(
            gridLayout.WorldToCell(other.bounds.min),
            gridLayout.WorldToCell(other.bounds.size) + new Vector3Int(0, 0, 1));

        IdentifyIntersections(other, cellBounds);
    }

    void OnTriggerStay2D(Collider2D other)
    {
        // Same as OnTriggerEnter2D()
        var cellBounds = new BoundsInt(
            gridLayout.WorldToCell(other.bounds.min),
            gridLayout.WorldToCell(other.bounds.size) + new Vector3Int(0, 0, 1));

        IdentifyIntersections(other, cellBounds);
    }

    void OnTriggerExit2D(Collider2D other)
    {
        // Intentionally pass zero size bounds
        IdentifyIntersections(other, new BoundsInt(Vector3Int.zero, Vector3Int.zero));
    }

    void IdentifyIntersections(Collider2D other, BoundsInt cellBounds)
    {
        // Take a copy of the tracked cells
        var exitedCells = trackedCells.ToList();

        // Find intersections within cellBounds
        foreach (var cell in cellBounds.allPositionsWithin)
        {
            // First check if there's a tile in this cell
            if (tilemap.HasTile(cell))
            {
                // Find closest world point to this cell's center within other collider
                var cellWorldCenter = gridLayout.CellToWorld(cell);
                var otherClosestPoint = other.ClosestPoint(cellWorldCenter);
                var otherClosestCell = gridLayout.WorldToCell(otherClosestPoint);

                // Check if intersection point is within this cell
                if (otherClosestCell == cell)
                {
                    if (!trackedCells.Contains(cell))
                    {
                        // other collider just entered this cell
                        trackedCells.Add(cell);

                        // Do actions based on other collider entered this cell
                    }
                    else
                    {
                        // other collider remains in this cell, so remove it from the list of exited cells
                        exitedCells.Remove(cell);
                    }
                }
            }
        }

        // Remove cells that are no longer intersected with
        foreach (var cell in exitedCells)
        {
            trackedCells.Remove(cell);

            // Do actions based on other collider exited this cell
        }
    }
}

仅供参考

From a separate discussion on forum.unity.com,我学到了几个要点(对我来说不是很明显):

  1. 对于 TilemapCollider2DOnTriggerEnter2D()OnTriggerExit2D() 在整个 TilemapCollider2D 级别调用,而不是在每个拼贴级别调用。即就像任何其他对撞机类型一样。
  2. Collision2D.contactsContactPoint2D的数组。 ContactPoint2D.point 被描述为“世界 space 中两个对撞机之间的接触点。”这对我来说意味着它是交点。然而,它实际上是物理模型想要施加力的位置。因此,我的解决方案使用触发碰撞器和 OnTriggerXxxx 而不是 OnCollisionXxxx,我自己计算出交叉点。