问题描述
我有一个Tilemap
。它有一个TilemapCollider2D
组件。上面画着几个瓷砖,每个瓷砖都有自己的精灵对撞机形状。然而,它们是精灵瓷砖,不是预制板。(它们不是使用预制画笔绘制的。)
我还有一个游戏对象,Collider2D
(在我的情况下是CircleCollider2D
),isTrigger
设置为true
,没有附加Rigidbody2D
,因为该游戏对象保持在相对于其父对象的固定位置。
[编辑:我发现这个对撞机实际上使用的是父游戏对象的Rigidbody2D
。如果没有刚体,则根本检测不到碰撞。]
当Collider2D
进入/退出磁贴时,我如何识别该磁贴的网格坐标(Vector3Int
)?
TilemapCollider2D.OnTriggerEnter2D()
或TilemapCollider2D.OnCollisionEnter2D()
。
例如,在下图中,我希望收到磁贴B、C、D和E的OnTriggerEnter2D()
,并知道它们在网格中的位置。
推荐答案
由于此问题不仅涉及Collider2D
和TilemapCollider2D
之间的冲突,而且还涉及Collider2D
和每个瓷砖之间的冲突,因此检测碰撞器之间的冲突并不那么简单。
(Simple detection of collision between the colliders is covered in this question on answers.unity.com.)
为了让tiemap脚本检测到每个磁贴的进入和退出,它需要响应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
}
}
}
FYI
From a separate discussion on forum.unity.com,我学到了几个要点(对我来说并不明显):
对于TilemapCollider2D
,OnTriggerEnter2D()
和amp;OnTriggerExit2D()
是在整个TilemapCollider2D
级别调用的,而不是在每个磁贴级别调用的。即就像任何其他对撞机类型一样。
Collision2D.contacts
是ContactPoint2D
的数组。ContactPoint2D.point
被描述为世界空间中两个对撞机之间的接触点。这对我来说意味着它是交叉点。然而,它实际上是物理模型想要施加力的位置。因此,我的解决方案使用触发对撞器和OnTriggerXxxx
而不是OnCollisionXxxx
,并且我自己计算交点。