트레이스 소스 필터링을 구현하는 방법

트레이스 소스 필터링을 구현하여 애니메이션 인사이트 트레이싱을 최적화하는 방법을 알아봅니다.

Choose your operating system:

Windows

macOS

Linux

필요한 사전지식

이 글은 다음 주제에 대한 지식이 있는 분들을 대상으로 합니다. 계속하기 전 확인해 주세요.

이 가이드에서는 두 가지 예시를 구현하면서 트레이스 소스 필터링을 프로젝트에 사용하는 방법에 대해 알아보겠습니다. 예를 들어 두 번째 사용 사례를 구현하면 트레이스 소스 필터링 시스템은 플레이어 폰의 레이캐스트가 다음 영상에서 스탠딩 액터에 부딪힐 때 액터를 필터링하여 가져올 것입니다.

액터를 어떻게 걸러서 가져오는지 보세요

필수 구성

이 하우투에서는 게임(Games) > 3인칭 템플릿(Third Person Template) 프로젝트를 C++ , 시작용 콘텐츠 없음(No Starter Content) 이 활성화된 상태로 사용했으며, 에픽게임즈 마켓플레이스(Epic Games Marketplace) 에서 사용 가능한 애니메이션 스타터 팩(Animation Starter Pack) 을 사용했습니다.

  1. 스탠딩 스켈레탈 메시 액터와 앉아 있는 스켈레탈 메시 액터를 씬에 각각 하나씩 추가합니다.

    Equip_Rifle_Standing을 오른쪽에, Crouch_Idle_Rifle_Ironsights를 왼쪽에 사용했습니다.

  2. 스탠딩 스켈레탈 메시 액터를 선택하고 Standing 액터 태그로 태그합니다.

  3. 마지막 단계를 반복하여 앉아 있는 스켈레탈 메시 액터도 태그합니다.

    HT_ReqSetup_2.png

트레이스 소스 필터링

개요

대규모 프로젝트에서 애니메이션 인사이트로 작업할 때는 애플리케이션의 오버헤드와 디스크 공간 사용량을 제한하는 것이 필수적입니다. 다음 예시에서는 애니메이션 인사이트 출력이 씬 내의 모든 액터에 대한 데이터를 트레이스합니다.

프로젝트가 수천 개의 오브젝트를 포함할 정도로 커진다면 퍼포먼스 문제를 파악하는 것이 쉽지 않을 것입니다.

애니메이션 인사이트 트레이스 데이터의 양을 줄이기 위해 트레이스 소스 필터링을 사용하여 어떤 게임플레이 오브젝트가 트레이스 데이터를 출력할지 선택합니다. 트레이스 소스 필터링을 활성화하려면 다음 단계를 따릅니다.

  1. 애니메이션 인사이트(Animation Insights) 에서 메뉴(Menu) > (필터링) 트레이스 소스 필터링(Trace Source Filtering) 을 선택합니다.

    HT_TraceSourceFiltering_1.png

  2. 트레이스 소스 필터링(Trace Source Filtering) 에서 옵션(Options) > 시각화(Visualize) > 액터 필터링(Actor Filtering) 을 선택합니다.

    HT_TraceSourceFiltering_2.png

  3. 트레이스 소스 필터링이 에디터 내에서 실행되는지 확인하려면 액티브 레벨 에디터 뷰포트(PIE 세션)에서 레벨을 플레이합니다. 다음이 나타나야 합니다.

    트레이스 소스 필터링이 씬 내의 모든 액터 주변 박스를 렌더링하고 있습니다.

  4. 트레이스 소스 필터를 구현하려면 Options(옵션) > Visualize(시각화) > 걸러진 액터만(Only Actor(s) passing Filtering) 을 선택합니다.

    HT_TraceSourceFiltering_3.png

PIE 세션에서 레벨을 플레이하고 '액터 필터링'과 '걸러진 액터만'을 모두 활성화하면 트레이스 소스 필터링이 씬 내의 모든 액터에 대한 시각화를 제공합니다. 아직 필터를 구현하지 않았기 때문입니다.

HT_TraceSourceFiltering_EndResult.png

트레이스 소스 필터링이 활성화된 애니메이션 인사이트

이제 트레이스 소스 필터링을 활성화했으니 필터를 구현하여 애니메이션 인사이트가 출력할 트레이스 데이터의 양을 제한할 수 있습니다. 이 가이드의 나머지 부분은 몇 가지 사용 사례와 함께 각 사용 사례에 대한 블루프린트 및 C++(네이티브) 필터 구현을 보여줍니다.

사용 사례 1

플레이어 폰의 위치에서 지정된 거리 내에 위치한 태그된 액터만 거르는 필터를 구현하려 한다고 가정하겠습니다. 이 사용 사례를 염두에 두고 다음과 같이 필터를 구현합니다.

구현 유형

필터 이름

사용

블루프린트

HasTag

정의된 액터 태그가 있는 액터를 필터링하여 가져옵니다.

네이티브

DistanceToPlayer

플레이어 폰 위치에서 지정된 거리 내에 위치한 액터를 필터링하여 가져옵니다. 기본 거리를 300 언리얼 유닛(cm)으로 정의합니다.

필터를 구현한 다음에는 이를 통합하여 태그된 액터가 플레이어 폰에서 지정된 거리 이내에 있는 경우 액터를 걸러서 가져오고 애니메이션 인사이트가 그 트레이스 데이터를 녹화할 수 있게 합니다. 이 섹션 마지막에는 필터가 다음과 같은 필터 로직으로 사용 사례의 요구 조건을 충족하게 됩니다.

필터 로직

True 반환

False 반환

DistanceToPlayer AND HasTag

필터 인 액터스

필터 아웃 액터스

필터 로직을 정의할 때 요구 조건을 완성된 문장으로 써 보는 것이 좋습니다. 구현에 앞서 로직의 잠재적인 결함을 찾아내는 데 도움이 되기 때문입니다.

HasTag 블루프린트 필터

  1. 트레이스 소스 필터링(Trace Source Filtering) 대화 상자에서 필터 추가(Add Filter) > 새 필터 블루프린트 생성(Create new Filter Blueprint) 을 선택합니다.

    HT_HasTagBPFilter_1.png

  2. 새 이름으로 애셋 저장(Save Asset As) 메뉴가 나타나면 '/Game/' 아래에 필터BP(FilterBP) 폴더를 새로 생성합니다.

    HT_HasTagBPFilter_2.png

  3. 에셋을 '/Game/FilterBP/'에 태그 있음(HasTag) 으로 저장합니다.

  4. HasTag 블루프린트 에디터에서 함수(Functions) > (함수) 오버라이드(Override) > 액터의 필터 통과 여부(Does Actor Pass Filter) 를 선택합니다.

    HT_HasTagBPFilter_3.png

  5. 다음 디테일로 새 변수를 추가합니다.

    • 변수 이름: 액터태그(ActorTag)

    • 변수 유형: 스트링(String)

    • 편집 가능 인스턴스: 활성화됨(Enabled)

    • 툴팁: 액터 태그 이름(Actor Tag Name)

  6. 기본값을 정의하려면 블루프린트를 컴파일합니다.

  7. 디테일 패널에서 기본값을 'ACTOR TAG'로 정의합니다.

    HT_HasTagBPFilter_4.png

  8. 액터의 필터 통과 여부(Does Actor Pass Filter) 함수를 다음 블루프린트로 정의합니다.

    이미지를 클릭하면 확대됩니다.

  9. 'My Blueprint(내 블루프린트)' 패널의 'Trace Source Filtering(트레이스 소스 필터링)' 카테고리에서 툴팁 텍스트 가져오기(Get Tool Tip Text) 함수를 오른쪽 클릭하고 함수 구현(Implement Function) 을 선택합니다.

    HT_HasTagBPFilter_6.png

  10. 툴팁 텍스트 가져오기(Get Tool Tip Text) 함수를 다음 블루프린트로 정의합니다.

    이미지를 클릭하면 확대됩니다.

    텍스트 현지화

  11. 디스플레이 텍스트 가져오기(Get Display Text) 함수를 다음 블루프린트로 정의합니다.

    이미지를 클릭하면 확대됩니다.

  12. HasTag 블루프린트 에디터를 컴파일 및 저장 후 닫아서 필터 영역에서 HasTag 블루프린트 필터를 봅니다.

    이미지를 클릭하면 확대됩니다.

    필터 추가(Add Filter)

  13. 액터 태그 이름을 정의하려면 HasTag 필터를 클릭하고 'ACTOR TAG'를 `Crouching`으로 변경합니다.

    HasTag 필터

  14. 액터를 필수 구성 섹션에 제대로 태그했는지 확인하려면 PIE 세션을 실행합니다.

    'Crouching(앉아 있는)'으로 태그한 왼쪽 액터가 PIE 세션 도중에 걸러져 들어옵니다.

    HasTag 블루프린트 필터가 예상대로 작동하는지 확인했다면 DistanceToPlayer 네이티브 필터를 구현할 준비가 된 것입니다.

    HasTag가 액터를 걸러서 가져오지 않는다면 액터 태그 이름이 'Actor Tag Name(액터 태그 이름)' 변수에 할당된 이름과 같은지 확인합니다. 이름이 같다면 블루프린트에서 버그를 검수합니다.

DistanceToPlayer 네이티브 필터

  1. 빈 C++ 클래스를 프로젝트에 추가하고 `DistanceToPlayer`로 이름을 붙입니다.

  2. 프로젝트의 '*.Build.cs' 파일에서 'SourceFilteringTrace', 'SourceFilteringCore', 'SourceFilteringEditor' 퍼블릭 디펜던시 모듈 이름을 추가합니다.

    // 저작권 1998-2019 에픽게임즈 판권 소유.
    
    using UnrealBuildTool;
    
    public class TraceSourceFilterHT : ModuleRules
    {
        public TraceSourceFilterHT(ReadOnlyTargetRules Target) : base(Target)
        {
            PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
    
            PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "SourceFilteringTrace", "SourceFilteringCore", "SourceFilteringEditor" });
        }
    }
  3. 'DistanceToPlayer.h'에 다음 코드를 추가합니다.

    // 프로젝트 세팅의 설명 페이지에 저작권 통지를 채워넣으세요.
    
    #pragma once
    
    #include "DataSourceFilter.h"
    #include "DistanceToPlayer.generated.h"
    
    UCLASS(NotBlueprintable)
    class UDistanceToPlayer : public UDataSourceFilter
    {
        GENERATED_BODY()
            UDistanceToPlayer();
    public:
        UPROPERTY(Category = Filtering, EditAnywhere)
            float Distance;
    protected:
        virtual bool DoesActorPassFilter_Internal(const AActor* InActor) const override;
        virtual void GetDisplayText_Internal(FText& OutDisplayText) const override;
    };
  4. 'DistanceToPlayer.cpp'에 다음 코드를 추가합니다.

    // 프로젝트 세팅의 설명 페이지에 저작권 통지를 채워넣으세요.
    
    #include "DistanceToPlayer.h"
    #include "GameFramework/PlayerController.h"
    #include "GameFramework/Pawn.h"
    #include "Engine/World.h"
    
    #define LOCTEXT_NAMESPACE "UDistanceToPlayer"
    
    /**
    Set initial distance to 300 Unreal Units (cm).
    */
    UDistanceToPlayer::UDistanceToPlayer() : Distance(300.0f)
    {
    }
    
    /**
    액터와 플레이어 폰 사이의 거리를 확인합니다.
    */
    bool UDistanceToPlayer::DoesActorPassFilter_Internal(const AActor* InActor) const
    {
        if (InActor)
        {
            UWorld* World = InActor->GetWorld();
            if (World)
            {
                if (APlayerController* Controller = World->GetFirstPlayerController())
                {
                    if (APawn* Pawn = Controller->GetPawn())
                    {
                        /**Return true if Actor is less than the distance threshold to our Player Pawn.*/
                        return Pawn->GetDistanceTo(InActor) < Distance;
                    }
                }
            }
        }
        return false;
    }
    
    /**
    플레이어는 거리 값을 커스터마이징할 수 있습니다.
    */
    void UDistanceToPlayer::GetDisplayText_Internal(FText& OutDisplayText) const
    {
        OutDisplayText = FText::Format(LOCTEXT("DisplayText", "Distance to Player Pawn < {0}"), { Distance });
    }
    #undef LOCTEXT_NAMESPACE // "UDistanceToPlayer"
  5. 필터 추가(Add Filter) 드롭다운에서 코드를 컴파일하여 플레이어와의 거리(DistanceToPlayer) 필터를 봅니다.

    HT_DistanceToPlayerNativeFilter_1.png

  6. 새 네이티브 필터를 필터 영역에 추가하려면 플레이어와의 거리(DistanceToPlayer) 를 선택합니다.

  7. Crouching 태그를 오른쪽 클릭하고 필터 제거(Remove Filter) 를 선택하여 HasTag 블루프린트 필터를 필터 영역에서 제거합니다.

    DistanceToPlayer 필터

  8. DistanceToPlayer 필터를 검증하려면 PIE 세션을 실행합니다.

    플레이어 폰에서 300 언리얼 유닛(cm) 내에 있는 액터가 걸러져 들어옵니다.

DistanceToPlayer 네이티브 필터가 예상대로 작동하는지 확인했다면 두 필터를 결합하여 사용 사례의 요구 조건을 충족할 준비가 된 것입니다.

사용 사례 1 구현하기

사용 사례의 요구 조건을 다시 살펴보겠습니다.

필터 로직

False 반환

True 반환

DistanceToPlayer AND HasTag

필터 인 액터스

필터 아웃 액터스

이 요구 조건을 충족하려면 다음 단계를 따릅니다.

  1. 태그 있음(HasTag) 필터를 필터 영역에 추가합니다.

  2. 액터 태그 이름 변수를 `Standing`으로 설정합니다.

  3. 필터 영역에서 태그 있음(HasTag) 플레이어와의 거리(DistanceToPlayer) 옆으로 드래그합니다.

    HasTag 드래그하기

  4. 거리(Distance) 변수의 기본값을 300에서 400으로 변경합니다.

    거리 변수 변경하기

    필터 세트를 구성할 때 AND, OR, NOT 논리 연산자를 사용할 수 있습니다. 다른 것을 선택하려면 논리 연산자를 오른쪽 클릭합니다.

    HT_DistanceToPlayerNativeFilter_4.png

  5. 필터 로직을 검증하려면 PIE 세션을 실행합니다.

플레이어 폰이 'Standing Actor Tag(스탠딩 액터 태그)'로 태그된 액터와 400cm 이내 거리로 들어가면 트레이스 소스 필터링 시스템이 액터를 걸러서 가져옵니다.

플레이어 폰이 거리 내에 있을 때 애니메이션 인사이트(왼쪽 하단 패널)가 어떻게 Equip_Pistol_Standing - 웨이트 블렌드(및 그래프)만 트레이스하는지 보세요.

사용 사례 2

씬 안에서 움직일 때 플레이어 폰의 레이캐스트에 부딪히거나 충돌하는 태그된 스켈레탈 메시 액터만 거르는 필터를 구현하려 한다고 가정하겠습니다. 이 사용 사례를 염두에 두고 다음과 같이 필터를 구현합니다.

구현 유형

필터 이름

사용

블루프린트

IsOfClass

지정된 클래스에 속한 액터를 필터링하여 가져옵니다. 기본 클래스는 스켈레탈 메시 액터로 정의합니다.

네이티브

레이캐스트

플레이어 폰의 레이캐스트에 부딪히는 태그된 액터를 걸러서 가져옵니다. 기본 러이 길이를 400cm으로 정의합니다.

필터를 구현한 다음에는 이를 통합하여 태그된 스켈레탈 매시 액터가 플레이어 폰의 레이캐스트에 부딪힐 경우 액터를 걸러서 가져오고 애니메이션 인사이트가 그 트레이스 데이터를 녹화할 수 있게 합니다. 이 섹션 마지막에는 필터가 다음과 같은 필터 로직으로 사용 사례의 요구 조건을 충족하게 됩니다.

필터 로직

True 반환

False 반환

RaycastActor AND IsOfClass

필터 인 액터스

필터 아웃 액터스

IsOfClass 블루프린트 필터

  1. 트레이스 소스 필터링(Trace Source Filtering) 대화 상자에서 필터 추가(Add Filter) > 새 필터 블루프린트 생성(Create new Filter Blueprint) 을 선택합니다.

  2. 에셋을 '/Game/FilterBP/'에 'IsOfCass'로 저장합니다.

  3. IsOfClass 블루프린트 에디터에서 함수(Functions) > (함수) 오버라이드(Override) > 액터의 필터 통과 여부(Does Actor Pass Filter) 를 선택합니다.

  4. 다음 디테일로 새 변수를 추가합니다.

    • 변수 이름: 액터 클래스 레퍼런스(ActorClassRef)

    • 변수 유형: 액터 클래스 레퍼런스(Actor Class Reference)

    • 편집 가능 인스턴스: 활성화됨

    • 툴팁: 액터 클래스 레퍼런스

  5. 기본값을 정의하려면 블루프린트를 컴파일합니다.

  6. 기본값을 스켈레탈 메시 액터(SkeletalMeshActor) 로 정의합니다.

    HT_IsOfClassBP_1.png

  7. Does Actor Pass Filter(액터의 필터 통과 여부) 함수를 다음 블루프린트로 정의합니다.

  8. 'My Blueprint(내 블루프린트)' 패널의 트레이스 소스 필터링(Trace Source Filtering) 카테고리에서 툴팁 텍스트 가져오기(Get Tool Tip Text) 함수를 오른쪽 클릭하고 함수 구현(Implement Function) 을 선택합니다.

  9. 툴팁 텍스트 가져오기(Get Tool Tip Text) 함수를 다음 블루프린트로 정의합니다.

  10. 디스플레이 텍스트 가져오기(Get Display Text) 함수를 다음 블루프린트로 정의합니다.

  11. HasTag 블루프린트 에디터를 컴파일 및 저장 후 닫아서 필터 영역에서 IsOfClass 블루프린트 필터를 봅니다.

  12. IsOfClass 필터를 검증하려면 PIE 세션을 실행합니다.

    스켈레탈 메시 액터가 PIE 세션 도중 걸러져 들어옵니다.

IsOfClass 블루프린트 필터가 예상대로 작동하는지 확인했다면 RaycastActor 네이티브 필터를 구현할 준비가 된 것입니다.

RaycastActor 네이티브 필터

  1. 빈 C++ 클래스를 프로젝트에 추가하고 `RaycastActor`로 이름을 붙입니다.

  2. 'RaycastActor.h'에 다음 코드를 추가합니다.

    // 프로젝트 세팅의 설명 페이지에 저작권 통지를 채워넣으세요.
    
    #pragma once
    
    #include "DataSourceFilter.h"
    #include "RaycastActor.generated.h"
    
    UCLASS(NotBlueprintable)
    class URaycastActor : public UDataSourceFilter
    {
        GENERATED_BODY()
            URaycastActor();
    
    private:
        UPROPERTY(Category = Filtering, EditAnywhere)
            FName ActorTagName;
    
        UPROPERTY(Category = Filtering, EditAnywhere)
            float RayLength;
    
    protected:
        virtual bool DoesActorPassFilter_Internal(const AActor* InActor) const override;
        virtual void GetDisplayText_Internal(FText& OutDisplayText) const override;
    };
  3. 'RaycastActor.cpp'에 다음 코드를 추가합니다.

    // 프로젝트 세팅의 설명 페이지에 저작권 통지를 채워넣으세요.
    
    #include "RaycastActor.h"
    #include "GameFramework/PlayerController.h"
    #include "GameFramework/Pawn.h"
    #include "Engine/World.h"
    #include "DrawDebugHelpers.h"
    
    #define LOCTEXT_NAMESPACE "URaycastActor"
    
    /**
    초기 거리를 400 언리얼 유닛(cm)으로 정의합니다.
    */
    URaycastActor::URaycastActor() : ActorTagName("ADD ACTOR TAG"), RayLength(400.0f)
    {
    }
    
    /**
    플레이어 폰의 레이캐스트가 태그된 액터에 부딪힙니다.
    */
    bool URaycastActor::DoesActorPassFilter_Internal(const AActor* InActor) const
    {
        if (InActor)
        {
            UWorld* World = InActor->GetWorld();
            if (World)
            {
                if (APlayerController* Controller = World->GetFirstPlayerController())
                {
                    if (APawn* Pawn = Controller->GetPawn())
                    {
                        FHitResult* HitResult = new FHitResult();
                        FVector StartTrace = Pawn->GetActorLocation();
                        FVector ForwardVector = Pawn->GetActorForwardVector();
                        FVector EndTrace = (ForwardVector * RayLength) + StartTrace;
                        FCollisionQueryParams* CollisionQuery = new FCollisionQueryParams();
                        World->LineTraceSingleByChannel(*HitResult, StartTrace, EndTrace, ECC_Visibility, *CollisionQuery);
                        DrawDebugLine(World, StartTrace, EndTrace, FColor(0, 255, 0), false, 0.0f);
    
                        /**Return true if Player Pawn's Ray hits a tagged Actor.*/
                        if (HitResult->GetActor() != NULL && HitResult->GetActor()->ActorHasTag(ActorTagName))
                        {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
    
    /**
    플레이어는 다음을 커스터마이징할 수 있습니다.
    * - 찾을 액터 태그 이름
    * - 레이 길이
    */
    void URaycastActor::GetDisplayText_Internal(FText& OutDisplayText) const
    {
        OutDisplayText = FText::Format(LOCTEXT("TraceSourceText", "Hit {0} Actor with Ray Length {1}"), FText::FromName(ActorTagName), RayLength);
    }
    #define LOCTEXT_NAMESPACE // "URaycastActor"
  4. 필터 추가(Add Filter) 드롭다운에서 코드를 컴파일하여 RaycastActor 필터를 봅니다.

    HT_RaycastActor_1.png

  5. 'is a SkeletalMeshActor filter(스켈레탈 메시 액터 여부 필터)'를 오른쪽 클릭하고 필터 제거(Remove Filter) 를 클릭하여 IsOfClass 블루프린트 필터를 필터 영역에서 제거합니다.

  6. 새 네이티브 필터를 필터 영역에 추가하려면 레이캐스트 액터(RaycastActor) 를 선택합니다.

  7. 액터 태그 이름을 정의하려면 Rayfilter 필터를 클릭하고 'ADD ACTOR TAG'를 `Standing`으로 변경합니다.

    RaycastActor 필터

  8. RaycastActor 필터를 검증하려면 PIE 세션을 실행합니다.

    스탠딩 액터이면서 플레이어 폰의 레이캐스트에 부딪히는 모든 액터를 걸러서 가져옵니다.

RaycastActor 네이티브 필터가 예상대로 작동하는지 확인했다면 두 필터를 결합하여 사용 사례의 요구 조건을 충족할 준비가 된 것입니다.

사용 사례 2 구현하기

사용 사례의 요구 조건을 다시 살펴보겠습니다.

필터 로직

True 반환

False 반환

RaycastActor AND IsOfClass

필터 인 액터스

필터 아웃 액터스

  1. IsOfClass 필터를 필터 영역에 추가합니다.

  2. 필터 영역에서 IsOfClass 를 RaycastActor 옆으로 드래그합니다.

    IsOfClass 필터 드래그하기

  3. 필터 로직을 검증하려면 PIE 세션을 실행합니다.

플레이어 폰의 레이캐스트가 'Standing Actor Tag(스탠딩 액터 태그)'로 태그된 액터와 부딪히면 트레이스 소스 필터링 시스템이 스켈레탈 메시 액터를 걸러서 가져옵니다.

플레이어 폰의 레이캐스트가 스탠딩 액터에 부딪혔을 때 애니메이션 인사이트(왼쪽 하단 패널)가 어떻게 Equip_Pistol_Standing - 웨이트 블렌드와 Crouch_Idle_Rifle_Ironsights - 웨이트 블렌드(및 그래프)만 트레이스하는지 보세요.

직접 해보세요!

이제 두 예시를 구현하여 트레이스 소스 필터링 사용법을 실습했으니 필요한 애니메이션 인사이트가 필요한 액터 데이터만 트레이스하도록 다른 필터를 구상해 보세요.

예를 들면 다음과 같은 필터가 가능합니다.

  • 투사체 콜리전

  • 유도 투사체 콜리전

  • 버튼 트리거

필터를 구현할 때 어떻게 논리적으로 여러 필터를 결합해야 애니메이션을 최적화하고 디버그하는 데 도움이 되는 액터 데이터만 걸러서 가져올 수 있을지 생각해 보세요. 여기서는 AND 논리 연산자만 사용했지만 OR 및 NOT 연산자도 사용하는 방법을 생각해 보세요.

언리얼 엔진 문서의 미래를 함께 만들어주세요! 더 나은 서비스를 제공할 수 있도록 문서 사용에 대한 피드백을 주세요.
설문조사에 참여해 주세요
취소