Initializing WidgetController & Widgets
Model ← WidgetController ← Widget의 단방향 참조 구조를 만들었고,
이제 WidgetController와 Widget이 필요한 정보를 초기화하도록 해야 함.
UAuraUserWidget
UCLASS()
class AURA_API UAuraUserWidget : public UUserWidget
{
GENERATED_BODY()
protected:
UFUNCTION(BlueprintImplementableEvent)
void WidgetControllerSet();
public:
UFUNCTION(BlueprintCallable)
void SetWidgetController(UObject* InWidgetController);
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UObject> WidgetController;
};
// UAuraUserWidget.cpp
void UAuraUserWidget::SetWidgetController(UObject* InWidgetController)
{
WidgetController = InWidgetController;
WidgetControllerSet();
}
SetWidgetController를 통해 WidgetController에 대한 레퍼런스를 가질 수 있게 한다.
이렇게 레퍼런스를 얻은 WidgetController는 WidgetControllerSet 이벤트를
블루프린트에서 각 위젯에서 오버라이드할 때 사용함으로서 필요한 이벤트를 바인딩 할 것이다.

UAuraWidgetController
class UAbilitySystemComponent;
class UAttributeSet;
USTRUCT(BlueprintType)
struct FWidgetControllerParams
{
GENERATED_BODY()
FWidgetControllerParams() {}
FWidgetControllerParams(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
: PlayerController(PC), PlayerState(PS), AbilitySystemComponent(ASC), AttributeSet(AS) {}
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<APlayerController> PlayerController = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<APlayerState> PlayerState = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent = nullptr;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<UAttributeSet> AttributeSet = nullptr;
};
UCLASS()
class AURA_API UAuraWidgetController : public UObject
{
GENERATED_BODY()
protected:
UPROPERTY(BlueprintReadOnly, Category = "WidgetController")
TObjectPtr<APlayerController> PlayerController;
UPROPERTY(BlueprintReadOnly, Category = "WidgetController")
TObjectPtr<APlayerState> PlayerState;
UPROPERTY(BlueprintReadOnly, Category = "WidgetController")
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
UPROPERTY(BlueprintReadOnly, Category = "WidgetController")
TObjectPtr<UAttributeSet> AttributeSet;
public:
UFUNCTION(BlueprintCallable)
void SetWidgetControllerParams(const FWidgetControllerParams& WCParams);
virtual void BroadcastInitialValues();
virtual void BindCallbacksToDependencies();
};
// UAuraWidgetController.cpp
void UAuraWidgetController::SetWidgetControllerParams(const FWidgetControllerParams& WCParams)
{
PlayerController = WCParams.PlayerController;
PlayerState = WCParams.PlayerState;
AbilitySystemComponent = WCParams.AbilitySystemComponent;
AttributeSet = WCParams.AttributeSet;
}
void UAuraWidgetController::BroadcastInitialValues()
{
// Implement in Child Class
}
void UAuraWidgetController::BindCallbacksToDependencies()
{
// Implement in Child Class
}
#pragma once
#include "CoreMinimal.h"
#include "UI/WidgetController/AuraWidgetController.h"
#include "OverlayWidgetController.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature, float, NewHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxHealthChangedSignature, float, NewMaxHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnManaChangedSignature, float, NewMana);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxManaChangedSignature, float, NewMaxMana);
UCLASS(BlueprintType, Blueprintable)
class AURA_API UOverlayWidgetController : public UAuraWidgetController
{
GENERATED_BODY()
public:
virtual void BroadcastInitialValues() override;
virtual void BindCallbacksToDependencies() override;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnHealthChangedSignature OnHealthChanged;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnMaxHealthChangedSignature OnMaxHealthChanged;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnManaChangedSignature OnManaChanged;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnMaxManaChangedSignature OnMaxManaChanged;
protected:
void HealthChanged(const FOnAttributeChangeData& Data) const;
void MaxHealthChanged(const FOnAttributeChangeData& Data) const;
void ManaChanged(const FOnAttributeChangeData& Data) const;
void MaxManaChanged(const FOnAttributeChangeData& Data) const;
};
// OverlayWidgetController.cpp
#include "UI/WidgetController/OverlayWidgetController.h"
#include "AbilitySystem/AuraAttributeSet.h"
void UOverlayWidgetController::BroadcastInitialValues()
{
UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);
OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());
OnMaxHealthChanged.Broadcast(AuraAttributeSet->GetMaxHealth());
OnManaChanged.Broadcast(AuraAttributeSet->GetMana());
OnMaxManaChanged.Broadcast(AuraAttributeSet->GetMaxMana());
}
void UOverlayWidgetController::BindCallbacksToDependencies()
{
UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);
// Dynamic Delegate가 아니므로 AddUObject 사용
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetHealthAttribute())
.AddUObject(this, &UOverlayWidgetController::HealthChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxHealthAttribute())
.AddUObject(this, &UOverlayWidgetController::MaxHealthChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetManaAttribute())
.AddUObject(this, &UOverlayWidgetController::ManaChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxManaAttribute())
.AddUObject(this, &UOverlayWidgetController::MaxManaChanged);
}
void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const
{
OnHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxHealthChanged(const FOnAttributeChangeData& Data) const
{
OnMaxHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::ManaChanged(const FOnAttributeChangeData& Data) const
{
OnManaChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxManaChanged(const FOnAttributeChangeData& Data) const
{
OnMaxManaChanged.Broadcast(Data.NewValue);
}AuraHUD
UCLASS()
class AURA_API AAuraHUD : public AHUD
{
GENERATED_BODY()
public:
UPROPERTY()
TObjectPtr<UAuraUserWidget> OverlayWidget;
UFUNCTION()
UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams& WCParams);
UFUNCTION()
void InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS);
private:
UPROPERTY(EditAnywhere)
TSubclassOf<UAuraUserWidget> OverlayWidgetClass;
UPROPERTY()
TObjectPtr<UOverlayWidgetController> OverlayWidgetController;
UPROPERTY(EditAnywhere)
TSubclassOf<UOverlayWidgetController> OverlayWidgetControllerClass;
};
// AuraHUD.cpp
#include "UI/HUD/AuraHUD.h"
#include "UI/WidgetController/OverlayWidgetController.h"
#include "UI/Widget/AuraUserWidget.h"
UOverlayWidgetController* AAuraHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
if (OverlayWidgetController == nullptr)
{
NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);
OverlayWidgetController = NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);
OverlayWidgetController->SetWidgetControllerParams(WCParams);
OverlayWidgetController->BindCallbacksToDependencies();
return OverlayWidgetController;
}
return OverlayWidgetController;
}
void AAuraHUD::InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
checkf(OverlayWidgetClass, TEXT("OverlayWidgetClass uninitialized. Please fill out BP_AuraHUD."));
checkf(OverlayWidgetClass, TEXT("OverlayWidgetControllerClass uninitialized. Please fill out BP_AuraHUD."));
UUserWidget* Widget = CreateWidget<UUserWidget>(GetWorld(), OverlayWidgetClass);
OverlayWidget = Cast<UAuraUserWidget>(Widget);
const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS);
UOverlayWidgetController* WidgetController = GetOverlayWidgetController(WidgetControllerParams);
OverlayWidget->SetWidgetController(WidgetController);
WidgetController->BroadcastInitialValues();
Widget->AddToViewport();
}
```cpp
void AAuraPlayerCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
/** Init Ability Actor Info for Server */
InitAbilityActorInfo();
}
void AAuraPlayerCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
// Init Ability Actor Info for Client
InitAbilityActorInfo();
}
void AAuraPlayerCharacter::InitAbilityActorInfo()
{
AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();
check(AuraPlayerState);
AuraPlayerState->GetAbilitySystemComponent()->InitAbilityActorInfo(AuraPlayerState, this);
AbilitySystemComponent = AuraPlayerState->GetAbilitySystemComponent();
AttributeSet = AuraPlayerState->GetAttributeSet();
if (AAuraPlayerController* AuraPlayerController = Cast<AAuraPlayerController>(GetController()))
{
if (AAuraHUD* AuraHUD = Cast<AAuraHUD>(AuraPlayerController->GetHUD()))
{
AuraHUD->InitOverlay(AuraPlayerController, AuraPlayerState, AbilitySystemComponent, AttributeSet);
}
}
}
```
- InitAbilityInfo()는 캐릭터가 컨트롤러에 의해 소유되는 시점인 OnPossessed()거나 플레이어 스테이트가 준비됐을 때 호출되는 OnRep_PlayerState()에서 호출되도록 한 함수이다.
따라서 여기서 호출한다면 필요한 모든 데이터가 이미 준비된 상태라고 볼 수 있으므로 AuraHUD의 InitOverlay()를 호출하기에 적절하다.
- 즉, 게임이 실행되고 MVC 모델을 구성하기 위한 함수 호출 순서는
1. InitAbilityActorInfo()(AuraPlayerCharacter)
2. GetOverlayWidgetController()(AuraHUD)
(위젯 컨트롤러를 반환하되, 없으면 생성하여 반환)
3. SetWidgetControllerParams()(OverlayWidgetController)
(위젯 컨트롤러에 모델로 사용할 레퍼런스 설정)
4. InitOverlay()(AuraHUD)
(AuraUserWidget을 생성함)
5. BIndCallbackstoDependencies()(OverlayWidgetController)
(Attribute 변경 시 호출 될 델리게이트에 위젯 컨트롤러의 델리게이트 호출 함수를 바인딩함)
6. SetWidgetController()(AuraUserWidget)
(AuraUserWidget에 위젯 컨트롤러 설정)
7. WidgetControllerSet()(AuraUserWidget)
(각 위젯 블루프린트에서 바인딩 된 이벤트 호출)
- 위의 순서와 같으며, 이렇게 구성한 경우
> Model → WidgetController에 대해 알지 못함
WidgetController → Widget에 대해 알지 못하나 Model은 알고 있음
Widget → Model에 대해 알지 못하지만 WidgetController는 알고 있음
이렇게 단방향의 참조 구조를 가지게 된다.
>