코드 및 공부/물리

RaycastAll과 RaycastNonAlloc

ekrxjvpvj0110 2024. 10. 30. 15:52

유니티 버전 - 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