Unity DOTS/Dots Custom Manual

UNITY DOTS - EntityQuery에 대해 알아보자

개양반 2022. 7. 27.

Entity는 Component를 담는 것이자 식별ID를 가지고 있는 녀석이다. 그럼, Query란 무엇일까? 사전적의미는 문의, 의문이다. Query란 특정한 데이터를 보여달라는 요청을 말한다. 즉, 유니티 DOTS에서 EntityQuery란 특정 Component 타입을 가진 Entity리스트를 달라는 의미가 된다. 

데이터를 읽거나, 쓰려면 어떤 타입을 가진 Entity를 조회할 것인지 알아야 한다. 이때, 사용하는 것이 바로 EntityQuery라는 것이다.

 


EntityQuery 정의하기

가장 간단하게 EntitiyQuery를 정의하는 방법을 알아보자. RotationQuaternion과 RotationSpeed를 지닌 Entity를 찾는 EntityQuery를 정의해보겠다.

EntityQuery query
    = GetEntityQuery(typeof(RotationQuaternion),
                     ComponentType.ReadOnly<RotationSpeed>());

RotationQuaternion 은 쓰고 읽기가 가능하고 RotationSpeed은 읽기만 하겠다고 정의한 EntityQuery 이다. EntityQuery 역시도 정의할때 어떤 데이터를 쓰기도 할 것인지 미리 알려야 한다. 그래야 스레드끼리의 경쟁 문제가 발생하지 않는다.

 

1. EntityQueryDesc

조금 복잡한 EntityQuery의 경우는 EntityQueryDesc로 직관적으로 정리할 수 있다.

var queryDescription = new EntityQueryDesc
{
    None = new ComponentType[] { typeof(Static) },
    All = new ComponentType[]{ typeof(RotationQuaternion),
                           ComponentType.ReadOnly<RotationSpeed>() }
};
EntityQuery query = GetEntityQuery(queryDescription);

위 코드에서 None 은 해당 데이터를 포함하지 않는 Entity를 얻을 때 사용한다. All을 해당 데이터를 모두 포함된 Entity를 얻을때 사용한다. 

None 해당 데이터타입을 가지지 않는 Entity를 조회할 때 사용
Alll 해당 데이터타입을 모두 가지고 있는 Entity를 조회할 때 사용
Any 해당 데이터타입 중 1개라도 가지고 있는 Entity를 조회할 때 사용

 

2. Query 합치기

Query를 합칠 수도 있다. 합쳐진 Query는 or 연산이 되어 합친 Query 중 1개 이상 만족한 Entity를 조회하게 된다. 

var desc1 = new EntityQueryDesc
{
    All = new ComponentType[] { typeof(RotationQuaternion) }
};

var desc2 = new EntityQueryDesc
{
    All = new ComponentType[] { typeof(RotationSpeed) }
};

EntityQuery query
    = GetEntityQuery(new EntityQueryDesc[] { desc1, desc2 });

 desc1 또는 desc2를 만족하거나 둘다 만족하는 Enitity를 조회하게 된다.

 

3. 시스템 구현 내부에서 Query받기

.WithStoreEntityQueryInField(ref query) 를 사용해서 시스템 구현에서 생성한 Query를 받아올 수도 있다. 아래의 예제는 ForEach에서 조회한 ref RotationQuaternion rotation, in RotationSpeed speed를 받아오는 예제이다.

public partial class RotationSpeedSys : SystemBase
{
    private EntityQuery query;

    protected override void OnUpdate()
    {
        float deltaTime = Time.DeltaTime;

        Entities
            .WithStoreEntityQueryInField(ref query) // ForEach 에서 사용한 쿼리를 받아온다.
            .ForEach(
            (ref RotationQuaternion rotation, in RotationSpeed speed) => {
                rotation.Value
                    = math.mul(
                        math.normalize(rotation.Value),
                            quaternion.AxisAngle(math.up(),
                                speed.RadiansPerSecond * deltaTime)
                     );
            })
            .Schedule();
    }
}

 

 

4. Query options

EntityQueryDesc를 만들때 특수한 쿼리를 위한 옵션을 설정할 수 있다. 설정할 수 있는 옵션에는 총 4가지가 있다.

Default 옵션이 없는 경우다. 쿼리가 설정한 대로 Entity를 조회한다.
IncludePrefab 특별한 Prefab Tag가 포함된 Archetype을 조회한다.
IncludeDisabled 특별한 Disabled tag 가 포함된 Archetpye을 조회한다.
FilterWriteGroup Query에서 WriteGroup을 고려한다.

기본적으로 EntityQuery는 Prefab Component가 있거나, Disble(비활성) 상태의 Entity를 무시한다. Include 옵션 활성화해서 이 기본 동작을 재정의할 수 있다.

그리고 FilterWriteGroup는 조금 어려운데 WriteGroup은 ComponentData에 [WriteGroup(typeof(C1))] 이런 식으로 지정할 수가 있는데 쿼리에서 WriteGroup 들을 고려해서 조회할지 말지 설정할때 사용한다.

참고


WirteGroup은 다른 시스템을 변경할 수 없는 경우에도 한 시스템이 다른 시스템을 재정의할 수 있는 메커니즘을 제공하는 것인데 내용이 많고 복잡한 개념이다. 이 옵션을 활성화하는 상황 자체가 머리가 복잡한 상황인지라 개념도 복잡하다. 지금은 대충 이런게 있다 정도로만 이해하자.

 

4-1. Query options 예제

조금 어려울 수 있다. 예제를 들어 설명하겠다.

3개의 CompoentData가 존재한다. C2, C3는 WriteGroup으로 C1이 명시되어 있다고 하자.

public struct C1 : IComponentData { }

[WriteGroup(typeof(C1))]
public struct C2 : IComponentData { }

[WriteGroup(typeof(C1))]
public struct C3 : IComponentData { }

 

위에서 만든 ComponentData로 Entity를 만들어보자.

EntityManager.CreateEntity(typeof(C1), typeof(C2));  // entity #1
EntityManager.CreateEntity(typeof(C1), typeof(C3));  // entity #2
EntityManager.CreateEntity(typeof(C1), typeof(C2), typeof(C3));  // entity #3

 

이번에는 Entity를 조회하기 위해 EntityQueryDesc를 만들어 본다.

        var queryDescription = new EntityQueryDesc
        {
            All = new ComponentType[] { ComponentType.ReadWrite<C1>(),
                                        ComponentType.ReadOnly<C3>() },
            Options = EntityQueryOptions.FilterWriteGroup
        };
        var query = GetEntityQuery(queryDescription);

All 에 C1과 C3를 명시하고 Ontions에 EntityQueryOptions.FilterWriteGroup를 설정했다.

그럼, All에서 명시되지 않은 C2를 가진 Entity는 조회에서 제외된다. 즉, C2를 가지고 있지 않은 #2 만 데이터가 조회된다.

즉,  EntityQueryOptions.FilterWriteGroup를 사용하면 All 또는 Any에서 명시하지 않은 WriteGroup 속성을 가진 ComponentData는 조회에서 제외된다.  

댓글

💲 추천 글