유니티 버전 - 2022.3.48f1
목차
- RaycastAll과 RaycastNonAlloc의 차이
- RaycastNonAlloc 이용
- RaycastNonAlloc 응용
- 마치며
RaycastAll과 RaycastNonAlloc의 차이
RaycastAll과 RaycastNonAlloc는 둘 다 Ray를 사용하여 오브젝트와의 충돌을 감지하지만, 성능과 메모리 사용 측면에서 차이가 있습니다
구분 | RaycastAll | RaycastNonAlloc |
메모리 할당 | 매 호출 시마다 새로운 배열 생성 | 미리 정의된 배열 사용으로 메모리 최소화 |
GC의 부담 | 높음 | 낮음 |
반환 오브젝트 | 충돌한 모든 오브젝트의 정보 반환 | 배열 크기 내에서의 충돌 정보만 반환 (나머지는 버려짐) |
적합한 상황 | 가끔 호출되고, 충돌시의 모든 정보가 필요할때 | 빈번히 호출되고, 최적화가 중요할 때 |
위를 근거로 생각해보자면 성능최적화를 위해 RaycastNonAlloc를 사용하면서, 얼마나 많은 충돌체가 한번에 충돌 하는지최대치를 예측하여 배열의 크기를 설정할 것인지 고민을 해봐야 할 것 같습니다
또, RaycastNonAlloc가 호출될때마다 기존에 배열에 존재하던 값은 삭제(덮어씌워짐)되어 최신 값으로 업데이트됩니다
RaycastNonAlloc 이용
먼저 Ray를 쏴서 경로상의 충돌체의 이름을 가져와보겠습니다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestRay : MonoBehaviour
{
private Ray _ray;
private RaycastHit[] _hits = new RaycastHit[5];
[SerializeField] private float _distance = 10f;
public LayerMask layerMask;
private void Start()
{
_ray = new Ray(transform.position, transform.forward);
// Ray의 시작점과 방향 초기화
}
private void Update()
{
int hitCount = Physics.RaycastNonAlloc(_ray, _hits, _distance, layerMask);
if (hitCount > 0)
{
for (int i = 0; i < hitCount; i++)
{
Debug.Log(_hits[i].collider.gameObject.name);
}
}
}
private void OnDrawGizmos()
{
Debug.DrawRay(transform.position, transform.forward * _distance, Color.blue);
}
}
해당 스크립트에서 Physics.RaycastNonAlloc의 설명입니다
int hitCount = Physics.RaycastNonAlloc(_ray, _hits, _distance, layerMask);
int A = Physics.RaycastNonAlloc(B, C, D, E);
// 변수와 알파벳은 1:1대응
A : 충돌된 오브젝트들의 개수를 나타냄
B : 미리 정의한 Ray 객체, 시작 지점과 나아가는 방향을 나타냄
C : 충돌 정보를 저장할 RaycastHit[] 타입의 배열, 충돌한 오브젝트들의 정보를 여기에 저장
D : Ray의 최대 거리
E : 감지할 레이어를 필터링, 지정된 레이어에 속한 오브젝트와의 충돌만을 감지하고 나머지는 무시함
위 스크립트를 이용하여 배열의크기가 5이고 오브젝트가 4개라고 가정했을 때 디버그를 살펴보겠습니다
오브젝트의 개수가 배열의 크기보다 작아 네 개의 큐브 정보 모두를 담을 수 있었습니다, 하지만 디버그를 보니 레이에 맞은 첫번째 오브젝트의 이름이 먼저 나오는 것이 아닌 가장 뒤쪽의 오브젝트 부터 순서대로 배열에 들어간것을 확인 할 수 있었습니다
다음은 배열의 크기가 5이고 오브젝트가 6개라고 가정했을 때 디버그를 살펴보겠습니다
오브젝트의 개수가 배열보다 크기때문에 배열에 모든 오브젝트들의 정보를 담을 수 없었고, 디버그를 보니 중간에 있는 EnemyCube (1)의 정보가 탈락되었습니다, 또 이번에는 뒤에서 부터 정보가 담긴 것이 아니라 순서대로 가다가 마지막에 앞뒤가 바뀌어 저장된 것을 확인 할 수 있었습니다
배열의 크기가 작아서 무시되는 오브젝트가 발생될 때, 이 오브젝트를 정해주는 것은 유니티에서 알아서 판단합니다
RaycastNonAlloc 응용
위에 보셨던 것 처럼 배열에 정보가 담길때, 아래 디버그를 쭉 살펴보면 규칙이 있지만서도 예측 할 수 없는 순서로 배열에 들어가는 것을 확인 할 수 있었습니다, 이번에는 배열에 담긴 오브젝트들의 정보를 정렬해보겠습니다
바뀐 부분만 설명하겠습니다, 먼저 Update문이 아니라 Start()에서 한번만 실행하도록 바꾸었습니다
private void TestRayOneShot()
{
int hitCount = Physics.RaycastNonAlloc(_ray, _hits, _distance, layerMask);
if (hitCount > 0)
{
Array.Sort(_hits, 0, hitCount, Comparer<RaycastHit>.Create((RaycastHit x, RaycastHit y) => x.distance.CompareTo(y.distance)));
}
for (int i = 0; i < _hits.Length; i++)
{
Debug.Log(_hits[i].collider.gameObject.name);
}
}
정렬을 해주는 로직입니다
Array.Sort(_hits,
0,
hitCount,
Comparer<RaycastHit>.Create((RaycastHit x, RaycastHit y) => x.distance.CompareTo(y.distance)));
Array.Sort(A,
B,
C,
D)
// 변수와 알파벳은 1:1대응
A : 정렬할 배열입니다
B : 정렬을 시작할 인덱스를 지정해줍니다, 0번째 요소부터 정렬을 시작합니다
C : 정렬을 할 요소가 몇개인지 정해줍니다, 만약 배열의 크기가 5인데 Ray에 반환된 오브젝트의 정보가 3개라면 배열의 나머지 두 칸은 빈 값입니다, 정렬을 하는데 있어 어떤 영향을 줄지 모르기때문에 전체 배열이 아닌 실제 충돌한 오브젝트의 개수만큼만 B번째 요소(여기서는 0) 에서부터 정렬하도록 하였습니다
D : 비교하고 정렬하는 방법입니다
Comparer<RaycastHit>
RaycastHit을 이용하여 비교한다는 것 입니다
.Create()
어떻게 비교할지를 ()안에 정의합니다
(RaycastHit x, RaycastHit y) =>
배열에 저장된 요소(RaycastHit)의 x와 y를 매개변수로 받아 => 다음을 실행합니다, 여기서 x는 배열의 앞에 있는 요소이고, y는 뒤에 있는요소입니다, 예를 들면 N번째의 비교에서 x는 배열의 [0] 요소이고, y는 배열의 [1]요소입니다, 비교를 마친 후 x는 배열의 [1] 요소가 되고, y는 배열의[2] 요소가 됩니다
x.distance.CompareTo(y.distance)
Ray에 먼저 맞은 쪽의 좌표가 Ray의 원점과 더 가까울 것이기때문에, x와 y중 원점에 가까웠던 요소의 distance가 더 작을 것입니다, 이를 바탕으로 x의 distance와 y의 distance를 CompareTo를 이용하여서 더 가까웠던 오브젝트가 배열에서 앞쪽에 정렬될 수 있도록 합니다
결과도 살펴보겠습니다
아까의 결과와 다르게 EnemyCube가 Ray의 원점에 가까운 순서대로(4 - 5 - 0 - 2 - 3) 정렬되었음을 확인할 수 있었습니다, 물론 EnemyCube의 개수가 배열의 최대크기(5)를 초과하기때문에 EnemyCube(1)의 정보가 탈락되었습니다
마치며
RaycastNonAlloc와 정렬을 사용한다면 관통 공격을 구현할때 적을 지날때마다 대미지를 감소시킨다거나, Ray와의 거리를 이용하여 우선적으로 동작이나 이벤트를 처리하도록 할 수 있습니다
아래는 유니티 공식 Raycast 관련 영상입니다
이상입니다
'코드 및 공부 > 물리' 카테고리의 다른 글
Physics.Raycast (0) | 2024.10.29 |
---|