#1. UI
1. 목표
- 가독성: UI 구현 시 각 UI 정의 코드를 일관된 형식에 맞춰 작성함으로써, 가독성과 유지 보수성 향상을 목표합니다.
- Event-Driven: Event-Driven 기반 설계를 통해 각 UI 클래스 내부 Tick 메서드 사용을 억제합니다. 더불어, Event-Driven 기반 설계를 통해 커플링 완화, 그리고 타입 안정성을 제공합니다.
- Pooling: 각 UI는 CreateWidget 호출을 통해 게임 시작 시점에 생성되어 지속적으로 재활용되며, 각 UI는 가시성 변화 기능을 통해 해당 UI를 화면에 나타낼 것인지, 숨길 것인지 결정됩니다.
2. 관련 이슈
- #49, Feature: Inventory 설계 및 관련 UI 제작
- #68, Feature: Inventory UI 추가 기능
3. 형식
0. 생성 시점
- 생성 시점에 호출되는 생명 주기 함수들 활용(NativeOnInitialized, ...etc) '외부 바인딩' 수행
1. 자식 생성(지연 초기화, 비동기 초기화)
- 객체 생성 이후 생성을 담당하는 객체가 발행하는 ReqeustStartInitBy~ 이벤트에 각 객체의 초기화 함수를 콜백으로 등록
- '외부 바인딩' 작업이지만, 초기화 요청 이벤트는 예외적으로 생성을 담당하는 쪽에서 해줍니다.
- 생성된 객체에 대하여 '내부 바인딩' 수행
- 각 객체의 초기화 완료 이벤트 구독
- +부가 작업
- 모든 객체 생성 이후 RequestStartInitBy~ 이벤트 호출
2. 초기화
- 각 객체의 지연 초기화 작업이 이루어지는 곳
- 0번 작업 반복
- 각 객체의 자식 위젯에 대히여 1번 작업 반복
3. 초기화 완료 체크
- 각 객체의 초기화 완료 이벤트를 구독하는 각각의 콜백 함수를 활용하여 자식 위젯들의 초기화 완료 체크
4. 구현 목표
- 지연 초기화(의존성 정의): 각 객체의 생성을 담당하는 쪽에서 지연 초기화를 수행합니다. 각 객체의 초기화 작업이 내부적으로 제각각 수행되면서, 서로가 서로의 이벤트를 바인딩하는 의존 관계가 존재할 경우 일관성 있는 결과를 얻어내기 어려웠습니다. 따라서, 지연 초기화를 통해 모든 객체 간 의존 관계를 정의하고, 이후 지연 초기화 작업을 일괄적으로 처리함으로써 기대한 결과를 얻어내고, 이러한 초기화 작업은 비동기적으로 수행되어 성능 향상에도 도움이 되었습니다.
- 로딩/세이브 시스템: UI는 대체로 여러 컴포넌트들의 내부 정보를 화면에 나타냅니다. 로딩/세이브 기능 활용을 위해 외부 컴포넌트들의 로딩 작업 이전에 생성 및 초기화 작업이 모두 완료되어야 합니다. 따라서, 위 형식을 지켜 UI의 초기화 시작/완료 시점을 관리해, 이들이 대표하는 컴포넌트들의 로딩 시작 시점을 명시적으로 확인할 수 있도록 합니다.
- 코드 일관성: 일관된 코드 형식을 유지하여 UI 관련 코드를 작성함으로써, 타 기능을 제작하는 개발 인원들의 코드 리뷰를 원활하게 도와줍니다.
- 디자이너와의 협업: 기획 단계에서 작성한 UI 관련 이미지들을 반영하여 UI들의 위치, 크기, 그리고 색상 등의 작업을 Editor 상에서 쉽게 변경할 수 있도록 열어줍니다.
#3. 초기화
1. 외부 바인딩
void UItemSlots::ExternalBindToInputComp()
{
//@World
UWorld* World = GetWorld();
if (!World)
{
UE_LOGFMT(LogItemSlots, Error, "{0}: World is null", __FUNCTION__);
return;
}
//@PC
APlayerController* PC = World->GetFirstPlayerController();
if (!PC)
{
UE_LOGFMT(LogItemSlots, Error, "{0}: PlayerController is null", __FUNCTION__);
return;
}
//@Input Comp
UBaseInputComponent* BaseInputComp = Cast<UBaseInputComponent>(PC->InputComponent);
if (!BaseInputComp)
{
UE_LOGFMT(LogItemSlots, Error, "{0}: Input Component를 찾을 수 없습니다", __FUNCTION__);
return;
}
//@TODO: Binding
BaseInputComp->UIInputTagTriggered.AddUFunction(this, "OnUIInputTagTriggered");
BaseInputComp->UIInputTagReleased.AddUFunction(this, "OnUIInputTagReleased");
}
외부 바인딩 작업은 피 의존 객체, 즉 외부 컴포넌트에서 발행하는 이벤트에 콜백 함수를 등록하고자 하는 쪽에서 수행합니다. 그리고, 이 외부 바인딩 작업은 의존 관계에 있는 두 객체가 모두 생성된 이후 가장 빠른 시점에 수행합니다. 클래스 외부의 서로 다른 유형의 클래스를 가져오는 작업이기 때문에, 코드가 조금 복잡해집니다. 따라서, 생성된 시점에 구독해야 할 모든 이벤트에 대하여 바인딩을 수행해 주어, 런 타임에 발생하는 이벤트에도 별도의 추가 작업 없이 관련 작업을 처리할 수 있도록 했습니다.
2. 호출 시점
- 생성 시점이 동일한 경우: 생성 이후에 호출되는 UUserWidget이 제공하는 생명 주기 함수에서 외부 바인딩 진행
- 생성 시점이 다를 경우: 외부 UI 객체의 자식 UI 객체를 가져오는 것이 쉽지 않습니다. 따라서, 비교적 가져오기 쉬운 부모 UI의 자식 UI들의 초기화 완료를 알리는 이벤트에 콜백을 등록하고, 콜백 함수 호출 시 자식 UI를 인자로 전달받아서 외부 바인딩을 진행합니다.
#4. 자식 생성
1. 초기화 요청 이벤트
void UItemSlots::InitializeItemSlots()
{
//@Item Slots
CreateItemSlots();
//@초기화 요청 이벤트
RequestStartInitByItemSlots.Broadcast();
}
void UItemSlots::CreateItemSlots()
{
UE_LOGFMT(LogItemSlots, Log, "아이템 슬롯 생성 시작");
//@Interactable Item Slot 블루프린트 클래스, Item Slot Box
if (!ensureMsgf(InteractableItemSlotClass && ItemSlotBox, TEXT("InteractableItemSlotClass 또는 ItemSlots가 유효하지 않습니다.")))
{
UE_LOGFMT(LogItemSlots, Error, "아이템 슬롯 생성 실패: InteractableItemSlotClass 또는 ItemSlotBox가 유효하지 않음");
return;
}
//@Clear Children
ItemSlotBox->ClearChildren();
int32 TotalSlots = DefaultRows * MaxItemSlotsPerRow;
int32 CurrentSlot = 0;
for (int32 Row = 0; Row < MaxRows; ++Row)
{
UHorizontalBox* HorizontalBox = NewObject<UHorizontalBox>(this);
UVerticalBoxSlot* VerticalBoxSlot = ItemSlotBox->AddChildToVerticalBox(HorizontalBox);
if (VerticalBoxSlot)
{
VerticalBoxSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill));
VerticalBoxSlot->SetHorizontalAlignment(HAlign_Fill);
VerticalBoxSlot->SetVerticalAlignment(VAlign_Fill);
VerticalBoxSlot->SetPadding(PaddingBetweenRows);
}
if (Row < DefaultRows)
{
for (int8 SlotIndex = 0; SlotIndex < MaxItemSlotsPerRow && CurrentSlot < TotalSlots; ++SlotIndex, ++CurrentSlot)
{
UInteractableItemSlot* ItemSlot = CreateWidget<UInteractableItemSlot>(this, InteractableItemSlotClass);
if (ItemSlot)
{
RequestStartInitByItemSlots.AddUFunction(ItemSlot, "InitializeItemSlot");
CancelItemSlotButton.AddUFunction(ItemSlot, "ItemSlotButtonCanceledNotified");
if (CurrentSlot == TotalSlots - 1)
{
InternalBindingToItemSlot(ItemSlot, true);
}
else
{
InternalBindingToItemSlot(ItemSlot);
}
UHorizontalBoxSlot* BoxSlot = HorizontalBox->AddChildToHorizontalBox(ItemSlot);
if (BoxSlot)
{
FSlateChildSize SlateChildSize;
SlateChildSize.SizeRule = ESlateSizeRule::Fill;
SlateChildSize.Value = 1.f;
//@Size
BoxSlot->SetSize(SlateChildSize);
//@Alignment
BoxSlot->SetHorizontalAlignment(HAlign_Fill);
BoxSlot->SetVerticalAlignment(VAlign_Fill);
//@Padding
BoxSlot->SetPadding(PaddingBetweenItemSlots);
}
}
else
{
UE_LOGFMT(LogItemSlots, Error, "InteractableItemSlot 생성 실패: 슬롯 {0}", CurrentSlot + 1);
}
}
}
else
{
// DefaultRows 이후의 행에 대해 Spacer 추가
USpacer* Spacer = NewObject<USpacer>(this);
if (!Spacer)
{
UE_LOGFMT(LogItemSlots, Error, "Spacer 생성 실패!");
return;
}
UVerticalBoxSlot* SpacerSlot = ItemSlotBox->AddChildToVerticalBox(Spacer);
if (SpacerSlot)
{
//@FSlateChildSize
FSlateChildSize SlateChildSize;
SlateChildSize.SizeRule = ESlateSizeRule::Fill;
SlateChildSize.Value = 0.5f;
//@Size
SpacerSlot->SetSize(SlateChildSize);
//@Alignment
SpacerSlot->SetHorizontalAlignment(HAlign_Fill);
SpacerSlot->SetVerticalAlignment(VAlign_Fill);
}
UE_LOGFMT(LogItemSlots, Verbose, "추가 행 {0}에 Spacer 추가 완료", Row + 1);
}
}
//@초기 상태로 설정
ResetItemSlots();
UE_LOGFMT(LogItemSlots, Log, "ItemSlots가 성공적으로 생성되었습니다. 총 슬롯 수: {0}, 기본 행 개수: {1}, 최대 행 개수: {2}, 열 개수: {3}",
TotalSlots, DefaultRows, MaxRows, MaxItemSlotsPerRow);
}
초기화 요청 이벤트는 자식 UI 생성을 담당하는 쪽에서 발행하고, 객체 생성 시 해당 이벤트에 자식 UI의 초기화 함수(Initialize~,... etc)를 콜백으로 등록합니다. 모든 자식 UI의 생성을 마치고, 초기화 요청 이벤트를 호출하여 지연 초기화 작업을 수행합니다.
2. 내부 바인딩
1. 개념
//@내부 바인딩 정의 함수...
void UInventoryUI::InternalBindingToInventoryUIContent(UInventoryUIContent* InventoryUIContent)
{
//@Item Description
if (!InventoryUIContent)
{
UE_LOGFMT(LogInventoryUI, Error, "InventoryUIContent UI가 유효하지 않습니다.");
return;
}
InventoryUIContent->InventoryUIContentInitFinished.BindUFunction(this, "OnInventoryUIContentInitFinished");
}
//@자식 UI의 생성 작업...
void UInventoryUI::CreateInventoryContent()
{
if (!InventoryUIContentOverlay)
{
UE_LOGFMT(LogInventoryUI, Error, "InventoryUIContentOverlay가 유효하지 않습니다.");
return;
}
if (!ensureMsgf(InventoryUIContentClass, TEXT("InventoryUIContentClass가 설정되지 않았습니다.")))
{
return;
}
UInventoryUIContent* InventoryUIContent = CreateWidget<UInventoryUIContent>(this, InventoryUIContentClass);
if (InventoryUIContent)
{
//@비동기 초기화 이벤트
RequestStartInitByInventoryUI.AddUFunction(InventoryUIContent, "InitializeInventoryUIContent");
//@내부 바인딩
InternalBindingToInventoryUIContent(InventoryUIContent);
UOverlaySlot* OverlaySlot = InventoryUIContentOverlay->AddChildToOverlay(InventoryUIContent);
if (OverlaySlot)
{
OverlaySlot->SetHorizontalAlignment(HAlign_Fill);
OverlaySlot->SetVerticalAlignment(VAlign_Fill);
}
UE_LOGFMT(LogInventoryUI, Log, "Inventory Content UI 위젯이 성공적으로 추가되었습니다.");
}
else
{
UE_LOGFMT(LogInventoryUI, Error, "Inventory Content UI 위젯 생성에 실패했습니다.");
}
}
'내부 바인딩' 역시 의존 관계에 있는 두 객체에서 피 의존 객체 쪽에서 수행합니다. 일반적으로, 내부 바인딩 작업은 자식 UI의 생성/초기화 작업 시점을 관리하는 상위 객체에서 수행하며, 일반적으로 두 가지 유형의 이벤트를 구독합니다.
먼저, 내부 바인딩 작업에서 상위 객체는 자식 UI의 초기화 완료 이벤트를 구독합니다. 이를 통해, 자식 UI의 초기화 완료 시점을 명시해 줍니다.
다음으로, 내부 바인딩 작업에서 상위 객체는 자식 UI에서 발생하는 이벤트들을 구독합니다. 이를 통해, 전체 자식 UI에 공통적으로 수행해야 하는 작업 혹은 자식 UI들의 이벤트를 구독하고자 하는 외부 컴포넌트들에게 그 시점을 알려주는 등의 작업을 수행합니다.
2. 호출 시점
- 자식 UI의 생성 후, 초기화 요청 이벤트 호출 전: 자손 UI의 생성 이후 초기화 요청 이벤트를 호출하기 전에 수행.
#5. 초기화 완료
1. 초기화 완료 이벤트
//@내부 바인딩 작업 시 Item Slot의 초기화 완료 이벤트에 등록한 콜백
void UItemSlots::OnItemSlotInitFinished()
{
bItemSlotReady = true;
//@초기화 완료 체크 함수 호출
CheckItemSlotInitFinished();
}
//@초기화 완료 체크
void UItemSlots::CheckItemSlotInitFinished()
{
if (bItemSlotReady)
{
bItemSlotReady = false;
//@Item Slots 초기화 완료 이벤트
ItemSlotsInitFinished.ExecuteIfBound();
UE_LOGFMT(LogItemSlots, Log, "아이템 슬롯 초기화가 완료되었고, CustomButton의 취소 이벤트 바인딩이 수행되었습니다.");
}
}
초기화 완료 이벤트는 모든 자식 UI의 초기화 작업이 끝나면 호출합니다.
'그룹프로젝트 > Dev' 카테고리의 다른 글
[GroupProject_AOW]#1. Attribute 수치 변화 이벤트 관련 인터페이스 (0) | 2024.04.17 |
---|