콘텐츠로 이동

Unity Physics LayerMask & Raycast

Unity Physics는 씬의 Collider들을 대상으로 다양한 쿼리를 지원합니다.

API형태반환
Raycast직선첫 번째 히트
RaycastAll직선모든 히트
RaycastNonAlloc직선미리 할당 배열
SphereCast구 + 직선첫 번째 히트
OverlapSphere구 범위범위 내 모든 Collider
BoxCast박스 + 직선첫 번째 히트
CheckSphere구 범위bool (겹침 여부)

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를 사용하면 특정 레이어만 쿼리에 포함하거나 제외할 수 있습니다.

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: Default
Layer 3: Player
Layer 6: Enemy
Layer 7: Ground
Layer 8: Projectile
Layer 9: Trigger
Layer 10: UI

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}");
}
}
}
}

// 캐릭터의 발 아래 지면 감지 (얇은 레이보다 안정적)
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);
}
}

// 캐릭터 앞 공간이 비어 있는지 확인 (대시 가능 여부)
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
);
}

// 씬 뷰에서 레이캐스트 시각화
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를 제외하며, 쿼리 빈도를 조절하면 물리 쿼리 비용을 크게 줄일 수 있습니다.