[Unity] 필수 에셋 추천 - LINQ to GameObject
본문 바로가기
프로그래밍/Unity

[Unity] 필수 에셋 추천 - LINQ to GameObject

by [아마군] 2019. 12. 31.
반응형

유니티로 개발할 때 매우 유용해서 어떤 프로젝트든 꼭 추가하는 몇개의 강력한 에셋이 있다.

 

예를 들어 본 블로그에 강좌를 올렸던 UniRx 가 대표적인 에셋이다.

 

이번 포스팅에는 UniRx 의 제작자인 NEUECC 가 올린 또하나의 꿀에셋 LINQ to GameObject 를 소개한다.

 

이 에셋은 GameObject 를 LINQ 구문과 연동해 매우 편리하게 관리할 수 있게 해주는 다양한 기능을 제공한다.

 


설치

 

 

유니티 에셋 스토어에서 LINQ to GameObject 를 검색하면 한 개의 무료 에셋이 나온다.

 

바로 이 에셋이 오늘 소개할 LINQ to GameObject 이다.

 

설명 및 이미지를 보면 알 수 있듯이 GameObject 노드 계층 구조를 검색하고 추가 하는 등의 기능을 제공하는 에셋이다.

 

일반적으로 유니티에서는 GameObject 의 노드 검색 시 Find 계열 함수를 주로 사용하게 되는데 퍼포먼스 측면이나 사용성 측면에서 아쉬운 점이 많은게 사실이다.

 

LINQ to GameObject 는 LINQ의 강력한 기능과 반복 성능 두가지 요소를 목표로 디자인 되어 있어 이런 부분을 보완해 줄 수 있다.

 

많이 쓰는 기능들 위주로 간단하게 짚어 보겠다.

 


사용

 

LINQ to GameObject 에셋을 Import 한 후 그 기능을 사용하려는 파일에 아래와 같이 namespace 사용을 선언해 준다.

using Unity.Linq;

일반적인 LINQ 의 경우 System.Linq 에 속해 있는데 반해 LINQ to GameObject 는 Unity.Linq 에 구현되어 있다.

 

이후 GameObject 객체의 함수 형태로 다양한 기능을 제공한다.

 

당연히 LINQ 기반의 구문으로 기능을 제공하므로 LINQ 에 익숙하지 않다면 우선 이에 대해 공부를 해야 할 것이다.


주요 기능 - Traverse

 

LINQ to GameObject 의 기본 개념은 GameObject 노드의 트리 축 이다.

 

현재 가지고 있는 객체를 위 이미지의 Origin 이라고 했을 때 방향별로 부모축, 형제축, 자식축, 자손축 등으로 구분하여 해당 축 방향의 노드들을 각각의 메소드로 지정하여 불러올 수 있다.

 

origin.Ancestors();   // 조상축 결과 : Container, Root
origin.Children();    // 자식축 결과 : Sphere_A, Sphere_B, Group, Sphere_A, Sphere_B
origin.Descendants(); // 자손축 결과 : Sphere_A, Sphere_B, Group, P1, Group, Sphere_B, P2, Sphere_A, Sphere_B
origin.BeforeSelf();  // 상위 형제축 결과 : C1, C2
origin.AfterSelf();   // 하위 형제축 결과 : C3, C4

위와 같이 각각의 메소드를 통해 원하는 축에 있는 오브젝트들의 컬렉션을 얻어 올 수 있다.

 

이렇게 얻어온 컬렉션은 마찬가지로 다른 LINQ 구문을 연결하여 사용할 수 있다.

 

실 사용 예는 아래와 같다.

// origin 오브젝트 하위에 이름이 "Title" 인 오브젝트 한개를 가져오는 구문
GameObject title = origin.Descendants().FirstOrDefault(x => x.name.Equals("Title"));

// root 오브젝트 하위에 tag 가 "foorbar"인 모든 오브젝트 들을 Destory 하는 구문
root.Descendants().Where(x => x.tag == "foobar").Destroy();

// origin 이 연결된 상,하위 모든 트리 내에 Clone 된 객체들을 모두 Destory 하는 구문
origin.transform.root.gameObject	
    .Descendants()					//origin 객체의 root 아래에 있는 모든 객체 리스트
    .Where(x => x.name.EndsWith("(Clone)"))
    .Destroy();

// root 오브젝트 자신 및 자식 오브젝트들에 Add되어 있는 FooScript Component 를 모두 가져온다.
var fooScripts = root.ChildrenAndSelf().OfComponent<FooScript>(); 

이처럼 Descendants나 Children 등의 객체 목록을 Transform 을 거치지 않고 GameObject 에서 직접 가져올 수 있으며 LINQ 구문을 통해 컨트롤 함으로써 직관적이고 효율적인 코드로 처리할 수 있다.

 

특히 기준 객체를 중심으로 하위의 모든 노드들을 가져오는 Descendants() 메소드를 매우 애용하게 될 것이다.

(프로젝트 특성에 따라 다르겠지만 나의 경우는 기존 gameObject.transform.Find("") 를 대체하는 위의 첫번째 예문 방식을 가장 많이 사용하고 있다.)

 

아래 표는 LINQ to GameObject 가 제공하는 트리 검색 함수 들이다.

Parent 이 GameObject의 부모 GameObject를 가져온다. 이 게임 오브젝트에 부모가 없으면 null을 반환한다.
Child 지정된 이름을 가진 첫 번째 자식 GameObject를 가져온다. 지정된 이름의 게임 오브젝트가 없으면 null을 반환한다.
Children 자식 GameObject들의 컬렉션을 반환한다.
ChildrenAndSelf 이 GameObject와 자식 GameObject들을 포함하는 컬렉션을 반환합니다
Ancestors 조상 GameObject들의 컬렉션을 반환한다.
AncestorsAndSelf 이 GameObject와 조상 GameObject들을 포함하는 컬렉션을 반환한다.
Descendants 자손 GameObject들의 컬렉션을 반환합니다.
DescendantsAndSelf 이 GameObject와 자손 GameObject들을 포함하는 컬렉션을 반환합니다.
BeforeSelf 이 GameObject 상단의 형제 GameObject들의 컬렉션을 반환합니다.
BeforeSelfAndSelf 이 GameObject와 상단의 형제 GameObject들이 포함된 컬렉션을 반환합니다.
AfterSelf 이 GameObject 하단의 형제 GameObject들의 컬렉션을 반환합니다.
AfterSelfAndSelf 이 GameObject와 하단의 형제 GameObject들이 포함된 컬렉션을 반환합니다.

주요 기능 - Operator

 

LINQ to GameObject에는 GameObject 의 추가와 파괴 등에 대한 다양한 Operator 메소드 들이 제공된다.

 

Origin 위치를 기준으로 원하는 곳에 새로운 GameObject를 간단하게 추가해 줄 수 있다.

 

실 사용 예는 아래와 같다.

 

var root = GameObject.Find("root"); 
var cube = Resources.Load("Prefabs/PrefabCube") as GameObject; 

// root 게임 오브젝트에 cube 오브젝트의 인스턴스를 새로 "clone" 해서 add 해준다.
// 반환값은 새로 add 된 GameObject 객체이다.
var clone = root.Add(cube);

// 여러개의 오브젝트들을 root 오브젝트의 형제축으로 다음 sibiling 위치에 추가한다.
var clones = root.AddAfterSelfRange(new[] { cube, cube, cube });  

// 내부적으로 null 체크 수행 후 Destory 한다.
root.Destroy();

무엇보다 Add 메소드 들의 경우 Add 시킬 각각의 GameObject 원본만 지정해 주면 GameObject.Instantiate 를 내부적으로 처리하여 클론 객체를 알아서 생성해 주기 때문에 매우 편리하게 사용할 수 있다.

 

아래 표는 Add 관련 함수 들이다.

Add GameObject/Component 를 GameObject 의 자식으로 추가한다. 타겟은 Clone 된다.
AddRange 다수의 GameObject/Component 를 GameObject 의 자식으로 추가한다. 타겟은 Clone 된다.
AddFirst GameObject/Component 를 GameObject의 첫번째 자식으로 추가한다. 타겟은 Clone 된다.
AddFirstRange 다수의 GameObject/Component 를 GameObject의 첫번째 자식으로 추가한다. 타겟은 Clone 된다.
AddBeforeSelf GameObject/Component 를 이 GameObject 앞에 추가한다. 타겟은 Clone 된다.
AddBeforeSelfRange 다수의 GameObject/Component 를 이 GameObject 앞에 추가한다. 타겟은 Clone 된다.
AddAfterSelf GameObject/Component 를 이 GameObject 뒤에 추가한다. 타겟은 Clone 된다.
AddAfterSelfRange 다수의 GameObject/Component 를 이 GameObject 뒤에 추가한다. 타겟은 Clone 된다.
Destroy  이 GameObject 를 null 체크를 거쳐 안전하게 Destroy 한다.

 

이 Add 관련 함수들과 동일한 방식의 MoveTo 함수들도 존재한다.

 

단, MoveTo 함수들의 경우 Add 함수들과 달리 대상을 Clone 하지 않고 인자로 넘겨준 GameObject 자체의 위치를 변경 시킨다.

MoveToLast GameObject/Component 를 이 GameObject 의 자식으로 이동한다. 
MoveToLastRange 다수의 GameObject/Component 를 이 GameObject 의 자식으로 이동한다. 
MoveToFirst GameObject/Component 를 이 GameObject 의 첫번째 자식으로 이동한다. 
MoveToFirstRange 다수의 GameObject/Component 를 이 GameObject 의 첫번째 자식으로 이동한다. 
MoveToBeforeSelf GameObject/Component 를 이 GameObject 앞으로 이동한다. 
MoveToBeforeSelfRange 다수의 GameObject/Component 를 이 GameObject 앞으로 이동한다. 
MoveToAfterSelf GameObject/Component 를 이 GameObject 뒤로 이동한다. 
MoveToAfterSelfRange 다수의 GameObject/Component 를 이 GameObject 뒤로 이동한다. 

Performance

 

LINQ to GameObject 는 퍼포먼스에 중점을 두고 최적화 되어 있다.

 

특히 검색 관련 함수들은 최적화된 구조체의 컬렉션을 반환함으로써 컬렉션을 수집하는 중에 가비지 발생을 최소화 하며 일부 LINQ 메소드들(First, FirstOrDefault, ToArray) 을 사용할 경우 더욱 최적화된 성능을 보여준다.

 

또한 ToArrayNonAlloc 메소드를 제공하는데 이를 사용하면 가비지를 발생 시키지 않도록 배열을 재사용할 수 있다. (Physics.RaycastNonAlloc 또는 void GetComponentsInChildren<T>(List<T> results) 등과 같은 방식)

 

GameObject[] array = new GameObject[0];

// 매 프레임마다 오브젝트의 컬렉션을 수집하지만 메모리를 새로 할당 하지 않는다.
void Update()
{
    var size = origin.Children().ToArrayNonAlloc(ref array);
    for (int i = 0; i < size; i++)
    {
        var element = array[i];
    }
}

간단한 반복이나 ForEach 또는 ToArrayNonAlloc 을 사용하는 경우 LINQ to GameObject 는 GC를 할당하지 않으며 매우 빠른 성능을 보여준다.

반응형

댓글