티스토리 뷰

언리얼에서는 언리얼 오브젝트(UObject)를 상속받은 파생 클래스들에 대한 선택적 클래스 스코프(Optional Class Scope)를 통한 빠른 검색을 지원하는 반복자 기능을 지원합니다. 이러한 기능을 수행하는 반복자는 크게 UObject들에 대한 순회를 지원하는 오브젝트 반복자와 AActor들에 대한 순회를 지원하는 객체 반복자입니다.

 

이러한 함수를 사용하여 모든 런타임 액터 및 오브젝트들을 검색하거나, 특정 클래스만을 검색할 수 있습니다. 이러한 기능의 제공은 사용자가 스스로 액터의 동적 배열을 유지하고 관리할 필요가 없으며, 단지 액터가 소멸(destroy)될 때 이 액터가 제거되는 것만 기억하면 됩니다.

 

액터 및 오브젝트 반복자는 항상 사용자의 게임 월드에 여전히 존재하는 모든 액터 및 오브젝트들의 정확한 리스트를 제공합니다. 그렇다면 이 두 가지 반복자에 대해서 알아보도록 하겠습니다.

 

 

 

준비


두 가지 반복자를 사용하기 위해서 먼저 다음의 헤더를 포함(include)해야 합니다 :

#include "EngineUtils.h"

 

이제 오브젝트 및 액터 반복자를 사용할 수 있습니다.

 

 

 

오브젝트 반복자


오브젝트 반복자는 UObject를 상속받은 클래스들에 대해 순회하는 반복자입니다. UObject를 상속받은 모든 클래스에 대해 순회를 수행하는 FObjectIterator 와 특정 타입 T에 대한 검색 및 순회를 수행하는 TObjectIterator / TObjectRange 로 나뉩니다.

 

일반적으로 AActor 클래스가 UObject 클래스를 상속받은 클래스인 점을 감안할 때, 오브젝트 반복자는 검색하고자하는 클래스 형식(type) T가 일반적(general)일 때(예를 들어 UObject, UObjectBase ...) 더 많은 결과를 제공하는 경향이 있습니다.

 

 

사용

- 모든 UObject 클래스 파생 클래스에 대한 오브젝트 반복자를 사용하는 예시는 다음과 같습니다(FObjectIterator의 경우):

for (FObjectIterator ObjectItr; ObjectItr; ++ObjectItr)
{
    ... // do Something
}

 

수까락의 프로그래밍 이야기의 저자 분께서 이 코드를 실행한 후 다음과 같이 언급하신 글이 있습니다 :

위 코드를 샘플 프로젝트에 넣고 테스트 해보면, 왜 Range 클래스가 없는지 이해도 간다.
별 것 없는 샘플 프로젝트에서 위 코드를 실행했더니 44000개가 넘는 로그가 출력되었다.

아주 특수한 상황에서의 디버깅이 아니면 쓸 일이 거의 없을 듯 하다.
(도대체 답을 모를 때라던가... 상식적인 방법은 다 써봤다던가... 뭐 그런 상황...)

 

- 특정 클래스에 대한 오브젝트 반복자를 사용하는 예시는 다음과 같습니다(TObjectIterator의 경우) :

// C++98 방식의 반복 순회 구현입니다.

for (TObjectIterator<USkeletalMeshComponent> ObjectItr; ObjectItr; ++ObjectItr)
{
    // 반복자에 대한 역참조 연산자 * 또는 -> 를 수행하여 객체 인스턴스에 접근할 수 있습니다.
    USkeletalMeshComponent* SKComp = *Itr;

    ... // do Something
}

 

- 좀 더 근대적인 C++ 문법에서는 foreach문을 통해서 순회를 수행할 수 있으며, 이 때 사용하는 것이 TObjectRange입니다 :

for( auto& Object : TObjectRange<USkeletalMeshComponent>())
{       
    ... // do Something
}

 

 

 

액터 반복자


액터 반복자는 AActor를 상속받은 클래스에 대해서 순회하는 반복자입니다. 모든 AActor 클래스의 파생 클래스에 대해서 순회하는 FActorIterator / FActorRange 와 특정 타입 T에 대한 검색 및 순회를 수행하는 TActorIterator / TActorRange 가 있습니다.

 

 

사용법

- 모든 AActor 클래스 파생 클래스에 대한 액터 반복자를 사용하는 예시는 오브젝트 반복자와 비슷하여 다음과 같습니다(FObjectIterator의 경우) :

for (FActorIterator ActorItr(WorldContextObject->GetWorld()); ActorItr; ++ActorItr)
{
    ... // do Something
}

 

- FObjectIterator의 ranged-for를 지원하는 FActorRange는 다음과 같이 사용됩니다 :

for(auto& Actor : FActorRange(WorldContextObject->GetWorld()))
{
    ... // do Something
}

특정 클래스에 대한 액터 반복자를 사용하는 예시 역시 오브젝트 반복자와 크게 다를 바 없습니다(TActorIterator의 경우):

```cpp
for (TActorIterator<AStaticMeshActor> ActorItr(GetWorld()); ActorItr; ++ActorItr)
{
    // 반복자에 대한 역참조 연산자 * 또는 -> 를 수행하여 객체 인스턴스에 접근할 수 있습니다.
    AStaticMeshActor *Mesh = *ActorItr;

    ... // do Something
}

 

- foreach문을 사용하는 경우에도 오브젝트 반복자와 크게 다른 부분이 없습니다(TActorRange의 경우) :

for(auto& Actor : TActorRange<AStaticMeshActor>(WorldContextObject->GetWorld()))
{
    ... // do Something
}

 

 

 

비교 및 주의사항


앞서 언급했듯이, AActor는 UObject를 상속받았기 때문에, 오브젝트 반복자는 AActor를 검색할 수 있으며, 액터 반복자는 AActor를 상속하지 않는 UObject 인스턴스를 검색할 수 없습니다.

이러한 점에서 오브젝트 반복자의 검색 범위가 훨씬 넓지만, 반면에 액터 반복자가 오브젝트 반복자의 특별한 경우에서의 Object Iterator이므로, UWorld* 컨텍스트가 주어지면 Object Iterator를 직접 사용할 때 필요로하는 액터의 세계를 확인하는 시간을 줄이는 장점 을 지니고 있습니다.

TActorIterator<AStaticMeshActor> ActorItr(GetWorld());

 

또한, 액터 반복자와 달리 오브젝트 반복자는 Pre-PIE World 및 Editor World에서 객체를 순회할 수 있습니다. 이로 인해서 예기치 않은 결과가 발생할 수 있습니다. 따라서 가능한한 ActorIterator를 사용하는 것이 좋습니다. 왜냐 하면, UE4 ObjectArray에서 PendingKill(즉, 가비지 컬렉팅을 기다리고 있음) 상태의 액터에 접근하지 못하는 것을 보장하고 액터가 호출 컨텍스트(Context, 액터 반복자를 생성할 때 제공하는 UWorld)와 동일한 세계에 있음을 보장하기 때문입니다.

 

 

 

예시


다음은 시험용으로 만든 프로젝트에서 월드 내 액터들에 대한 반복자를 돌린 것에 대한 결과입니다. 소스 코드는 다음과 같습니다(클래스가 AMyPawn인 점은 무시해주셔도 좋을 것 같습니다) :

// 월드의 모든 액터를 출력합니다
void AMyPawn::PrintAllWorldActor()
{
    int numActors = 0;
    for (TActorIterator<AActor> ActorItr(GetWorld()); ActorItr; ++ActorItr)
    {
        if (GEngine)
            GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Red, ActorItr->GetName());

        // 출력 로그에 로그를 기록합니다.
        UE_LOG(Test, Log, TEXT("%s"), *(ActorItr->GetName()));

        ++numActors;
    }

    GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Blue, FString::FromInt(numActors));
}

결과는 다음과 같습니다 :

현재 월드에 존재하는 액터에 대한 정보(실행 시 월드 아웃라이너의 모습)
코드의 결과 : 주목할 점은 28개의 액터를 기록한 것이 아닌 35개의 액터를 기록하고 있는 점이다. 아마 월드 아웃라이너에는 등록되지 않지만 월드에는 존재하는 액터가 있는 모양이다.

 

위의 PrintAllWorldActor(옳지 않은 함수 이름이라고 생각하지만 너그럽게 봐주시길 바랍니다) 함수의 내부 for문은 사실 FActorIterator를 통해서 순회한 것과 같은 결과입니다.

 

 

 

레퍼런스


Unreal Wiki - Iterators : Object & Actor Iterators, Optional Class Scope For Fast Search
수까락의 프로그래밍 이야기 - [UE4] World 내 Object / Actor 순회
Unreal Engine 4.9 Release Note

댓글