목표
1. HUD 내의 다양한 요소를 각각의 클래스로 나누고자 했다.
2. 기존 위젯 애니메이션 제어 방식을 개선하고자 했다. (FindFunction 함수 호출 최소화)
- 위젯 애니메이션 재생 및 종료 시점에 위젯 삭제 (WBP 그래프에서 Play Animation with Finished Event 노드 활용) 를 하기 위해 FindFunction 함수로 위젯 블루프린트 내에서 생성한 애니메이션 관련 이벤트를 실행시키는 것이 효율적이지 않다고 생각되어UUserWidget 를 상속한 클래스의 함수를 블루프린트에서 오버라이드 하여 실행하는 방식으로 구현해보고자 했다.
[프로세스]
1. 화면 상단 중앙에 띄울 알림 메세지 창을 UUserWidget 을 상속한 C++ 클래스 NotificationWidget 으로 구현
2. NotificationWidget 을 HUD 위젯 블루프린트 내에 배치
3. DisplayNotification 함수 호출 시 Vertical Box 생성 및 Vertical Box 의 Child 로 UTextBlock 2개 생성 (제목, 내용) 및 SetText
3. UFUNCTION(BlueprintImplementableEvent) 매크로를 활용해 FadeInOut 함수를 위젯 블루프린트 내에서 구현
4. 해당 함수는 애니메이션을 재생시키고 Finished 이벤트 발생 시 RemoveNotification 함수 호출
5. RemoveNotification 함수는 VerticalBox, UTextBlock 을 삭제
VerticalBox, TextBlock 등을 생성하기 위에 지난번과 달리 이번에는 UWidgetTree 의 ConstructWidget 함수를 사용해보았다.
(지난번 NewObject 함수로 TextBlock 을 생성했던 것은 아래 글에 적었었다.)
[언리얼/C++] 디버프 - 코드로 UTextBlock 동적 생성 및 추가
디버프 효과가 적용될 때 디버프 이름과 남은 시간을 UTextBlock 으로 생성하여 HUD 위젯 블루프린트에서 만들어 놓은 Vertical Box 에 추가하는 과정을 기록한다. 프로세스아래의 내용을 0.1초마다 호
jaboy.tistory.com
UWidgetTree / ConstructWidget 로 위젯 생성
UWidgetTree 는 블루프린트 위젯 내에 생성되는 모든 위젯의 콜렉션이다.
예를 들어 GetWidgetFromName 함수 호출 시 아래와 같이 WidgetTree 에서 FindWidget 함수로 찾는다.
// UserWidget.cpp
UWidget* UUserWidget::GetWidgetFromName(const FName& Name) const
{
return WidgetTree ? WidgetTree->FindWidget(Name) : nullptr;
}
// WidgetTree.cpp
UWidget* UWidgetTree::FindWidget(const FName& Name) const
{
UWidget* FoundWidget = nullptr;
ForEachWidget([&] (UWidget* Widget) {
if ( Widget->GetFName() == Name )
{
FoundWidget = Widget;
}
});
return FoundWidget;
}
위에서 볼 수 있듯이 WidgetTree 는 UUserWidget 이 변수로 갖고 있다.
이 WidgetTree 에 ConstructWidget 함수로 특정 위젯 타입의 객체를 생성하고 이름 붙일 수 있다.
/** Constructs the widget, and adds it to the tree. */
template <typename WidgetT>
[[nodiscard]] FORCEINLINE_DEBUGGABLE WidgetT* ConstructWidget(TSubclassOf<WidgetT> WidgetClass = WidgetT::StaticClass(), FName WidgetName = NAME_None)
{
if(WidgetClass != nullptr)
{
if constexpr(std::is_base_of_v<UUserWidget, WidgetT>)
{
return CreateWidget<WidgetT>(this, *WidgetClass, WidgetName);
}
else
{
static_assert(std::is_base_of_v<UWidget, WidgetT>, "WidgetTree::ConstructWidget can only create UWidget objects.");
return NewObject<WidgetT>(this, WidgetClass, WidgetName, RF_Transactional);
}
}
return nullptr;
}
사실 NewObject 의 오버로드된 함수들을 좀 자세히 확인해봤어야하는데 그 생각을 못했다.. ㅎㅎ...
this (WidgetTree), RF_Transactional (EObjectFlag 열거형에 정의된 값) 이라는 플래그를 인자로 전달하고 있는데...
둘 다 잘 모르는 개념이었다.
우선 해당 NewObject 함수의 정의는 아래와 같다.
/**
* Convenience template for constructing a gameplay object
*
* @param Outer the outer for the new object. If not specified, object will be created in the transient package.
* @param Class the class of object to construct
* @param Name the name for the new object. If not specified, the object will be given a transient name via MakeUniqueObjectName
* @param Flags the object flags to apply to the new object
* @param Template the object to use for initializing the new object. If not specified, the class's default object will be used
* @param bCopyTransientsFromClassDefaults if true, copy transient from the class defaults instead of the pass in archetype ptr (often these are the same)
* @param InInstanceGraph contains the mappings of instanced objects and components to their templates
* @param ExternalPackage Assign an external Package to the created object if non-null
*
* @return a pointer of type T to a new object of the specified class
*/
template< class T >
FUNCTION_NON_NULL_RETURN_START
T* NewObject(UObject* Outer, const UClass* Class, FName Name = NAME_None, EObjectFlags Flags = RF_NoFlags, UObject* Template = nullptr, bool bCopyTransientsFromClassDefaults = false, FObjectInstancingGraph* InInstanceGraph = nullptr, UPackage* ExternalPackage = nullptr)
FUNCTION_NON_NULL_RETURN_END
{
// 어쩌구 저쩌구 ~ 전달받은 인자를 파라미터에 저장하고 Internal 함수를 호출한다
}
EObjectFlag 를 살펴보면 아래와 같았다...
/**
* Flags describing an object instance
* When modifying this enum, update the LexToString implementation!
*/
enum EObjectFlags
{
// Do not add new flags unless they truly belong here. There are alternatives.
// if you change any the bit of any of the RF_Load flags, then you will need legacy serialization
RF_NoFlags = 0x00000000, ///< No flags, used to avoid a cast
// This first group of flags mostly has to do with what kind of object it is. Other than transient, these are the persistent object flags.
// The garbage collector also tends to look at these.
RF_Public =0x00000001, ///< Object is visible outside its package.
RF_Standalone =0x00000002, ///< Keep object around for editing even if unreferenced.
RF_MarkAsNative =0x00000004, ///< Object (UField) will be marked as native on construction (DO NOT USE THIS FLAG in HasAnyFlags() etc)
RF_Transactional =0x00000008, ///< Object is transactional.
RF_ClassDefaultObject =0x00000010, ///< This object is used as the default template for all instances of a class. One object is created for each class
RF_ArchetypeObject =0x00000020, ///< This object can be used as a template for instancing objects. This is set on all types of object templates
RF_Transient =0x00000040, ///< Don't save object.
// This group of flags is primarily concerned with garbage collection.
RF_MarkAsRootSet =0x00000080, ///< Object will be marked as root set on construction and not be garbage collected, even if unreferenced (DO NOT USE THIS FLAG in HasAnyFlags() etc)
RF_TagGarbageTemp =0x00000100, ///< This is a temp user flag for various utilities that need to use the garbage collector. The garbage collector itself does not interpret it.
// The group of flags tracks the stages of the lifetime of a uobject
RF_NeedInitialization =0x00000200, ///< This object has not completed its initialization process. Cleared when ~FObjectInitializer completes
RF_NeedLoad =0x00000400, ///< During load, indicates object needs loading.
RF_KeepForCooker =0x00000800, ///< Keep this object during garbage collection because it's still being used by the cooker
RF_NeedPostLoad =0x00001000, ///< Object needs to be postloaded.
RF_NeedPostLoadSubobjects =0x00002000, ///< During load, indicates that the object still needs to instance subobjects and fixup serialized component references
RF_NewerVersionExists =0x00004000, ///< Object has been consigned to oblivion due to its owner package being reloaded, and a newer version currently exists
RF_BeginDestroyed =0x00008000, ///< BeginDestroy has been called on the object.
RF_FinishDestroyed =0x00010000, ///< FinishDestroy has been called on the object.
// Misc. Flags
RF_BeingRegenerated =0x00020000, ///< Flagged on UObjects that are used to create UClasses (e.g. Blueprints) while they are regenerating their UClass on load (See FLinkerLoad::CreateExport()), as well as UClass objects in the midst of being created
RF_DefaultSubObject =0x00040000, ///< Flagged on subobject templates that were created in a class constructor, and all instances created from those templates
RF_WasLoaded =0x00080000, ///< Flagged on UObjects that were loaded
RF_TextExportTransient =0x00100000, ///< Do not export object to text form (e.g. copy/paste). Generally used for sub-objects that can be regenerated from data in their parent object.
RF_LoadCompleted =0x00200000, ///< Object has been completely serialized by linkerload at least once. DO NOT USE THIS FLAG, It should be replaced with RF_WasLoaded.
RF_InheritableComponentTemplate = 0x00400000, ///< Flagged on subobject templates stored inside a class instead of the class default object, they are instanced after default subobjects
RF_DuplicateTransient =0x00800000, ///< Object should not be included in any type of duplication (copy/paste, binary duplication, etc.)
RF_StrongRefOnFrame =0x01000000, ///< References to this object from persistent function frame are handled as strong ones.
RF_NonPIEDuplicateTransient =0x02000000, ///< Object should not be included for duplication unless it's being duplicated for a PIE session
// RF_Dynamic =0x04000000, ///< Was removed along with bp nativization
RF_WillBeLoaded =0x08000000, ///< This object was constructed during load and will be loaded shortly
RF_HasExternalPackage =0x10000000, ///< This object has an external package assigned and should look it up when getting the outermost package
// RF_Unused =0x20000000,
// RF_MirroredGarbage is mirrored in EInternalObjectFlags::Garbage because checking the internal flags is much faster for the Garbage Collector
// while checking the object flags is much faster outside of it where the Object pointer is already available and most likely cached.
RF_MirroredGarbage =0x40000000, ///< Garbage from logical point of view and should not be referenced. This flag is mirrored in EInternalObjectFlags as Garbage for performance
RF_AllocatedInSharedPage =0x80000000, ///< Allocated from a ref-counted page shared with other UObjects
};
ㅋㅋ 엄청 기네;; 근데 잘 알아두면 액터와 달리 라이프사이클이 직접적으로 정해지지 않는 UObject 객체를 관리할 때 유용할 것도 같다.....?
위에서 말한 "Transactional" 플래그를 가진 오브젝트는 아래와 같은 특징을 갖는다고 한다.
Undo/Redo functionality:
The core function of a transaction is to enable users to easily undo or redo actions by grouping related changes together as a single transaction.
Multi-user editing:
When multiple users are working on the same level, each user's edits are sent as transactions to other users, allowing everyone to see the changes in real-time.
Explicit control with Blueprint nodes:
Developers can use specific Blueprint nodes like "Begin Transaction", "End Transaction", and "Transact Object" to define which actions should be part of a transaction.
음..........?.....알듯말듯ㅠ 일단 넘어가....
BlueprintImplementableEvent 오버라이드
이것이 무엇인지, 어떻게 활용하는지 아래에 간단히 잘 정리되어 있었다.
[UE5] C++와 블루프린트 연결 방법 (C++에서 Blueprint Event 시점 제어, BlueprintNativeEvent, BlueprintImplementabl
0. 서문C++ 함수의 동작 중 Blueprint의 이벤트를 발생시키는 방법을 찾는 과정에서 Delegate와 UFUNCTION을 사용하여 C++과 Blueprint를 연결하는 방법을 발견했습니다.특히, C++에서 'BlueprintNativeEvent'와 'Bluep
kyunstudio.tistory.com
이렇게 만든 함수를 위젯 블루프린트 상에서 오버라이드하고 PlayAnimationWithFinishedEvent 노드를 활용해 삭제까지 잘 구현되게 했다.
위젯 블루프린트의 그래프에서 위와 같이 해당 함수를 오버라이드하고 아래와 같이 스크립팅했다.
휴우우... WidgetTree 의 ConstructWidget 사용하는 것에 대해 검색도 잘 안되었고
뭔가 머리도 잘 안돌아가서 속도가 느린 하루였지만 그래도 해결되어서 다행이당.
'언리얼_엔진_게임개발_공부 > 프로젝트' 카테고리의 다른 글
[슈터게임] 6. DataTable 활용한 상점 구현 (0) | 2025.03.04 |
---|---|
[슈터게임] 5. C++ 코드에서 UTextBlock 위젯의 폰트 / 아웃라인 변경 - UFont / FSlateFontInfo / FFontOutlineSettings (0) | 2025.02.26 |
[슈터게임] 3. HUD 위젯의 이미지 교체하기 / SetBrush... (0) | 2025.02.22 |
[슈터게임] 2. UMG UUserWidget 고마운 기능들 (0) | 2025.02.20 |
[슈터게임] 1. 게임 UI 레퍼런스 분석 & WBP 레이아웃 (0) | 2025.02.18 |