【问题标题】:Detect which Tilemap cells have collided with a Collider2D in Unity在 Unity 中检测哪些 Tilemap 单元格与 Collider2D 发生了碰撞
【发布时间】:2020-11-23 21:39:08
【问题描述】:

我有一个Tilemap。这有一个TilemapCollider2D 组件。在上面绘制了几块瓷砖,每块瓷砖都有自己的精灵对撞机形状。但是它们是精灵图块而不是预制件。 (它们不是使用 Prefab Brush 绘制的。)

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

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

当Collider2D 进入/退出一个图块时,如何识别该图块的网格坐标(Vector3Int)?

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

例如,在下图中,我希望收到OnTriggerEnter2D() 用于瓷砖 B、C、D 和 E 并知道它们在网格中的位置。

【问题讨论】:

  • @derHugo 是的,我看过,但它是从圆形对象检测的角度做事,而不是瓷砖地图,所以我不确定它是否适用,我想避免如果可能的话,光线投射路线。我做了更多的实验并且......我发现我的圆形对撞机会自动使用父母的 Rigidbody2D,这是我没想到的,但它解释了为什么发生碰撞/触发检测。
  • 我按照这些说明制作了两个碰撞器isTrigger=false,我不得不将它们放在具有不同层碰撞设置的特殊层上,在项目设置 > 物理 2D 中避免其他对象卡在这些瓷砖上。这导致 OnCollisionEnter2D 工作,但行为是零星的。在第一次接触时,例如使用瓷砖 C 和 D,我会立即发生碰撞,但只有当圆圈到达两个网格方块下方时,我才会对所有其他瓷砖发生碰撞。诡异的。有什么想法吗?
  • 可能与此错误有关的奇怪行为? stackoverflow.com/questions/53462798/…或者有更理智的解释和解决方案吗?
  • 为了总结这些 cmets,我从 Unity 的几个人那里得到了对奇怪行为的一个很好的解释。 forum.unity.com/threads/…

标签: unity3d 2d collision-detection


【解决方案1】:

由于这个问题不仅涉及Collider2D和TilemapCollider2D之间的碰撞,还涉及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. 对于TilemapCollider2D,OnTriggerEnter2D() 和OnTriggerExit2D() 在整个TilemapCollider2D 级别调用,而不是在每个图块级别。即就像任何其他对撞机类型一样。
  2. Collision2D.contacts 是 ContactPoint2D 的数组。 ContactPoint2D.point 被描述为“世界空间中两个对撞机之间的接触点”。这对我来说意味着它是交叉点。然而,它实际上是物理模型想要施加力的位置。因此,我的解决方案使用触发对撞机和OnTriggerXxxx 而不是OnCollisionXxxx,我自己算出交叉点。

【讨论】:

    【解决方案2】:

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

    我是这样设置的:

    瓷砖地图

    -A - 有 colider2d 和rigidbody2d

    -B - 有colider2d和rigidbody2d

    -C - 有colider2d和rigidbody2d

    -D - 有 colider2d 和rigidbody2d

    Circle - 有圆形 collider2d 和一个圆形脚本

    以下是在圆形脚本中,并给出了确切的触发对象

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

    请注意,您至少需要一个刚体才能获得触发器,但如果需要,您可以从字母切换到圆圈。

    【讨论】:

    • 我会让我的问题更清楚:上面有字母的瓷砖不是预制件;即它们不是用 Prefab Brush 绘制的,因此它们不是 Tilemap 的子级。这是故意的,因为我想从具有单个 GameObject 的 Tilemap 中受益,而使用 Prefabs 会导致每个 tile 有一个 GameObject。我要问的问题与 TilemapColldier2D.OnTriggerEnter2D() 有关
    • 我对瓦片地图不是很熟悉,但是如果没有画甚至没有孩子,你是如何区分各种瓦片的?如果我想帮忙,只需要知道你的设置如何。
    • 您查看 derHugo 的评论了吗?有类似的吗?
    猜你喜欢
    • 2014-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多