检测哪些 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}");
}
请注意,您至少需要一个刚体才能触发,但如果需要,您可以将字母换成圆圈。
因为这个问题不仅涵盖了 Collider2D
和 TilemapCollider2D
之间的碰撞,还涵盖了 Collider2D
和 每个方块之间的碰撞 ,它不像检测碰撞器之间的碰撞那么简单。
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,我学到了几个要点(对我来说不是很明显):
- 对于
TilemapCollider2D
,OnTriggerEnter2D()
和 OnTriggerExit2D()
在整个 TilemapCollider2D
级别调用,而不是在每个拼贴级别调用。即就像任何其他对撞机类型一样。
Collision2D.contacts
是ContactPoint2D
的数组。 ContactPoint2D.point
被描述为“世界 space 中两个对撞机之间的接触点。”这对我来说意味着它是交点。然而,它实际上是物理模型想要施加力的位置。因此,我的解决方案使用触发碰撞器和 OnTriggerXxxx
而不是 OnCollisionXxxx
,我自己计算出交叉点。
我有一个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}");
}
请注意,您至少需要一个刚体才能触发,但如果需要,您可以将字母换成圆圈。
因为这个问题不仅涵盖了 Collider2D
和 TilemapCollider2D
之间的碰撞,还涵盖了 Collider2D
和 每个方块之间的碰撞 ,它不像检测碰撞器之间的碰撞那么简单。
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,我学到了几个要点(对我来说不是很明显):
- 对于
TilemapCollider2D
,OnTriggerEnter2D()
和OnTriggerExit2D()
在整个TilemapCollider2D
级别调用,而不是在每个拼贴级别调用。即就像任何其他对撞机类型一样。 Collision2D.contacts
是ContactPoint2D
的数组。ContactPoint2D.point
被描述为“世界 space 中两个对撞机之间的接触点。”这对我来说意味着它是交点。然而,它实际上是物理模型想要施加力的位置。因此,我的解决方案使用触发碰撞器和OnTriggerXxxx
而不是OnCollisionXxxx
,我自己计算出交叉点。