KoreanFoodie's Study

[언리얼] 언리얼에서 더티 플래그(Dirty Flag) 패턴 사용하기 본문

Game Dev/Unreal C++ : Dev Log

[언리얼] 언리얼에서 더티 플래그(Dirty Flag) 패턴 사용하기

GoldGiver 2022. 12. 1. 18:09

더티 플래그 패턴이란?

더티 플래그 패턴에 대해서는, 이전 블로그 글에서 간단하게 언급한 바 있다. 더티 플래그 패턴은 일반적으로 렌더링 과정에서, 계층 구조가 존재할 경우, 필요할 때 / 필요한 타이밍에 화면을 갱신하는 부분에서 자주 쓰인다.

또한 더티 플래그를 체크해 UI 를 갱신하게 되면, Tick 마다 UI 업데이트가 중복해서 일어나는 것을 막을 수 있다.

 

그렇다면 이 패턴을 언리얼에서는 어떤 방식으로 적용해 볼 수 있을까? 예를 들어, 우리가 어떤 UI 를 갱신 하는데,Dirty Flag 를 두어 해당 더티 플래그가 켜져 있을 때만 UI 를 갱신한다고 가정해 보자.

일단, 실제로 UI 클래스를 만들기 전에, 더티 플래그 패턴을 사용할 위젯들이 공통적으로 사용할 인터페이스를 다음과 같이 정의할 수 있다.

 

DirtyUpdateUIInterface.h

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Interface.h"
#include "UpdateUIInterface.generated.h"

UINTERFACE(meta=( CannotImplementInterfaceInBlueprint ))
class MYPROJECT_API UDirtyUpdateUIInterface : public UInterface
{
	GENERATED_UINTERFACE_BODY()
};

class MYPROJECT_API IDirtyUpdateUIInterface
{
	GENERATED_IINTERFACE_BODY()

protected:
	bool _isDirty = false;

protected:
	// Implement this in derived classes; Only include code for updating UI
	virtual void UpdateUIInternal() = 0;

public:
	// Set Dirty Flag 'true'
	void UpdateUI();

	// **Important** Bind this to "Tick" function!
	void CheckUpdate();

	// Getter/Setter for _isDirty flag
	void SetDirtyFlag(bool InDirty);
	bool GetDirtyFlag() const { return _bDirty; }
};

 

DirtyUpdateUIInterface.cpp

#include "DirtyUpdateUIInterface.h"


UDirtyUpdateUIInterface::UDirtyUpdateUIInterface(const class FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

}
void IDirtyUpdateUIInterface::UpdateUI()
{ 
	SetDirtyFlag(true); 
}
void IDirtyUpdateUIInterface::SetDirtyFlag(bool IsDirty)
{ 
	_isDirty = IsDirty; 
}
void IDirtyUpdateUIInterface::CheckUpdate()
{
	if (GetDirtyFlag())
	{
		SetDirtyFlag(false);
		UpdateUIInternal();
	}
}

 

이제 우리가 사용할 UI 클래스들은 위의 인터페이스를 상속받는다. 예를 들어, HP 상태를 보여주는 HP 바 위젯이 위의 더티 플래그 인터페이스를 상속받는다고 가정해 보자. 구현은 다음과 같을 것이다(실제 프로젝트에서 만든 것이 아닌 테스트 코드이니 실제로 적용할 때는 조금 고쳐서 사용하자).

HPBar.h

#pragma once

#include "CoreMinimal.h"
#include "IDirtyUpdateUIInterface.h"
#include "HPBar.generated.h"

class UserWidget;

UClass()
class MYPROJECT_API UHPBar : public UUserWidget, public IDirtyUpdateUIInterface
{
	GENERATED_BODY()
	
protected:
	// UUserWidget 을 오버라이딩한다
	virtual void NativeOnInitialized() override;
	virtual void NativeDestruct() override;

	// NativeTick 을 오버라이드 해 주어야 한다
	virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;

	// IDirtyUpdateUIInterface 의 함수를 오버라이딩한다
	virtual void UpdateUIInternal() override;
	
	// UObject 오버라이딩
	virtual void BeginDestroy() override;

protected:
	// HP 바 클릭
	UFUNCTION()
	void OnClickHPBar();

private:
	void UpdateHP();
};

 

HPBar.cpp

void UHPBar::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
	IDirtyUpdateUIInterface::CheckUpdate();

	Super::NativeTick(MyGeometry, InDeltaTime);
}

void UHPBar::NativeOnInitialized()
{
	// 기타 필요한 작업
	
	Super::NativeOnInitialized();
}

void UHPBar::NativeDestruct()
{
	// 기타 필요한 작업
	
	Super::NativeDestruct();
}

void UHPBar::InvalidateAllInternal()
{
	UpdateHP();
}

void UHPBar::BeginDestroy()
{
	// 기타 필요한 작업

	Super::BeginDestroy();
}

void UHPBar::OnClickHPBar()
{
	// 기타 필요한 작업
}

void UHPBar::UpdateList()
{
	// 기타 필요한 작업
	// HP 바에 들어갈 수치를 조정해 주는 코드가 들어갈 것이다
}

 

위의 코드에서의 핵심은, 바로 NativeTick 에서 IDirtyUpdateUIInterface::CheckUpdate() 를 호출하는 부분이다!

더티 플래그를 이용하면 UI 갱신의 흐름이 다음과 같이 정립된다 :

  1. 어떤 이벤트가 발생 했을 때, 실제로 업데이트를 하는 것이 아니라 더티 플래그만 바꾸어 준다.
  2. 다음 틱(NativeTick) 에서 더티 플래그를 체크해 플래그가 ON 되었을 경우, UI 를 업데이트해 준다.

위의 HPBar 을 상속받은 위젯이 있다고 가정해 보자. UI 업데이트의 순서가 (하위에 있는 것) -> (상위에 있는 것) 순으로 이루어지므로, 기존 더티 플래그 패턴의 이점인, Hierarchy 가 있을 때의 업데이트 비효율성 문제도 해결됨을 파악할 수 있다. 호출 순서를 보면, Super::NativeTick(...) 을 현재 필요한 UI 업데이트 작업 이후에 수행하기 때문이다!

 

Comments