1. 모델 - 뷰 - 컨트롤러 모델(MVC 모델)
(1) 위젯 생성

위젯을 생성하고, 백그라운드 이미지로 검은색을 설정한 이후, 애니메이션으로 자연스럽게 페이드 인 되도록 지정한다.
(2) 위젯 컨트롤러 생성
#pragma once
#include "CoreMinimal.h"
#include "UI/WidgetController/AuraWidgetController.h"
#include "GameOverWidgetController.generated.h"
class UAuraUserWidget;
UCLASS(BlueprintType, Blueprintable)
class AURA_API UGameOverWidgetController : public UAuraWidgetController
{
GENERATED_BODY()
public:
virtual void BroadcastInitialValues() override;
virtual void BindCallbacksToDependencies() override;
UFUNCTION()
void HandleOnDeath(AActor* DeadActor);
private:
UPROPERTY()
TObjectPtr<UAuraUserWidget> GameOverWidget;
UPROPERTY(EditAnywhere)
TSubclassOf<UAuraUserWidget> GameOverWidgetClass;
};
HandleOnDeath 콜백 함수를 이용하여 캐릭터가 사망할 때 델리게이트 호출 -> HandleOnDeath 함수 호출의 흐름을 만들 것이다.
(2-1) 델리게이트
AuraCharacterBase 클래스에서 클라이언트 측의 캐릭터를 랙돌처리하는 함수에서 OnDeath 델리게이트를 호출하고 있다.
void AAuraCharacterBase::MulticastHandleDeath_Implementation(const FVector& DeathImpulse)
{
// 랙돌 효과
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BurnDebuffComponent->Deactivate();
StunDebuffComponent->Deactivate();
OnDeath.Broadcast(this);
}
이 델리게이트는 멀티캐스트로 선언되어 있기 때문에, 여기에 게임오버 위젯의 콜백 함수를 바인딩하면 같이 호출될 것이다.
(2-2) 바인딩, 콜백 함수
#include "UI/WidgetController/GameOverWidgetController.h"
#include "AbilitySystem/AuraAbilitySystemComponent.h"
#include "AbilitySystem/AuraAbilitySystemLibrary.h"
#include "Blueprint/UserWidget.h"
#include "Character/AuraCharacter.h"
#include "UI/Widget/AuraUserWidget.h"
void UGameOverWidgetController::BroadcastInitialValues()
{
}
void UGameOverWidgetController::BindCallbacksToDependencies()
{
if (GetAuraASC())
{
if (ICombatInterface* CombatInterface = Cast<ICombatInterface>(GetAuraASC()->GetAvatarActor()))
{
FOnDeath& OnDeathDelegate = CombatInterface->GetOnDeathDelegate();
OnDeathDelegate.AddDynamic(this, &UGameOverWidgetController::HandleOnDeath);
}
}
}
void UGameOverWidgetController::HandleOnDeath(AActor* DeadActor)
{
// 게임오버 위젯 생성
UUserWidget* Widget = CreateWidget<UUserWidget>(GetWorld(), GameOverWidgetClass);
GameOverWidget = Cast<UAuraUserWidget>(Widget);
// 위젯에 위젯 컨트롤러를 연결
GameOverWidget->SetWidgetController(this);
// 기능 실행 및 뷰포트에 추가
BroadcastInitialValues();
Widget->AddToViewport();
}
위젯 컨트롤러가 초기화된 이후에 델리게이트의 참조를 가져와 바인딩한다.
(3) AuraHUD 클래스 재구성
한편, 게임 플레이에 직접적으로 영향을 미치는 게임오버 UI를 표시하기 위해 AuraHUD에서 게임오버 컨트롤러를 생성하고 제어해야 한다.
public:
UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams& WCParams);
UAttributeMenuWidgetController* GetAttributeMenuWidgetController(const FWidgetControllerParams& WCParams);
USpellMenuWidgetController* GetSpellMenuWidgetController(const FWidgetControllerParams& WCParams);
UGameOverWidgetController* GetGameOverWidgetController(const FWidgetControllerParams& WCParams);
UPROPERTY()
TObjectPtr<UGameOverWidgetController> GameOverWidgetController;
UPROPERTY(EditAnywhere)
TSubclassOf<UGameOverWidgetController> GameOverWidgetControllerClass;
};
UGameOverWidgetController* AAuraHUD::GetGameOverWidgetController(const FWidgetControllerParams& WCParams)
{
if (GameOverWidgetController == nullptr)
{ // 없으면 생성
GameOverWidgetController = NewObject<UGameOverWidgetController>(this, GameOverWidgetControllerClass);
GameOverWidgetController->SetWidgetControllerParams(WCParams);
GameOverWidgetController->BindCallbacksToDependencies();
}
return GameOverWidgetController;
}
AuraHUD의 위젯 컨트롤러 Getter 함수들은 없으면 생성하도록 구성되어 있다.
void AAuraHUD::InitOverlay(APlayerController *PC, APlayerState *PS, UAbilitySystemComponent *ASC, UAttributeSet *AS)
{
// 위젯과 위젯 컨트롤러 생성
checkf(OverlayWidgetClass, TEXT("Overlay Widget not Initialized. BP_AuraHUD"));
checkf(OverlayWidgetControllerClass, TEXT("Overlay Widget Controller not Initialized. 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();
// 게임오버 위젯 컨트롤러 생성
GameOverWidgetController = GetGameOverWidgetController(WidgetControllerParams);
}
AuraHUD의 InitOverlay 함수에 따라 오버레이 위젯을 생성하고, 오버레이 위젯과 컨트롤러를 연결하고 있다.
여기에서 게임오버 위젯 컨트롤러를 생성하게 한다.
- 속성 메뉴, 스펠 메뉴 컨트롤러는?
속성 메뉴와 스펠 메뉴의 위젯 컨트롤러는 오버레이 위젯의 버튼이 눌릴 때 생성되고 있다.
2. 타이머 시간 가져오기
타이머는 Aura 캐릭터의 Die 함수가 호출되면, Aura 캐릭터의 DeathTime에 따라 타이머 델리게이트를 생성하여 타이머를 시작한다.
(1) 코드 리펙토링
기존에 플레이어가 사망하면 저장 게임을 불러오던 함수의 이름을 구체적으로 명시하고, 사망하면 남은 시간을 위젯 컨트롤러로 전달하는 함수로 리펙토링한다.
(1-1) AuraCharacter 클래스의 Die 함수
void AAuraCharacter::Die(const FVector& DeathImpulse)
{
// 랙돌 효과 발생
Super::Die(DeathImpulse);
AAuraGameModeBase* AuraGM = Cast<AAuraGameModeBase>(UGameplayStatics::GetGameMode(this));
if (AuraGM == nullptr)
return;
// 타이머 종료 후 저장 데이터를 불러와 게임 재시작
FTimerDelegate DeathTimerDelegate;
DeathTimerDelegate.BindLambda([this, AuraGM]()
{
if (IsValid(AuraGM))
{
AuraGM->RestartGameFromSaveData(this);
}
});
// 타이머 설정
GetWorldTimerManager().SetTimer(DeathTimer, DeathTimerDelegate, DeathTime, false);
// 사망과 관련된 처리들
float RemainingTime = GetWorldTimerManager().GetTimerRemaining(DeathTimer);
AuraGM->PlayerDied(this, RemainingTime);
// 카메라 추락 방지
Camera->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
}
(1-2) AuraGameModeBase의 PlayerDied와 RestartGameFromSaveData 함수
void AAuraGameModeBase::PlayerDied(ACharacter* DeadCharacter, float RemainingTime)
{
if (AAuraPlayerController* AuraPC = Cast<AAuraPlayerController>(DeadCharacter->GetController()))
{
if (UGameOverWidgetController* GameOverWC = UAuraAbilitySystemLibrary::GetGameOverWidgetController(DeadCharacter))
{
GameOverWC->SetRemainingTime(RemainingTime);
}
}
}
void AAuraGameModeBase::RestartGameFromSaveData(ACharacter* DeadCharacter)
{
// 저장 오브젝트 불러와 마지막 저장 확인
ULoadScreenSaveGame* SaveGame = RetrieveInGameSaveData();
if (IsValid(SaveGame) == false)
return;
UGameplayStatics::OpenLevel(DeadCharacter, FName(SaveGame->MapAssetName));
}
(2) GameOver 위젯 컨트롤러의 블루프린트 델리게이트
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FRestartTimer, float, RemainingTime);
UPROPERTY(BlueprintAssignable)
FRestartTimer RestartTimer;
void UGameOverWidgetController::SetRemainingTime(float InRemainingTime)
{
RemainingTime = InRemainingTime;
// 타이머 시작
RestartTimer.Broadcast(InRemainingTime);
}
SetRemainingTime 함수를 호출하면, 해당 함수 내에서 델리게이트를 호출하여 남은 시간을 넘긴다.
(3) WBP_GameOver 위젯에서 델리게이트 바인딩



3. 폴리싱
(1) GameOver UI에서 컨트롤러 조작 모드 변경
WBP_GameOver 이벤트 그래프에 아래 조작을 추가한다.

(2) WBP_GameOver의 타이머 제거

위젯이 파괴될 때 타이머를 제거하도록 처리한다.
(3) 게임오버 UI가 표시되면 오버레이 숨기기

(4) 재시작, 게임 종료 버튼 추가

(4-1) 버튼에 이벤트 바인딩

(4-2) Restart Game 함수
UFUNCTION(BlueprintCallable)
void RestartGame();
void UGameOverWidgetController::RestartGame()
{
AAuraGameModeBase* AuraGM = Cast<AAuraGameModeBase>(UGameplayStatics::GetGameMode(this));
if (AuraGM == nullptr)
return;
AuraGM->RestartGameFromSaveDataWithWorldContextObject(GameOverWidget);
}
기존의 RestartGameFromSaveData는 ACharacter 타입의 포인터를 받는 함수였다.
void AAuraGameModeBase::RestartGameFromSaveData(ACharacter* DeadCharacter)
{
// 저장 오브젝트 불러와 마지막 저장 확인
ULoadScreenSaveGame* SaveGame = RetrieveInGameSaveData();
if (IsValid(SaveGame) == false)
return;
UGameplayStatics::OpenLevel(DeadCharacter, FName(SaveGame->MapAssetName));
}
void AAuraGameModeBase::RestartGameFromSaveDataWithWorldContextObject(UObject* WorldContextObject)
{
// 저장 오브젝트 불러와 마지막 저장 확인
ULoadScreenSaveGame* SaveGame = RetrieveInGameSaveData();
if (IsValid(SaveGame) == false)
return;
UGameplayStatics::OpenLevel(WorldContextObject, FName(SaveGame->MapAssetName));
}
AuraGameModeBase에서 캐릭터가 아닌 오브젝트를 받는 방식의 함수를 생성한다.
- 위젯은 UObject를 상속받는다.
따라서 GetWorld 함수를 사용하는 WorldContextObject로 적합하다.
'UE 5 스터디 > Gameplay Ability System(GAS)' 카테고리의 다른 글
| 32. 로그 라이크 설계 (0) | 2025.06.09 |
|---|---|
| 31-12. 레벨 디자인 - 맵 이동 제어 - 레벨 블루프린트 (0) | 2025.05.20 |
| 31-10. 스펠 계열화 (0) | 2025.05.13 |
| 31-9. UI - 스펠 메뉴 - 마법 공격력 적용 데미지 출력하기 - 태그로 속성 값 가져오기 (0) | 2025.05.02 |
| 31-8. ExecCalc 계산식 수정 - 속성 재조정, 마법 공격력, 어빌리티 마법 공격력 계수 (0) | 2025.04.30 |
