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