Unity Physics LayerMask & Raycast
Physics 쿼리 API 개요
섹션 제목: “Physics 쿼리 API 개요”Unity Physics는 씬의 Collider들을 대상으로 다양한 쿼리를 지원합니다.
| API | 형태 | 반환 |
|---|---|---|
Raycast | 직선 | 첫 번째 히트 |
RaycastAll | 직선 | 모든 히트 |
RaycastNonAlloc | 직선 | 미리 할당 배열 |
SphereCast | 구 + 직선 | 첫 번째 히트 |
OverlapSphere | 구 범위 | 범위 내 모든 Collider |
BoxCast | 박스 + 직선 | 첫 번째 히트 |
CheckSphere | 구 범위 | bool (겹침 여부) |
Raycast 기본 사용법
섹션 제목: “Raycast 기본 사용법”void Update(){ // 카메라에서 마우스 방향으로 레이 생성 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit;
// 최대 100 유닛 거리까지 레이캐스트 if (Physics.Raycast(ray, out hit, 100f)) { Debug.Log($"Hit: {hit.collider.name}"); Debug.Log($"Point: {hit.point}"); Debug.Log($"Normal: {hit.normal}"); Debug.Log($"Distance: {hit.distance}");
// 히트된 오브젝트에 접근 hit.collider.GetComponent<IInteractable>()?.Interact(); }}LayerMask 설정과 활용
섹션 제목: “LayerMask 설정과 활용”LayerMask를 사용하면 특정 레이어만 쿼리에 포함하거나 제외할 수 있습니다.
public class RaycastExample : MonoBehaviour{ [SerializeField] private LayerMask _enemyLayer; [SerializeField] private LayerMask _groundLayer;
void Update() { // Inspector에서 설정한 LayerMask 사용 if (Physics.Raycast(transform.position, Vector3.forward, out RaycastHit hit, 50f, _enemyLayer)) { hit.collider.GetComponent<Enemy>()?.TakeDamage(10); }
// 코드에서 LayerMask 생성 int mask = LayerMask.GetMask("Enemy", "Destructible"); // 또는 비트 연산 int groundMask = 1 << LayerMask.NameToLayer("Ground");
// 특정 레이어 제외: 비트 반전 int excludePlayer = ~LayerMask.GetMask("Player");
Physics.Raycast(transform.position, Vector3.down, 2f, excludePlayer); }}레이어 설정 가이드라인
섹션 제목: “레이어 설정 가이드라인”Layer 0: DefaultLayer 3: PlayerLayer 6: EnemyLayer 7: GroundLayer 8: ProjectileLayer 9: TriggerLayer 10: UI할당 없는 RaycastNonAlloc
섹션 제목: “할당 없는 RaycastNonAlloc”RaycastAll은 매 프레임 배열을 할당합니다. RaycastNonAlloc은 미리 할당된 배열을 재사용해 GC 압력을 줄입니다.
public class AoEDetector : MonoBehaviour{ // 미리 할당: 최대 20개 히트 처리 private readonly RaycastHit[] _hits = new RaycastHit[20]; private readonly Collider[] _colliders = new Collider[20];
[SerializeField] private float _detectionRadius = 5f; [SerializeField] private LayerMask _enemyMask;
void FixedUpdate() { // 원형 범위 내 모든 Collider 탐지 (할당 없음) int count = Physics.OverlapSphereNonAlloc( transform.position, _detectionRadius, _colliders, _enemyMask );
for (int i = 0; i < count; i++) { var enemy = _colliders[i].GetComponent<Enemy>(); enemy?.NotifyAlert(transform.position); } }
// 다방향 레이캐스트 (NonAlloc) void CheckMultipleDirections() { Vector3[] directions = { Vector3.forward, Vector3.left, Vector3.right };
foreach (var dir in directions) { int count = Physics.RaycastNonAlloc( transform.position, dir, _hits, 10f, _enemyMask);
for (int i = 0; i < count; i++) { Debug.Log($"Hit {_hits[i].collider.name} in direction {dir}"); } } }}SphereCast — 두께 있는 레이
섹션 제목: “SphereCast — 두께 있는 레이”// 캐릭터의 발 아래 지면 감지 (얇은 레이보다 안정적)bool IsGrounded(){ float radius = 0.3f; float distance = 0.2f; Vector3 origin = transform.position + Vector3.up * radius;
return Physics.SphereCast( origin, radius, Vector3.down, out _, distance, _groundLayer );}
// 총알처럼 두께 있는 발사체 시뮬레이션void FireProjectile(Vector3 origin, Vector3 direction){ if (Physics.SphereCast(origin, 0.05f, direction, out RaycastHit hit, 100f)) { Debug.DrawLine(origin, hit.point, Color.red, 2f); hit.collider.GetComponent<IDamageable>()?.TakeDamage(50); }}BoxCast — 박스 형태 캐스트
섹션 제목: “BoxCast — 박스 형태 캐스트”// 캐릭터 앞 공간이 비어 있는지 확인 (대시 가능 여부)bool CanDash(Vector3 direction, float distance){ Vector3 halfExtents = new Vector3(0.4f, 0.9f, 0.4f); // 캐릭터 반크기
return !Physics.BoxCast( transform.position, halfExtents, direction, out _, transform.rotation, distance, _obstacleLayer );}Debug 시각화
섹션 제목: “Debug 시각화”// 씬 뷰에서 레이캐스트 시각화void DebugRaycast(Vector3 origin, Vector3 direction, float maxDistance){ if (Physics.Raycast(origin, direction, out RaycastHit hit, maxDistance)) { Debug.DrawLine(origin, hit.point, Color.green); Debug.DrawRay(hit.point, hit.normal * 0.5f, Color.blue); Debug.DrawLine(hit.point, origin + direction * maxDistance, Color.red); } else { Debug.DrawRay(origin, direction * maxDistance, Color.yellow); }}
// OnDrawGizmos로 Overlap 범위 시각화void OnDrawGizmosSelected(){ Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(transform.position, _detectionRadius);}성능 최적화 체크리스트
섹션 제목: “성능 최적화 체크리스트”public class OptimizedDetector : MonoBehaviour{ // 1. QueryTriggerInteraction 명시 (기본값 UseGlobal은 비용 있음) void DetectWithTriggerControl() { Physics.Raycast( transform.position, Vector3.forward, out _, 100f, _enemyMask, QueryTriggerInteraction.Ignore // 트리거 무시 ); }
// 2. FixedUpdate에서 물리 쿼리 (Update보다 안정적) void FixedUpdate() { // 물리 쿼리는 FixedUpdate에서 }
// 3. 쿼리 빈도 제한 private float _checkInterval = 0.1f; private float _nextCheckTime;
void Update() { if (Time.time < _nextCheckTime) return; _nextCheckTime = Time.time + _checkInterval;
// 0.1초마다 한 번만 실행 PerformExpensiveQuery(); }
private void PerformExpensiveQuery() { /* ... */ }}| API | 용도 | GC 할당 |
|---|---|---|
Raycast | 단일 히트 | 없음 |
RaycastAll | 모든 히트 | 있음 |
RaycastNonAlloc | 모든 히트 (최적화) | 없음 |
OverlapSphere | 범위 내 Collider | 있음 |
OverlapSphereNonAlloc | 범위 내 Collider (최적화) | 없음 |
SphereCast | 두께 있는 레이 | 없음 |
NonAlloc 버전을 사용하고, LayerMask로 불필요한 Collider를 제외하며, 쿼리 빈도를 조절하면 물리 쿼리 비용을 크게 줄일 수 있습니다.