본문 바로가기

Unreal Engine

Unreal Engine 에서 Delegate 사용

나중에도 쓸일이 있을 것 같아서 남겨둠

 

  • UPrimitiveComponent 의 delegate인 OnBeingOverlap, OnEndOverlap 과 같은 Dynamic Delegate에는 AddDynamic 으로 함수를 바인딩 할 수 있다.
    바인딩 되는 함수가 UFUNCTION() 이어야 하며, 블루프린트에서 노출할 수도 있다.
    // Volume에서 Overlap Event 가 발생하면, OnVolumeOverlap을 호출한다.
    AMyPostProcessVolume::AMyPostProcessVolume(const FObjectInitializer& ObjectInitializer)
    	: Super(ObjectInitializer)
    {
    
    	 GetBrushComponent()->OnComponentBeginOverlap.AddDynamic(this, &AMyPostProcessVolume::OnVolumeOverlap);
    
    }
     

Game Delegate 에 바인딩 하고 싶을 때는 AddDynamic 대신 AddRaw, AddUObject, AddStatic 등을 사용해서 바인딩 할 수 있다.

  • AddStatic : 멤버 함수 아닌 정적 함수를 바인딩 할 때 사용
  • AddRaw : UObject 파생 클래스가 아닌 C++ 클래스에 사용.
    사용하고자 하는 Delegate와 같은 시그니처를 가진 함수를 정의한 후, 이 함수를 바인딩한다.
  • AddLambda : 괄호 안의 람다를 바인딩 해 준다.

 

OnViewTargetChanged

특정 카메라가 뷰 타겟으로 플레이어 카메라에 적용되었을 때만 수행하고 싶은 동작이 있다고 하자.

View Target 이 바뀌는 타이밍을 알 수 있다면, 이 카메라를 사용할 때만 월드에서 특정 동작을 수행하고, 뷰타겟에서 빠져나갈 때는 이전 상태로 복구할 수 있다.

 

UE5 엔진 코드 중 NiagaraWorldManager.cpp 에는 다음과 같은 코드가 있다.

ViewTargetChangedHandle = FGameDelegates::Get().GetViewTargetChangedDelegate().AddLambda(
			[](APlayerController* PC, AActor* OldTarget, AActor* NewTarget)
			{
				OnRefreshOwnerAllowsScalability();
			}
		);
	}

 

그 헤더 파일에서는 ViewTargetChangedHandle을 아래와 같이 선언하였다.

 

static FDelegateHandle ViewTargetChangedHandle;

 

위 코드를 참고했을 때, ViewTarget이 변경되면 AddLambda() 안의 람다식을 실행하도록 하려면 아래와 같이 작성하면 된다는 것을 알 수 있었다.

 

FGameDelegates::Get().GetViewTargetChangedDelegate().AddLambda( /^...^/ );

 

게임 내에서 카메라 등이 뷰 타겟으로 활성회되었을 때만 수행하고 싶은 동작이 있을 때 유용하다.

(Delegate를 통해 OldViewTarget, NewViewTarget을 모두 Actor 형태로 전달받을 수 있다)

 

// in PlayerCameraManager.cpp
FGameDelegates::Get().GetViewTargetChangedDelegate().Broadcast(PCOwner, OldViewTarget, NewTarget);

 

그런데 CineCameraActor에서는 OnBecomeViewTarget, OnEndViewTarget 함수를 블루프린트에서 확장해서 사용할 수 있도록 제공하고 있다.

CineCameraActor 클래스를 상속받은 BP에서 아래 이벤트를 꺼낼 수 있다.

 

 

이 부분들은 블루프린트에서 접근할 용도로 만들어 진 것으로 보이기 때문에, 위에서 본 Delegate를 사용하는 방법은 수행하려는 동작을 반드시 C++ 로 구현해야 하는 경우에 사용할 수 있을 듯 하다.

 

 

 

OnFeatureLevelChanged

엔진 코드 중 SCascadePreviewViewport.cpp 에서는 아래와 같은 코드를 볼 수 있다.

 

	UEditorEngine* Editor = (UEditorEngine*)GEngine;
	PreviewFeatureLevelChangedHandle = Editor->OnPreviewFeatureLevelChanged().AddLambda([this](ERHIFeatureLevel::Type NewFeatureLevel)
		{
			if(ViewportClient.IsValid())
			{
				UWorld* World = ViewportClient->GetPreviewScene().GetWorld();
				if (World != nullptr)
				{
					World->ChangeFeatureLevel(NewFeatureLevel);
				}
			}
		});

 

에디터에서 FeatureLevel이 바뀌었을 때 (내 경우에는 주로 SM6 ↔ ES3.1)를 알고, 바뀐 FeatureLevel을 넘겨받을 수 있다.

넘겨받은 FRHIFeatureLevel 목록은 아래 RHIFeatureLevel.h 에서 확인할 수 있다.

	enum Type : int
	{
		/** Feature level defined by the core capabilities of OpenGL ES2. Deprecated */
		ES2_REMOVED,

		/** Feature level defined by the core capabilities of OpenGL ES3.1 & Metal/Vulkan. */
		ES3_1,

		/**
		 * Feature level defined by the capabilities of DX10 Shader Model 4.
		 * SUPPORT FOR THIS FEATURE LEVEL HAS BEEN ENTIRELY REMOVED.
		 */
		SM4_REMOVED,

		/**
		 * Feature level defined by the capabilities of DX11 Shader Model 5.
		 *   Compute shaders with shared memory, group sync, UAV writes, integer atomics
		 *   Indirect drawing
		 *   Pixel shaders with UAV writes
		 *   Cubemap arrays
		 *   Read-only depth or stencil views (eg read depth buffer as SRV while depth test and stencil write)
		 * Tessellation is not considered part of Feature Level SM5 and has a separate capability flag.
		 */
		SM5,

		/**
		 * Feature level defined by the capabilities of DirectX 12 hardware feature level 12_2 with Shader Model 6.5
		 *   Raytracing Tier 1.1
		 *   Mesh and Amplification shaders
		 *   Variable rate shading
		 *   Sampler feedback
		 *   Resource binding tier 3
		 */
		SM6,

		Num
	};

 

Delegate 와 상관없이 현재 월드의 FeatureLevel을 얻고 싶으면 World 에서 FeatureLevel을 읽어오면 된다.

에디터 상태이거나 아직 에디터가 켜지기 전, Construction 단계에서 에디터의 FeatureLevel 상태를 얻고 싶으면

Editor 참조에서 DefautlWorldFeatureLevel 을 받아올 수 있다.

(이 부분은 에디터에서만 사용되는 코드이기 때문에 #if WITH_EDITOR 로 감싸주었다)

// 현재 월드의 FeatureLevel을 얻는다.
GetWorld()→GetFeatureLevel();

// 월드가 없을 때 (인게임 상황이 아닐 때), 에디터에서 활성화된 FeatureLevel을 얻는다.
// 생성자에서 GetWorld()로 월드를 참조하려고 시도하면 에러가 발생한다.
int CurrentFeatureLevel = Editor->DefaultWorldFeatureLevel;

 

이 Delegate를 이용하면, 모바일, PC 각 플랫폼마다 다르게 동작해야 하는 요소들 (포스트 프로세스 볼륨, 각 플랫폼별 별도 라이트 셋팅 등)을 각 상태를 에디터 상태에서 전환하도록 할 수 있다.

 

에디터에서 FeatureLevel 을 스위칭 할 때마다, 월드에 배치된 액터에서 이것을 감지하고 상태를 변경하도록 하였다.

 

덧붙임

위에서 사용한 Editor 는 UEditorEngine 클래스 인스턴스를 참조한다. 이 클래스는 Editor/EditorEngine.h 에 포함되어 있다.

위 경우에는 cpp 파일에서만 사용하고 있기 때문에, 헤더 파일이 아닌 cpp 파일에서 include 해 준다.

그렇게 해야 이 클래스의 헤더 파일을 include 하고 있는 다른 클래스에 영향을 주지 않고, 컴파일 시간이 늘어나는 일도 피할 수 있다.

만약 헤더 파일에서 변수 선언할 때 이것을 사용해야 한다면, 컴파일 에러 방지를 위해 전방선언으로 클래스 이름만 명시해 준다.

 

https://lykanstudio.tistory.com/16

 

[Unreal Engine 4 C++] class 키워드와 전방선언

언리얼 엔진에서 해더 파일(.h 파일)에 클래스 변수를 선언할 때 2가지 방법이 있습니다. UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera", meta = (AllowPrivateAccess = "true")) class USpringArmComponent* SpringA

lykanstudio.tistory.com