콘텐츠로 이동

Unity Physics Queries 고급 활용 가이드

Unity의 Physics Queries는 레이캐스트, 오버랩, 캐스트 쿼리를 통해 씬의 콜라이더를 검색합니다. 올바른 API를 선택하고 NonAlloc 버전을 사용하면 GC 알로케이션 없이 효율적인 충돌 감지를 구현할 수 있습니다.


// 기본 Raycast
void BasicRaycast()
{
if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, 100f))
{
Debug.Log($"히트: {hit.collider.name} 거리: {hit.distance:F2}m");
Debug.Log($"히트 포인트: {hit.point}, 법선: {hit.normal}");
}
}
// LayerMask 사용
void LayeredRaycast()
{
int layerMask = LayerMask.GetMask("Enemy", "Obstacle");
if (Physics.Raycast(transform.position, transform.forward,
out RaycastHit hit, 100f, layerMask))
{
// Enemy 또는 Obstacle 레이어만 감지
}
}
// 트리거 포함/제외
void TriggerRaycast()
{
Physics.Raycast(transform.position, transform.forward,
out RaycastHit hit, 100f,
Physics.DefaultRaycastLayers,
QueryTriggerInteraction.Ignore); // 트리거 제외
// QueryTriggerInteraction.Collide); // 트리거 포함
}

// 프레임마다 호출되는 경우 NonAlloc 필수
public class EnemyDetector : MonoBehaviour
{
// 한 번만 할당
private readonly Collider[] _hitBuffer = new Collider[20];
private readonly RaycastHit[] _rayBuffer = new RaycastHit[10];
void Update()
{
// OverlapSphere — 범위 내 모든 콜라이더
int count = Physics.OverlapSphereNonAlloc(
transform.position, 5f, _hitBuffer,
LayerMask.GetMask("Enemy"));
for (int i = 0; i < count; i++)
{
var enemy = _hitBuffer[i].GetComponent<Enemy>();
enemy?.TakeNotice(transform.position);
}
// RaycastAll NonAlloc
int rayCount = Physics.RaycastNonAlloc(
transform.position, transform.forward,
_rayBuffer, 50f);
// 거리순 정렬
System.Array.Sort(_rayBuffer, 0, rayCount,
Comparer<RaycastHit>.Create((a, b) =>
a.distance.CompareTo(b.distance)));
}
}

// SphereCast: 구체를 앞으로 쏘아 충돌 감지
// 캐릭터 발 감지, 넓은 범위 사격에 유용
void SphereCastExample()
{
float radius = 0.5f;
if (Physics.SphereCast(transform.position, radius, transform.forward,
out RaycastHit hit, 20f))
{
Debug.DrawLine(transform.position, hit.point, Color.red);
}
}
// CapsuleCast: 캡슐 형태로 충돌 감지
// 캐릭터 이동 전방 장애물 감지에 적합
void CapsuleCastExample()
{
var capsule = GetComponent<CapsuleCollider>();
float height = capsule.height * 0.5f - capsule.radius;
Vector3 top = transform.position + Vector3.up * height;
Vector3 bottom = transform.position - Vector3.up * height;
if (Physics.CapsuleCast(top, bottom, capsule.radius,
transform.forward, out RaycastHit hit, 2f))
{
Debug.Log($"전방 장애물: {hit.collider.name}");
}
}

// 근접 공격 판정 — 박스 범위
public class MeleeAttack : MonoBehaviour
{
[SerializeField] private Vector3 attackBox = new Vector3(1.5f, 1f, 2f);
[SerializeField] private LayerMask enemyLayer;
private readonly Collider[] _hitBuffer = new Collider[10];
public void PerformAttack()
{
Vector3 attackCenter = transform.position + transform.forward * 1f;
int count = Physics.OverlapBoxNonAlloc(
attackCenter,
attackBox * 0.5f,
_hitBuffer,
transform.rotation,
enemyLayer);
for (int i = 0; i < count; i++)
{
var enemy = _hitBuffer[i].GetComponent<IDamageable>();
enemy?.TakeDamage(50f);
}
// 에디터 시각화
#if UNITY_EDITOR
ExtDebug.DrawBox(attackCenter, attackBox * 0.5f, transform.rotation, Color.red, 0.5f);
#endif
}
}

5. Physics.ComputePenetration — 겹침 해소

섹션 제목: “5. Physics.ComputePenetration — 겹침 해소”
// 두 콜라이더가 겹쳐있을 때 분리 벡터 계산
public class PenetrationResolver : MonoBehaviour
{
void Update()
{
var col = GetComponent<Collider>();
var hits = Physics.OverlapBox(
transform.position, transform.localScale * 0.5f,
transform.rotation);
foreach (var other in hits)
{
if (other == col) continue;
if (Physics.ComputePenetration(
col, transform.position, transform.rotation,
other, other.transform.position, other.transform.rotation,
out Vector3 direction, out float distance))
{
// 겹침 해소
transform.position += direction * distance;
}
}
}
}

public class FieldOfView : MonoBehaviour
{
[SerializeField] private float viewRadius = 10f;
[SerializeField, Range(0, 360)] private float viewAngle = 90f;
[SerializeField] private LayerMask targetLayer;
[SerializeField] private LayerMask obstacleMask;
private readonly Collider[] _targets = new Collider[20];
public List<Transform> FindVisibleTargets()
{
var visible = new List<Transform>();
int count = Physics.OverlapSphereNonAlloc(
transform.position, viewRadius, _targets, targetLayer);
for (int i = 0; i < count; i++)
{
Transform target = _targets[i].transform;
Vector3 dirToTarget = (target.position - transform.position).normalized;
// 시야각 체크
if (Vector3.Angle(transform.forward, dirToTarget) > viewAngle * 0.5f)
continue;
float dist = Vector3.Distance(transform.position, target.position);
// 장애물 체크 (레이캐스트)
if (!Physics.Raycast(transform.position, dirToTarget, dist, obstacleMask))
visible.Add(target);
}
return visible;
}
}

// ✅ NonAlloc 버전 사용 (GC 제로)
Physics.OverlapSphereNonAlloc(pos, r, buffer);
// ✅ LayerMask로 검색 범위 최소화
int mask = LayerMask.GetMask("Enemy");
// ✅ QueryTriggerInteraction.Ignore로 트리거 제외
Physics.Raycast(pos, dir, out hit, dist, mask, QueryTriggerInteraction.Ignore);
// ✅ 매 프레임이 아닌 주기적 실행
private float _checkInterval = 0.1f;
private float _nextCheck;
void Update() {
if (Time.time < _nextCheck) return;
_nextCheck = Time.time + _checkInterval;
CheckForEnemies();
}
// ❌ 피해야 할 패턴
Physics.OverlapSphere(pos, r); // 매 호출 GC 할당

쿼리용도
Raycast단일 광선 충돌 감지
RaycastAll / NonAlloc경로 상 모든 충돌
SphereCast구체 형태 레이캐스트
CapsuleCast캡슐 형태 레이캐스트
OverlapSphere범위 내 모든 콜라이더
OverlapBox박스 범위 감지
ComputePenetration겹침 분리 벡터 계산