Unity Physics Queries 고급 활용 가이드
Unity의 Physics Queries는 레이캐스트, 오버랩, 캐스트 쿼리를 통해 씬의 콜라이더를 검색합니다. 올바른 API를 선택하고 NonAlloc 버전을 사용하면 GC 알로케이션 없이 효율적인 충돌 감지를 구현할 수 있습니다.
1. Raycast 기본과 고급
섹션 제목: “1. Raycast 기본과 고급”// 기본 Raycastvoid 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); // 트리거 포함}2. NonAlloc — GC 없는 쿼리
섹션 제목: “2. NonAlloc — GC 없는 쿼리”// 프레임마다 호출되는 경우 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))); }}3. SphereCast / CapsuleCast
섹션 제목: “3. SphereCast / CapsuleCast”// 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}"); }}4. OverlapBox / OverlapCapsule
섹션 제목: “4. OverlapBox / OverlapCapsule”// 근접 공격 판정 — 박스 범위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; } } }}6. 시야 감지 시스템
섹션 제목: “6. 시야 감지 시스템”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; }}7. 성능 최적화 체크리스트
섹션 제목: “7. 성능 최적화 체크리스트”// ✅ 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 | 겹침 분리 벡터 계산 |