게임 플레이시 플레이어에게 제공되어야 하는 시각적인 정보들을 필요로 한다.
이런 정보들을 화면에 나타낼 때는 Widget 을 통해 나타낸다.
그리고 언리얼엔진에서는 UUSerWidget 이라는 c++ 클래스 기반으로 만들어지는 위젯 블루프린트를 통해 시각적인 게임 데이터를 볼 수 있도록 기능을 제공한다.
체력바나 마나바와 같은 것들은 값들을 받아와야 시각적으로 표현 가능하다.
해당 데이터들은 캐릭터에 속한 AttributeSet 클래스에 수치적으로 저장되어 있고, PlayerState 는 화면에 표시해야 하는 여러 변수들을 포함할 수 있다.
또한 그외의 여러 클래스들도 위젯에서 플레이어에게 보여주기 위해 필요할 수도 있다.
위젯 오브젝트는 참조를 통해 해당 값을 받아와 플레이어에게 시각적으로 보여줄 수 있지만 이는 매우 비효율적이다.
3번의 WidgetController 에서 브로드캐스트를 통해 View에서 데이터를 받고, View 에서 버튼이 클릭되면(스탯을 올린다던가 하는 행위) WidgetController 에서 이를 받아 Model 로 변화를 알린다.

먼저 UserWidgetClass 를 생성한다.
추가
경로: UI->Widget(Widgets 아님)
이어서WidgetController를 생성한다.
이제 WidgetController 에서 브로드캐스트를 받기 위한 멤버변수가 필요하다.
// UserWidgetClass.h
public:
// WidgetController를 설정하고, WidgetControllerSet()[=BeginPlay()] 를 호출함를 호출하게 됨
UFUNCTION(BlueprintCallable)
void SetWidgetController(UObject* InWidgetController);
// 블루프린트에서 접근할 수 있어야 함
UPROPERTY(BlueprintReadOnly)
TObjectPtr<UObject> WidgetController;
protected:
// WidgetController가 설정되자마자 호출, BeginPlay()와 동일한 역할을 하는 함수
// BlueprintImplementableEvent를 통해 위젯에서 위젯 컨트롤러를 설정할 때마다 호출됨
UFUNCTION(BlueprintImplementableEvent)
void WidgetControllerSet();
// AuraUserWidget.cpp
void UAuraUserWidget::SetWidgetController(UObject* InWidgetController)
{
WidgetController = InWidgetController;
WidgetControllerSet();
}
이제 WidgetController 가 Model로부터 가져온 데이터에 대해 응답하고, 전체 위젯에 Broadcast하기 위한 변수를 필요로 한다.
현재 프로젝트에서는 ASC , AttributeSet , PlayerState , PlayerController 가 대상이다.
해당 변수들을 통해 Model에서 값을 받받고, 전체 위젯에 Boradcast를 하게 되는 것이다.
// AuraWidgetcontroller.h
...
class UAttributeSet;
class UAbilitySystemComponent;
...
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;
위젯을 생성하기 위해 AuraUserWidget 클래스 기반의 WidgetBlueprint 를 생성한다.



위젯의 크기를 지정해주기 위해 Size Box를 먼저 생성한다.

박스의 크기를 지정하기 위해 우측 상단의 Graph 탭에서 float 타입의 변수 두개를 생성한다.

Desired 로 변경하고, Sizebox_Root 에서 IsVariable 을 활성화 시킨다.

IsVariable 을 활성화시키면 SizeBox_Root 에 접근 가능해진다.

노드를 아래와 같이 구성하고

Default Value 의 값을 설정해주면 Event Pre Contstruct 노드에 의해 값이 미리 설정되게 할 수 있다.


깔끔하게 정리하기 위해 노드들을 우클릭하고 Collapse to Function 을 클릭하면 함수로 리팩토링할수 있다.


변수들도 보기 편하게 하기 위해 Category 탭에서 GlobeProperties 를 입력해주면 카테고리 지정이 가능해진다.

BoxHeight 를 GlobeProperties 로 드래그드랍하여 정리한다.

사이즈 박스 위에 위젯 관련된 것을 쌓아올리기 위해 Overlay 를 추가한다.
(포토샵에서의 일종의 레이어와 같음)

게이지를 표시하기 위핸 이미지를 추가하고

Padding 에서 Horizontal Alignment 와 Vertical Alignment 를 풀로 채워준다.

Image_Background 도 동일하게 IsVariable 를 활성화시키고, Graph 탭에서 Slate Brush 타입의 변수를 하나 생성해준다.


이미지를 추가하기 위한 노드를 구성하고

Default Value 탭에서 미리 준비해둔 이미지를 추가해준 후, 컴파일하면 이미지가 추가된다.


동일하게 리팩토링을 해서 깔끔하게 정돈한다.

이제 체력 퍼센트에 따른 게이지 변경을 위한 Progress Bar 를 추가하고

Padding 탭에서 Horizontal Alignment 와 Vertical Alignment 를 풀로 채워준다.

Style 탭에서 미리 준비해둔 이미지를 추가한다.

이미지가 제대로 나타나지 않는다면 하단의 Appearance -> Fill Color and Opacity 탭에서 좌측의 Saturation 바를 흰색으로 변경하면 적용된다.

Fill Image 에서 Draw As : Image 로 변경하면 이미지와 동일한 체력바가 나타나는 것을 확인할 수 있다.

마지막으로 Backgroud Image 탭에서 Tint 탭의 알파값을 0으로 만들면 투명하게 변경 가능하다.

퍼센트를 조절하면 좌우로 변경되는데, Progress -> Bar Fill Type : Bottom to Top 으로 변경하면 아래에서 위로 변경되도록 변경할 수 있다.

ProgressBar_Globe 도 동일하게 IsValiable 을 활성화시키고

Set Style 노드를 추가하여 연결시킨다.

위에서 했던 것과 동일하게 Tint 의 알파값을 0으로 설정하고, 해당 설정을 Background Image 에 적용시킨다.

Fill Image 도 추가하기 위해 Slate Brush 를 추가해주고 Make ProgressBar Style : Fill Image 핀에 연결시킨 후, (컴파일하고)이미지를 적용시킨다.


이제 이미지만 변경시키면 아래와 같이 편하게 다른 부분은 굳이 추가로 설정하지 않아도 된다.


마지막으로 동일하게 리팩토링을 통해 관리하기 쉽도록 수정한다.

따로 추가는 안했지만, 전부
Globe Properties에 속하므로 미리 카테고리에 적용시켜서 관리하기 쉽게 한다.
테두리 않으로 체력바를 넣기 위해서는 Slot 탭의 padding 값을 조절해야 한다.
( Slot(Overlay Slot) 임을 유의)

padding 값을 조절하기 위해 노드를 추가한다.

동일하게 GlobeProperties 카테고리에 속하도록 카테고리를 지정해주고, 값은 이전에 확인한 10으로 지정해준다.

동일하게 리팩토링을 통해 정리하면 끝.

게이지가 줄어든 부분은 그냥 투명하게 두는 것이 아니라 빈 유리처럼 적용시켜두면 좀 더 리얼하게 UI를 구성할 수 있을 것이다.
Image 를 추가해주고 IsVariable 을 활성화시킨다.


Horizontal Alignment 와 Vertical Alignment 를 풀로 채운다.

Graph 탭으로 넘어와서 이미지를 추가할 Slate Brush 를 생성하고, GlobeProperties 카테고리에 넣어준 다음, 이미지를 추가한다.

노드를 연결시킨 후 컴파일하면 정상적으로 적용된 것을 확인할 수 있다.


4.3.5와 동일하게 유리부분을 패딩을 통해 좀 더 깔끔하게 정리하기 위해 적당한 값을 찾아본다.

동일하게 패딩이 적용되도록 노드를 추가한다.

유리부분이 너무 하얗게 비추므로, 알파값을 조절하여 투명도를 추가하여 리얼리티를 살린다.

동일하게 리팩토링을 통해 간략하게 정리하도록 한다.

최종적으로 정리된 것을 보면 깔끔하게 정리 된 것을 확인할 수 있다.

4.3에서 베이스가 되는 위젯을 생성하였으므로 해당 블루프린트를 이용한 HP바와 MP바를 생성한다. 동일하게 생성하되, 부모 클래스를 4.3에서 생성한 WBP_GlobProgressBar 로 지정한다.



상속된 변수들에 접근하기 위해 Show Inherited Variabled 를 활성화 시켜준다.

이제 ProgressBarFillImage 를 체력 이미지로 변경가능하다.

MP도 동일한 방법으로 위젯 생성이 가능하다.


테스트용으로 위젯이 정상적으로 스크린에 출력되는지 확인한다.
먼저 AuraUserWidget 파생 위젯 블루프린트 클래스를 생성한다.



Canvas Pannel 을 추가하고 WBP_HealthGlobe 를 캔버스에 드래그드랍한다.
Cansvas Pannel에 직접 드래그드랍하면 크래시가 발생하므로 반드시Hierarchy에 추가
앵커를 바닥기준으로 변경한다.

제대로 적용되는지 확인해보기 위해서 상단의 Open Level Blueprint 를 클릭한 후

Create Widget 노드를 생성하여 연결시켜준다.

컴파일 후 실행하면 위젯이 정상적으로 스크린에 표시되는 것을 확인할 수 있다.

베이스가 되는 블루프린트 변수의 눈모양을 켜두면

WBP_Overlay 에서 값의 변경이 가능하다.

마나 위젯도 동일하게 배치하고 실행하면 아래와 같이 스크린에 표시된다.


정상적으로 출력되는 것을 확인하였으므로 HUD 클래스를 추가하고, 클래스 기반 블루프린트를 생성한 다음 GameMode에 추가하여 화면에 출력한다.
먼저 HUD 클래스를 생성한다.


오버레이 위젯을 화면에 나타내기 위한 변수를 추가한다.
// AuraHUD.h
class UAuraUserWidget;
public:
UPROPERTY()
TObjectPtr<UAuraUserWidget> OverlayWidget;
protected:
virtual void BeginPlay() override;
private:
// 블루프린트에서 UAuraUserWidget 타입만 나타나도록
UPROPERTY(EditAnywhere);
TSubclassOf<UAuraUserWidget> OverlayWidgetClass;
// AuraHUD.cpp
...
#include "UI/Widget/AuraUserWidget.h"
...
void AAuraHUD::BeginPlay()
{
Super::BeginPlay();
// OverlayWidgetClass라고 지정된 블루프린트 클래스 기반으로 UUserWidget 타입의 위젯을 생성
UUserWidget* Widget = CreateWidget<UUserWidget>(GetWorld(), OverlayWidgetClass);
Widget->AddToViewport();
}
코드작성이 완료되었으면 AuraHUD 기반 블루프린트 생성을 진행한다.


OverlayWidgetClass 를 WBP_Overlay 로 지정해주고 컴파일한다.

게임모드 블루프린트를 열고 HUD class : BP_AuraHUD 로 변경한다.

기존에 Open Level Blueprin 에 생성해두었던 위젯을 생성하는 노드는 다 지운다.

실행하면 정상적으로 작동되는 것을 확인할 수 있다.

이전에 생성해둔 WidgetController 클래스인 AuraWidgetController 클래스는 4가지의 키 밸류가 있다.
이 4가지의 변수들을 구조체를 생성하여 보유하도록 수정한다.
// AuraWidgetController.h
class ...;
// 블루프린트에서 사용가능하도록 USTRUCT매크로 사용
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;
};
...
public:
// 구조체의 변수를 통해 기존의 protected 섹션 변수를 초기화하기 위한 함수
// const를 통해 함수 내에서 WCParams의 값 변경 불가능, "&" 을 통한 참조로 복사비용절감, 직접 접근 가능하되 const를 통한 값변경 불가능
UFUNCTION(BlueprintCallable)
void SetWidgetControllerParams(const FWidgetControllerParams& WCParams);
...
// AuraWidgetController.cpp
...
void UAuraWidgetController::SetWidgetControllerParams(const FWidgetControllerParams& WCParams)
{
PlayerController = WCParams.PlayerController;
PlayerState = WCParams.PlayerState;
AbilitySystemComponent = WCParams.AbilitySystemComponent;
AttributeSet = WCParams.AttributeSet;
}
이제 AuraWidgetController 클래스 기반의 OverlayWidgetController 클래스를 생성한다.


AuraHUD 클래스를 통해 화면에 OverlayWidget을 출력하도록 코드를 수정한다.
// AuraHUD.h
...
#include "UI/WidgetController/OverlayWidgetController.h"
class ...;
struct FWidgetControllerParams;
public:
...
// OverlayWidgetController의 4가지 키 밸류를 가져오기 위한 함수
UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams& WCParams);
void InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS);
protected:
// virtual void BeginPlay() override; 코드 내용 InitOverlay()로 옮긴 후 삭제
private:
...
// OverlayWidgetController에 접근하기 위함
UPROPERTY()
TObjectPtr<UOverlayWidgetController> OverlayWidgetController;
UPROPERTY(EditAnywhere)
TSubclassOf<UOverlayWidgetController> OverlayWidgetControllerClass;
// AuraHUD.cpp
...
UOverlayWidgetController* AAuraHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
if(OverlayWidgetController == nullptr)
{
// NewObject : 새로운 UObject를 생성
// this : outer를 AuraHUD 자신이 되도록
OverlayWidgetController = NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);
OverlayWidgetController->SetWidgetControllerParams(WCParams);
return OverlayWidgetController;
}
return OverlayWidgetController;
}
void AAuraHUD::InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
checkf(OverlayWidgetClass, TEXT("Overlay Widget Class uninitialized, pleas fill out BP_AuraHUD"));
checkf(OverlayWidgetControllerClass, TEXT("Overlay Widget Controller Class uninitialized, pleas 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);
Widget->AddToViewport();
}
// void AAuraHUD::BeginPlay()
{
// 코드 전부 InitOverlay로 이동 후 BeginPlay() 삭제
}
간략한 모식도

그림으로도 부족한것 같아 추가 설명 작성
1.AuraWidgetController클래스를 생성하고, 해당 클래스에FWidgetControllerParams구조체를 생성하여 4개의 키 밸류(PlayerController,PlayerState,AbilitySystemComponent,AttributeSet)를 초기화할 수 있도록 함
2.AuraWidgetController클래스를 상속받은OverlayWidgetController클래스를 생성함
3.AuraHUD클래스를 생성함으로써, 스크린에 기존에 생성해둔WBP_OverlayWidget를 나타낼수 있도록 함.
4.AuraHUD클래스의InitOverlay()함수는 뷰포트에WBP_OverlayWidget을 나타내기 위한 함수임
GetWorld()를 통해 현재 월드에서UUserWidget타입의 위젯을 생성하고,OverlayWidgetClass를 통해 동적으로WBP_OverlayWidget을Widget에 할당함Widget변수에 저장된UUserWidget타입의 위젯을UAuraUserWidget타입으로 캐스팅하고, 성공할 경우 캐스팅된 결과를OverlayWidget에 저장함.UUserWidget클래스를 상속하여 추가 기능을 만든 것이UAuraUserWidget클래스,UAuraUserWidget토대로 만든 블루프린트 클래스가WBP_OverlayWidget
단순 위젯 출력을 위해서는Widget을 사용해도 되지만 추후에 해당 위젯에 컨트롤러를 set하기 위해서 캐스팅 필요
FWidgetControllerParams타입의 구조체WidgetControllerParams생성후,InitOveraly()의 파라미터로 받은 4개의 키 밸류를 구조체에 할당함
해당 구조체를GetOverlayWidgetController()함수의 파라미터로 전달하여 받은OverlayWidgetController를WidgetController로 초기화함
GetOverlayWidgetController()함수에서OverlayWidgetController가 유효하지 않을 경우,NewObject()함수의 타입과 파라미터를 통해UOverlayWidgetController타입의OverlayWidgetController를 생성함
생성한OverlayWidgetController에GetOverlayWidgetController()함수의 파라미터로 전달받은 4개의 키 밸류를SetWidgetControllerParams()함수에 전달함으로써 초기화하고OverlayWidgetController를 리턴함
OverlayWidget의 컨트롤러를WidgetController(OverlayWidgetController였던것)로 설정Widget을 뷰포트에 나타나도록 함
Widget은 4번의 세번째 문장에 따라 단순 화면 출력을 위한 변수이므로, 해당 변수를 통해 뷰포트 출력WidgetController를 화면에 출력을 위한Widget이 아닌OverlayWidget에 부착시키기 위해UAuraUserWidget으로 캐스팅을 한 것으로 보임
캐릭터 클래스에는 4가지 키밸류 초기화를 위한 값들이 전부 있다.

캐릭터 클래스에서 InitOverlay() 함수 호출을 통해 키밸류를 전달하여 위젯 컨트롤러가 해당 값들에 대한 접근이 가능하도록 코드를 작성한다.
추가로 HUD 클래스는 PlayerController 클래스를 통해 접근 가능하다.
// Auracharacter.cpp
...
#include "Player/AuraPlayerController.h"
#include "UI/HUD/AuraHUD.h"
...
void AAuraCharacter::InitAbilityActorInfo()
{
...
// check가 아닌 if를 사용하는 이유
// 3인 멀티플레이어에서 자신의 플레이어 컨트롤러는 유효하지만, 다른 두 캐릭터는 유효하지 않을 수 있음
// 그렇기에 플레이어 컨트롤러가 null일수 있고, 이는 멀티플레이어에서 정상적인 상황임
// 플레이어 컨트롤러가 null이 아닌 경우에만 if 조건을 사용하여 코드를 진행시
// 요약 : check 사용시 null일 경우 프로그램이 크래시되기 때문에 if 사용, 해당 케이스에서는 크래시 필요없음
if(AAuraPlayerController* AuraPlayerController = Cast<AAuraPlayerController>(GetController()))
{
// 위와 동일하게 HUD는 로컬플레이어에 대해서만 유효하므로 check 필요 없음
if(AAuraHUD* AuraHUD = Cast<AAuraHUD>(AuraPlayerController->GetHUD()))
{
AuraHUD->InitOverlay(AuraPlayerController, AuraPlayerState, AbilitySystemComponent, AttributeSet);
}
}
}
AuraPlayerController 클래스에도 check가 아닌 if를 사용해도 되는 부분이 동일하게 있으므로 수정한다.
// AuraPlayerController.cpp
...
void AAuraPlayerController::BeginPlay()
{
...
UEnhancedInputLocalPlayerSubsystem* Subsystem = ........
// 플레이어 컨트롤러의 경우, 플레이어가 있는 로컬로 제어되는 곳에만 subsystem이 유효함
// 즉 위의 코드와 동일하게 굳이 check를 사용해서 크래시를 발생시킬 필요가 없음
if(SubSystem)
{
SubSystem->AddMappingContext(AuraContext, 0);
}
}
컴파일후 BP_AuraHUD 에서 Overlay Widget Controller Class : OverlayWidgetController 로 설정해준다.

실행하면 아래와 같이 OverlayWidget 이 스크린에 표시되는 것을 확인할 수 있다.

이제 WidgetController 에서 초기화된 값을 브로드캐스트하도록 구현해야한다.
먼저 상위 클래스인 AuraWidgetController 클래스에 함수를 선언한다.
// AuraWidgetController.h
public:
...
virtual void BroadcastInitialValues();
// AuraWidgetController.cpp
...
void UAuraWidgetController::BroadcastInitialValues()
{
}
이어서 OverlayWidgetController 클래스에서 해당 함수를 override하고 구현부를 작성한다.
// OverlayWidgetController.h
...
// Broadcast를 위한 델리게이트
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature, float, NewHealth);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxHealthChangedSignature, float, NewMaxHealth);
...
// 블루프린트에서 OverlayWidgetController 캐스팅을 위함
UCLASS(BlueprintType, Blueprintable)
...
public:
virtual void BroadcastInitialValues() override;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnHealthChangedSignature OnHealthChanged;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnMaxHealthChangedSignature OnMaxHealthChanged;
// OverlayWidgetController.cpp
...
#include "AbilitySystem/AuraAttributeSet.h"
...
void UOverlayWidgetController::BroadcastInitialValue()
{
// 상위 클래스인 AuraWidgetController의 내용이 비어있으므로 Super 필요없음
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);
OnHealthChanged.Broadcast(AuraAttributeSet->GetHealth());
OnMaxHealthChanged.Broadcast(AuraAttributeSet->GetMaxHealth());
}
최종족으로 HUD에서 브로드캐스트 함수를 호출하도록 한다.
// AuraHUD.cpp
...
void AAuraHUD::InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
...
OverlayWidget->SetWidgetController(WidgetController);
/** 코드 추가 */
WidgetController->BroadcastInitialValues();
/** 코드 추가 */
Widget->AddToViewport();
}
에디터로 돌아와서 WBP_Overlay 블루프린트를 열고 Graph 탭에 가서 저번에 만들어두었던 WidgetControllerSet 함수를 구현하기 위한 노드를 추가한다.

먼저 WBP_HealthGlobe 와 WBP_ManaGlobe 의 IsVariable 을 활성화시킨다.

AuraUserWidget 파생 블루프린트 클래스이므로 Event Widget Controller Set 노드를 호출 가능하다.
해당 노드를 통해 동일한 파생 블루프린트 클래스인 WBP_HealthGlobe 와 WBP_ManaGlobe 의 SetWidgetController() 함수를 호출하도록 하고 파라미터로 WidgetController 를 할당한다.

이어서 Overlay Widget Controller 에 접근하기 위해 OverlayWidgetController 클래스 기반 블루프린트 PB_OverlayWidgetController 를 하나 생성하고


기존에 AuraHUD 블루프린트에 적용시켜둔 OverlayWidgetController c++ 클래스를 방금 생성한 블루프린트로 교체한 다음
(강의 내용따라 정리하다보니 블루프린트 생성이 늦어짐, 그냥 c++ 사용해도 무방하긴 하지만 블루프린트로 정리하는게 깔끔한것 같음)

WBP_HealthGlobe 의 Graph 탭에서 동일하게 Event Widget Controller Set 호출하고 WidgetController 를 캐스팅한다.

As BP Overlay Widget Controller 핀에서 Assign On Health Changed 와 Assign on MaxHealth Changed 노드를 연결한다.

델리게이트를 통해 값이 변경되면 그에 따른 이벤트가 실행가능해진다.

새로 변경되는 값들을 변수로 승격시켜 각각 Health 와 MaxHealth 로 변수명을 변경한다.

이제 WBP_HealthGlobe 와 WBP_ManaGlobe 의 부모 클래스에서 Progress Bar 에 접근 가능한 함수를 하나 만들어야 한다.
(해당 함수를 통해 Health와 MaxHealth를 통한 퍼센트를 얻어서 업데이트 가능함)
부모 블루프린트 클래스인 WBP_GlobeProgressBar 에서 SetProgressBarPercent 라는 함수를 하나 만들고

Input Parameter 로 float 타입의 Percent 를 하나 생성한다.

그리고 ProgressBarGlobe 로부터 SetPercent 함수를 호출하여 퍼센트를 구할 수 있게 한다.
(만약 ProgressBarGlobe 가 보이지 않는다면 Designer 탭의 Hierarchy 에서 ProgressBar_Globe 를 선택하고 Details 패널에서 IsVariable 활성화 여부를 확인한다)

다시 WBP_HealthGlobe 로 돌아와서 퍼센트를 변경시키도록 노드를 추가해주면 된다.

컴파일후 실행시 최대체력 100에 현재체력 100이므로 기존과 다르게 체력바가 꽉 차있는 것을 확인할 수 있다.

추가
체력바 꽉 안찰시AttributeSet클래스에서MaxHealth관련 초기화 했는지 확인
MaxMana도 혹시 모르니 초기화여부 확인
요약
우선 기본적으로 OverlayWidget, HealthGlobe, ManaGlobe는AuraUserWidget로부터 파생된 블루프린트 클래스이므로 셋 다SetWidgetController()및WidgetControllerSet()호출 가능
1.AuraHUD.cpp에서OverlayWidget->SetWidgetController(WidgetController);호출
2.SetWidgetController()함수에 의해OverlayWidget의 위젯 컨트롤러는 파라미터(WidgetController) 값으로 컨트롤러 할당
3. 또한SetWidgetController()함수 내에 있는WidgetControllerSet()함수 호출
4. 에디터의WBP_Overlay에서Event Widget Controller Set노드에 의해WBP_HealthGlobe와WBP_ManaGlobe둘에게서WidgetControllerSet()함수가 호출되고 파라미터로WidgetController전달
5.SetWidgetController()에 의해 HP와 MP 위젯 컨트롤러는 파라미터(WidgetController) 값으로 컨트롤러 할당
6.SetWidgetController()함수 내에서WidgetControllerSet()함수를 호출하므로 에디터의WBP_HealthGlobe와WBP_ManaGlobe에서Event Widget Controller Set노드에 의해BP_OverlayWidgetController에 대해 캐스팅을 시도함
7. 캐스팅 성공시BP_OverlayWidgetController에 접근하여 델리게이트 호출시 event 관련 노드를 추가하여 퍼센트에 의한 체력표시가 가능하게 함
노드정리
1. Sequence1
2. Sequence2
3. Sequence3
위에서 WidgetController를 통해 초기화값을 Broadcast할 수 있게 하였고 이를 통해 AttributeSet 에 캐스트할 수 있으며, AttributeSet 을 통해 Attribute Value 에 접근할 수 있게 했다.
이제 이러한 Attribute의 변화에 따라 ASC에서 변경점을 적용시킬 필요가 있다.
아래 코드는 어떤 함수를 이용해야 하는지에 대한 예시와 설명이다.
// OverlayWidgetController.cpp
...
void UOverlayWidgetController::BroadcastInitialValue()
{
...
// Attribute의 값이 변경될 때 호출되는 델리게이트를 가져오는데 사용
// 함수 내의 파라미터는 AttributeSet에 존재하는 Attribute
// UObject의 멤버 함수를 바인딩할 경우 AddUObject() 사용
// 간단요약 : 캐릭터의 attribute 변경 발생시 델리게이트를 통한 함수바인딩으로 값 변경
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetHealthAttribute()).AddUObject(this, ...);
}
위와 같은 형식으로 코드를 구현한다.
먼저 부모클래스에 함수를 하나 선언한다.
// AuraWidgetController.h
public:
...
virtual void BindCallbacksToDependencies();
// AuraWidgetController.cpp
...
void UAuraWidgetController::BindCallbacksToDependencies()
{
// 공백
}
// OverlayWidgetController.h
...
struct FOnAttributeChangeData;
...
public:
...
// attribute 변경 발생시 델리게이트를 통한 함수 바인딩을 진행할 함수
virtual void BindCallbacksToDependencies() override;
protected:
// 바인딩 함수
void HealthChanged(const FOnAttributeChangeData& Data) const;
void MaxHealthChanged(const FOnAttributeChangeData& Data) const;
// OverlayWidgetController.cpp
void UOverlayWidgetController::BindCallbacksToDependencies()
{
// 부모 클래스에 코드없으므로 Super 필요없음
const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetHealthAttribute()).AddUObject(this, &UOverlayWidgetController::HealthChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxHealthAttribute()).AddUObject(this, &UOverlayWidgetController::MaxHealthChanged);
}
void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const
{
OnHealthChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxHealthChanged(const FOnAttributeChangeData& Data) const
{
OnMaxHealthChanged.Broadcast(Data.NewValue);
}
코드가 제대로 작동하는지 확인하기 위해 체력을 100->50으로 변경한다.
// AuraAttributeSet.cpp
UAuraAttributeSet::UAuraAttributeSet()
{
InitHealth(50.f);
...
}
다음으로 초기화값을 Broadcast하는 AuraHUD 에 코드를 추가한다.
// AuraHUD.cpp
UOverlayWidgetController* AuraHUD::GetOverlayWidgetController(const FWidgetControllerParam& WCParams)
{
if(OverlayWidgetController == nullptr)
{
...
OverlayWidgetController->BindCallbacksToDependencies();
return OverlayWidgetController;
}
...
}
컴파일 후 실행하면 아래와 같이 hp가 반인 것을 확인할 수 있고

포션을 먹으면 25가 회복되어서 75%만큼 hp가 차는 것을 확인할 수 있다.

Mana도 동일하게 코드를 작성해준다.
// OverlayWidgetController.h
...
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnManaChangedSignature, float, NewMana);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxManaChangedSignature, float, NewMaxMana);
...
public:
...
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnManaChangedSignature OnManaChanged;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FOnMaxManaChangedSignature OnMaxManaChanged;
protected:
...
void ManaChanged(const FOnAttributeChangeData& Data);
void MaxManaChanged(const FOnAttributeChangeData& Data);
// OverlayWidgetController.cpp
void UOverlayWidgetController::BroadcastInitialValue()
{
...
OnManaChanged.Broadcast(AuraAttributeSet->GetMana());
OnMaxManaChanged.Broadcast(AuraAttributeSet->GetMaxMana());
void UOverlayWidgetController::BindCallbacksToDependencies()
{
...
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetManaAttribute()).AddUObject(this, &UOverlayWidgetController::ManaChanged);
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxManaAttribute()).AddUObject(this, &UOverlayWidgetController::MaxManaChanged);
}
...
void UOverlayWidgetController::ManaChanged(const FOnAttributeChangeData& Data)
{
OnManaChanged.Broadcast(Data.NewValue);
}
void UOverlayWidgetController::MaxManaChanged(const FOnAttributeChangeData& Data)
{
OnMaxManaChanged.Broadcast(Data.NewValue);
}
이제 포션 위에 캐릭터가 overlap 되었을 때, 마나를 감소시키기 위한 코드를 추가한다.
// AuraEffectActor.cpp
void AAuraEffectActor::OnOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if(...)
{
...
MutableAuraAttributeSet->SetMana(AuraAttributeSet->GetMana() - 25.f);
Destroy();
}
}
에디터로 돌아와서 WBP_ManaGlobe 파일을 열고 Event Widget Controlelr Set 노드를 생성한 뒤 Get Widget Controller 노드를 통해 캐스팅을 시도하고, 결과를 변수로 승격시킨 후 이름을 BP_OverlayWidgetController 로 변경한다.

BP_OverlayWidgetController 로부터 Assign on ManaChanged 와 Assign on MaxManaChanged 노드를 생성하여 연결한다.

New Mana 를 변수로 승격시키고 Mana 로 변경한 뒤, Set Progress Bar Widget 노드를 통해 현재 마나의 퍼센트를 표시하도록 한다.

동일하기 MaxMana 부분도 진행한다.

컴파일 후 실행하고, 포션을 먹으면 마나는 줄어드는 것을 확인할 수 있다.
