UI 애니메이션 효과 및 3D 위젯 UI 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

2026. 6. 12. 09:09·Unreal Engine/UE 기초

 

UI 애니메이션 효과 및 3D 위젯 UI 구현하기

 


 

UI 애니메이션 효과 디자인하기

 

1️⃣ UMG와 Animation에 대해 이해하기

  • 언리얼 엔진에서는 UI를 만들 때 주로 UMG (언리얼 모션 그래픽스, Unreal Motion Graphics)를 사용합니다. UMG에는 Animation 기능이 탑재되어 있어, 예를 들어 버튼이 클릭될 때 색이 바뀌거나, 텍스트가 화면 위로 등장했다가 사라지는 등 다양한 연출을 쉽게 구현할 수 있습니다.
  • Animation 패널
    • UMG 에디터 내에서 메뉴 상단에서 Window - Animations를 클릭하면 애니메이션을 다루는 창이 뜹니다.
    • UMG 에디터 왼쪽 하단 (기본 레이아웃 기준)에 위치한 패널입니다.
    • 여기서 새 애니메이션을 생성하거나, 이미 만든 애니메이션을 선택해 타임라인을 확인할 수 있습니다.

  • Keyframe(키 프레임)
    • 특정 시간대에 UI 속성(크기, 위치, 투명도, 색상 등)을 어떻게 바꿀지를 기록하는 지점입니다.
    • 여러 개의 키 프레임을 연결하여 하나의 애니메이션 타임라인을 완성합니다.

 

UMG 위젯 애니메이팅 UE 공식 문서: https://dev.epicgames.com/documentation/unreal-engine/animating-umg-widgets-in-unreal-engine?lang=ko

 

Animating UMG Widgets in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community

How to create animated UI elements in UMG in Unreal Engine.

dev.epicgames.com

 

 

UMG UI 디자이너 퀵스타트 가이드 UE 공식 문서: https://dev.epicgames.com/documentation/unreal-engine/umg-ui-designer-quick-start-guide-in-unreal-engine?lang=ko

 

UMG UI Designer Quick Start Guide in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community

Getting started with using Unreal Motion Graphics in Unreal Engine.

dev.epicgames.com

 


 

2️⃣ Text 위젯에 애니메이션 적용하기

  • 이제 실제로 “Game Over” 텍스트를 사용해 화면에 서서히 떠올랐다가 사라지는 연출을 만들어 봅시다.
  • 위젯 블루프린트 내에서 텍스트 배치하기
    • WBP_MainMenu를 열어서 디자이너 화면에 TextBlock 하나를 추가해 이름을 GameOverText 라고 하고, 또 하나 TextBlock을 추가해 이름을 TotalScoreText 라고 합시다.
    • 두 TextBlock 모두 기본 Text를 설정해줍니다. (Content - Text 카테고리에 입력)
    • 평소에는 GameOverText와 TotalScoreText를 Hidden (또는 Opacity=0) 상태로 배치합니다.
    • 이 텍스트가 게임 시작 시 바로 보이지 않도록, TextBlock의 Visibility를 Hidden 으로 설정해 둡니다. 우리는 애니메이션으로 텍스트를 천천히 보이게 할 것이므로, 기본 상태를 숨기는 편이 좋습니다.
  • 애니메이션 트랙 생성
    • 에디터 왼쪽 하단의 Animation 패널에서 + Animation 버튼을 클릭해 새 애니메이션을 만듭니다. “Anim_TimeOver” 라고 이름을 붙입니다.

 

  • 아래쪽 타임라인 영역에서 Track을 추가할 수 있는데, 여기서 + Add 를 클릭하고 GameOverText를 선택합니다.

 

  • Keyframe (키 프레임) 설정
    • 추가된 GameOverText에서 옆에 희미하게 보이는 + 버튼을 누르면 추가할 수 있는 트랙들이 있습니다. 여기서 Render Opacity (투명도)를 트랙에 추가하여, 이 값이 시간에 따라 어떻게 변하는지 기록할 수 있도록 설정합니다.
    • 시간 바를 옮기면서 각각 Render Opacity 값을 변경하면 자동으로 트랙에 Key가 등록됩니다.

 


 

3️⃣ 애니메이션을 Blueprint 재생 함수로 묶어두기

  • 우리는 이 애니메이션을 재생하기 위해서는 그 동안의 작성한 게임 로직들 즉, C++에서 UMG의 애니메이션 재생이라는 흐름이 필요합니다. C++에서 블루프린트의 함수를 호출해야하는 반대 상황인것입니다.
  • 이벤트 그래프에서 PlayGameOverAnim Function을 만들어줍니다.
  • 시작시 GameOverText을 visible로 처리하고, Play Animation을 연결합니다. 즉, “PlayTimeOverAnim”을 호출하면 “Anim_TimeOver” 애니메이션이 재생되도록 구성합니다.
  • 애니메이션 재생이 끝나면 자연스럽게 점수가 뜰 수 있도록, TotalScoreText 를 visible 처리를 하도록 연결해줍니다.


 

게임 오버 UI 애니메이션 효과 추가하기

#include "MainPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputSubsystems.h" // Enhanced Input System의 Local Player Subsystem을 사용하기 위해 포함
#include "MainGameState.h"
#include "MainGameInstance.h"
#include "Blueprint/UserWidget.h"
#include "Kismet/GameplayStatics.h"
#include "Components/TextBlock.h"

// 어차피 블루프린트 상에서 전부 다 초기화를 하기 때문에 여기서는 전부 다 nullptr 처리
AMainPlayerController::AMainPlayerController()
	: InputMappingContext(nullptr),
	  MoveAction(nullptr),
	  JumpAction(nullptr),
	  LookAction(nullptr),
	  SprintAction(nullptr),
	  HUDWidgetClass(nullptr),
	  HUDWidgetInstance(nullptr),
	  MainMenuWidgetClass(nullptr),
	  MainMenuWidgetInstance(nullptr)

{
}

void AMainPlayerController::BeginPlay()
{
	Super::BeginPlay();

	// GetLocalPlayer():현재 PlayerController에 연결된 Local Player 객체를 가져옴     
	// Local Player 는 그 플레이어의 입력이나 화면 뷰 같은 것을 관리하는 어떤 객체
	if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
	{
		// Local Player에서 EnhancedInputLocalPlayerSubsystem을 획득
		// UEnhancedInputLocalPlayerSubsystem: 입력 시스템을 관리 (IMC 추가 혹은 삭제하는 역할)
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
			LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
		{
			if (InputMappingContext)
			{
				// Subsystem을 통해 우리가 할당한 IMC를 활성화
				// 우선순위(Priority)는 0이 가장 높은 우선순위
				Subsystem->AddMappingContext(InputMappingContext, 0);
			}
		}
	}

	FString CurrentMapName = GetWorld()->GetMapName();
	if (CurrentMapName.Contains("MenuLevel"))
	{
		ShowMainMenu(false);
	}


	/*// HUD 위젯 생성 및 표시
	if (HUDWidgetClass)
	{
		HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
		if (HUDWidgetInstance)
		{
			HUDWidgetInstance->AddToViewport();
		}
	}

	AMainGameState* MainGameState = GetWorld() ? GetWorld()->GetGameState<AMainGameState>() : nullptr;
	if (MainGameState)
	{
		MainGameState->UpdateHUD();
	}*/
}

UUserWidget* AMainPlayerController::GetHUDWidget() const
{
	return HUDWidgetInstance;
}


// 메뉴 UI 표시
void AMainPlayerController::ShowMainMenu(bool bIsRestart)
{
	// HUD가 켜져 있다면 닫기
	if (HUDWidgetInstance)
	{
		HUDWidgetInstance->RemoveFromParent();
		HUDWidgetInstance = nullptr;
	}

	// 이미 메뉴가 떠 있으면 제거
	if (MainMenuWidgetInstance)
	{
		MainMenuWidgetInstance->RemoveFromParent();
		MainMenuWidgetInstance = nullptr;
	}

	// 메뉴 UI 생성
	if (MainMenuWidgetClass)
	{
		MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
		if (MainMenuWidgetInstance)
		{
			MainMenuWidgetInstance->AddToViewport();

			bShowMouseCursor = true;
			SetInputMode(FInputModeUIOnly());
		}

		if (UTextBlock* ButtonText = Cast<UTextBlock>(
			MainMenuWidgetInstance->GetWidgetFromName(TEXT("StartButtonText"))))
		{
			if (bIsRestart)
			{
				ButtonText->SetText(FText::FromString(TEXT("Restart")));
				
				
			}
			else
			{
				ButtonText->SetText(FText::FromString(TEXT("Start")));
			}
		}
		
		if (bIsRestart)
		{
			UFunction* PlayAnimFunc = MainMenuWidgetInstance->FindFunction(FName("PlayGameOverAnim"));
			if (PlayAnimFunc)
			{
				MainMenuWidgetInstance->ProcessEvent(PlayAnimFunc, nullptr);
			}

			if (UTextBlock* TotalScoreText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName("TotalScoreText")))
			{
				if (UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(UGameplayStatics::GetGameInstance(this)))
				{
					TotalScoreText->SetText(FText::FromString(
						FString::Printf(TEXT("Total Score: %i"), MainGameInstance->TotalScore)
					));
				}
			}
		}
	}
}

// 게임 HUD 표시
void AMainPlayerController::ShowGameHUD()
{
	// HUD가 켜져 있다면 닫기
	if (HUDWidgetInstance)
	{
		HUDWidgetInstance->RemoveFromParent();
		HUDWidgetInstance = nullptr;
	}

	// 이미 메뉴가 떠 있으면 제거
	if (MainMenuWidgetInstance)
	{
		MainMenuWidgetInstance->RemoveFromParent();
		MainMenuWidgetInstance = nullptr;
	}

	if (HUDWidgetClass)
	{
		HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
		if (HUDWidgetInstance)
		{
			HUDWidgetInstance->AddToViewport();

			bShowMouseCursor = false;
			SetInputMode(FInputModeGameOnly());

			AMainGameState* MainGameState = GetWorld() ? GetWorld()->GetGameState<AMainGameState>() : nullptr;
			if (MainGameState)
			{
				MainGameState->UpdateHUD();
			}
		}
	}
}

// 게임 시작 - BasicLevel 오픈, GameInstance 데이터 리셋
void AMainPlayerController::StartGame()
{
	if (UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(UGameplayStatics::GetGameInstance(this)))
	{
		MainGameInstance->CurrentLevelIndex = 0;
		MainGameInstance->TotalScore = 0;
	}

	UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
	SetPause(false);
}

 


 

WidgetComponent로 캐릭터 체력 표시하기

 

1️⃣ WidgetComponent 개념 이해하기

  • WidgetComponent란?
    • UMG (언리얼 모션 그래픽스)로 만든 위젯 (텍스트, 이미지, 버튼 등)을 3D 월드에 붙일 수 있게 해주는 컴포넌트입니다.
      • 예: “NPC 머리 위 체력바”, “아이템 위에 ‘F 키를 누르세요’ 텍스트” 등이 가능해집니다.
    • 언리얼 엔진에서 WidgetComponent를 사용하면, 2D로만 보이던 UI를 공간 내 특정 위치에 붙여 놓고, 카메라 각도에 따라 회전하거나 크기가 달라지는 모습을 만들 수 있습니다.
    • WidgetComponent는 Actor에 부착(Attach)할 수 있는 컴포넌트이며, 특정 UUserWidget(UMG Blueprint 클래스)을 3D 상에 표시하게 해 줍니다.
      • 보통은 SetWidgetSpace(EWidgetSpace::World)로 설정하여, 월드 공간에 UI가 존재하게 만듭니다.
      • 초기 상태에서 SetVisibility(false)로 해 두고, 플레이어가 가까이 왔을 때 SetVisibility(true)로 변경하여 표시할 수 있습니다.

 


 

2️⃣ Widget Blueprint 준비하기

  • Contents Browser에서 UI - Widgets 폴더에서 우클릭하고 User Interface > Widget Blueprint로 Parent는 User Widget을 선택해서 새 위젯 블루프린트를 하나 만듭니다. 이름으로 WBP_HP 라고 하겠습니다.

3️⃣ Character 클래스에 WidgetComponent 추가하기

  • UI 는 보통 2D 인데 3D 화를 시켜서 사용할 경우 2가지 모드가 있습니다: 스크린 vs 월드
    • 스크린: UI 가 화면에 고정 되어 있음. (플레이어의 카메라 방향과 상관없이 항상 정면에서 보인다)
    • 월드: 월드의 캐릭터 움직임에 따라서 글씨도 같이 돌아가게 됩니다.
    • 여기서는 스크린으로 하겠습니다: OverheadWidget->SetWidgetSpace(EWidgetSpace::Screen);
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MainCharacter.generated.h"

// 미리 선언
// 전방 선언(Forward Declaration) 
class USpringArmComponent; // 스프링 암 관련 클래스 헤더
class UCameraComponent; // 카메라 관련 클래스 전방 선언
class UWidgetComponent;

// Enhanced Input에서 액션 값을 받을 때 사용하는 구조체
struct FInputActionValue;

UCLASS()
class BC_CH3_ASSIGNMENT_5_API AMainCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMainCharacter();
	
	virtual void BeginPlay() override;
	
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	USpringArmComponent* SpringArmComp  = nullptr;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	UCameraComponent* CameraComp = nullptr;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI")
	UWidgetComponent* OverheadWidget = nullptr;
	
	// 현재 체력을 가져오는 함수
	UFUNCTION(BlueprintPure, Category = "Health")
	float GetHealth() const;
		
	// 체력을 회복시키는 함수
	UFUNCTION(BlueprintCallable, Category = "Health")
	void AddHealth(float Amount);
	
protected:
	
	// === Variables ===
	// 최대 체력
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Health")
	float  MaxHealth;
	// 현재 체력
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Health")
	float  Health;
	
	// = Movements
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
	float NormalSpeed; // 기본 걷기 속도
	
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
	float SprintSpeedMultiplier;  // "기본 속도" 대비 몇 배로 빠르게 달릴지
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Movement")
	float SprintSpeed; 	// 실제 스프린트 속도 SprintSpeed= NormalSpeed * SprintSpeedMultiplier
	
	
	// =====	
	
	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;	
	
	UFUNCTION()
	void Move(const FInputActionValue& value);
	
	UFUNCTION()
	void Look(const FInputActionValue& value);
	
	// on off 의 형태의 것들은 그냥 나눠 주는 것이 좋다
	// 왜냐하면 이전에는 세세하게 처리하기가 상당이 까다로웠는데
	// EnhancedInputSystem은 그것들을 매우 편하게 변경해주었기 때문에 왠만하면 나눠 주는 것이 좋다
	UFUNCTION()
	void StartJump(const FInputActionValue& value);
	UFUNCTION()
	void StopJump(const FInputActionValue& value);
	
	UFUNCTION()
	void StartSprint(const FInputActionValue& value);
	UFUNCTION()
	void StopSprint(const FInputActionValue& value);
	
	
	// 사망 처리 함수 (체력이 0 이하가 되었을 때 호출)
	UFUNCTION(BlueprintCallable, Category = "Health")
	virtual void OnDeath();
	
	void UpdateOverheadHP();

	// 데미지 처리 함수 - 외부로부터 데미지를 받을 때 호출됨
	// 또는 AActor의 TakeDamage()를 오버라이드
	virtual float TakeDamage(
		float DamageAmount,
		struct FDamageEvent const& DamageEvent, // Damage 관련 추가 정보 (예: Skill 속성에 따른 반응) 
		AController* EventInstigator, //데미지를 발생 시킨 주체
		AActor* DamageCauser // 데미지를 일으킨 오브젝트
		) override;
	
};
#include "MainCharacter.h"
#include "EnhancedInputComponent.h"
//#include "InputActionValue.h"
#include "MainPlayerController.h"

// 카메라, 스프링 암 실제 구현이 필요한 경우라서 include
// 전방 선언(Forward Declaration) 한것 여기서 (실질적으로 사용하는곳) 포함
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h" 

//
#include "GameFramework/CharacterMovementComponent.h" // GetCharacterMovement() 사용을 위해
#include "GameFramework/Actor.h"
#include "Components/WidgetComponent.h"
#include "Components/TextBlock.h"
//#include "Kismet/GameplayStatics.h"

AMainCharacter::AMainCharacter()
{ 	
	PrimaryActorTick.bCanEverTick = false;	
	
	// (1) 스프링 암 생성
	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	// 스프링 암을 루트 컴포넌트 (CapsuleComponent)에 부착
	SpringArmComp->SetupAttachment(RootComponent);
	// 캐릭터와 카메라 사이의 거리 기본값 300으로 설정
	SpringArmComp->TargetArmLength = 300.0f;  
	// 컨트롤러 회전에 따라 스프링 암도 회전하도록 설정
	SpringArmComp->bUsePawnControlRotation = true;  
	
	
	//

	// (2) 카메라 컴포넌트 생성
	CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	// 스프링 암의 소켓 위치에 카메라를 부착
	CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
	// 카메라는 스프링 암의 회전을 따르므로 PawnControlRotation은 꺼둠
	CameraComp->bUsePawnControlRotation = false;
	
	OverheadWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverheadWidget"));
	OverheadWidget->SetupAttachment(GetMesh());
	OverheadWidget->SetWidgetSpace(EWidgetSpace::Screen);
	
	
	// Movements & Sprint Speed	
	NormalSpeed = 600.0f;
	SprintSpeedMultiplier = 1.75f;
	SprintSpeed = NormalSpeed * SprintSpeedMultiplier;

	
	// #include "GameFramework/CharacterMovementComponent.h" 추가해야함
	// MaxWalkSpeed 변경시 캐릭터 이동속도가 즉시 변경
	GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
	
	
	// 초기 체력 설정
	MaxHealth = 100.0f;
	Health = MaxHealth;
}

void AMainCharacter::BeginPlay()
{
	Super::BeginPlay();
	UpdateOverheadHP();
}

// Called to bind functionality to input
void AMainCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	// Enhanced InputComponent로 캐스팅
    if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        // IA를 가져오기 위해 현재 소유 중인 Controller를 AMainPlayerController로 캐스팅
        if (AMainPlayerController* PlayerController = Cast<AMainPlayerController>(GetController()))
        {
        	// null check
            if (PlayerController->MoveAction)
            {
                // IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
                EnhancedInput->BindAction(
                    PlayerController->MoveAction,
                    ETriggerEvent::Triggered,
                    this,
                    &AMainCharacter::Move
                );
            }
            
            if (PlayerController->JumpAction)
            {
                // IA_Jump 액션 키를 "키를 누르고 있는 동안" StartJump() 호출
                EnhancedInput->BindAction(
                    PlayerController->JumpAction,
                    ETriggerEvent::Triggered,
                    this,
                    &AMainCharacter::StartJump
                );
                
                // IA_Jump 액션 키에서 "손을 뗀 순간" StopJump() 호출
                EnhancedInput->BindAction(
                    PlayerController->JumpAction,
                    ETriggerEvent::Completed,
                    this,
                    &AMainCharacter::StopJump
                );
            }
            
            if (PlayerController->LookAction)
            {
                // IA_Look 액션 마우스가 "움직일 때" Look() 호출
                EnhancedInput->BindAction(
                    PlayerController->LookAction,
                    ETriggerEvent::Triggered,
                    this,
                    &AMainCharacter::Look
                );
            }
            
            if (PlayerController->SprintAction)
            {
                // IA_Sprint 액션 키를 "누르고 있는 동안" StartSprint() 호출
                EnhancedInput->BindAction(
                    PlayerController->SprintAction,
                    ETriggerEvent::Triggered,                   
                    this, 
                    &AMainCharacter::StartSprint
                );
                // IA_Sprint 액션 키에서 "손을 뗀 순간" StopSprint() 호출
                EnhancedInput->BindAction(
                    PlayerController->SprintAction, 
                    ETriggerEvent::Completed, 
                    this, 
                    &AMainCharacter::StopSprint
                );
            }    
        }
    }

}

void AMainCharacter::Move(const FInputActionValue& value)
{
	// 컨트롤러가 있어야 방향 계산이 가능
	if (!Controller) return;

	// Value는 Axis2D로 설정된 IA_Move의 입력값 (WASD)을 담고 있음
	// 예) (X=1, Y=0) → 전진 / (X=-1, Y=0) → 후진 / (X=0, Y=1) → 오른쪽 / (X=0, Y=-1) → 왼쪽
	const FVector2D MoveInput = value.Get<FVector2D>();

	// IsNearlyZero
	// 부동소수점들은 딱 0으로 안 떨어 질 수도 있기 때문에 작은 오차들은 0으로 처리 하기 위한 함수
	if (!FMath::IsNearlyZero(MoveInput.X))
	{		
		// 캐릭터가 바라보는 방향(정면)으로 X축 이동
		AddMovementInput(GetActorForwardVector(), MoveInput.X);
	}

	if (!FMath::IsNearlyZero(MoveInput.Y))
	{
		// 캐릭터의 오른쪽 방향으로 Y축 이동
		AddMovementInput(GetActorRightVector(), MoveInput.Y);
	}
}

void AMainCharacter::Look(const FInputActionValue& value)
{
	// 마우스의 X, Y 움직임을 2D 축으로 가져옴
	FVector2D LookInput = value.Get<FVector2D>();

	// X는 좌우 회전 (Yaw), Y는 상하 회전 (Pitch)
	// 좌우 회전
	AddControllerYawInput(LookInput.X);
	
	// 상하 회전
	// IA에서 반전해둔 상태
	// 여기엔 추후 변경 가능한 옵션 추가해주자
	AddControllerPitchInput(LookInput.Y);
}

void AMainCharacter::StartJump(const FInputActionValue& value)
{
	// Jump 함수는 Character가 기본 제공
	if (value.Get<bool>())
	{
		Jump();
	}
}
void AMainCharacter::StopJump(const FInputActionValue& value)
{	
	// StopJumping 함수도 Character가 기본 제공
	if (!value.Get<bool>())
	{
		StopJumping();
	}	
}

void AMainCharacter::StartSprint(const FInputActionValue& value)
{
	// Shift 키를 누른 순간 이 함수가 호출된다고 가정
	// 스프린트 속도를 적용
	if (GetCharacterMovement())
	{
		// 중간에 변경시 
		SprintSpeed = NormalSpeed * SprintSpeedMultiplier;		
		
		GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
	}

}
void AMainCharacter::StopSprint(const FInputActionValue& value)
{
	// Shift 키를 뗀 순간 이 함수가 호출
	// 평상시 속도로 복귀
	if (GetCharacterMovement())
	{
		GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
	}
}


float AMainCharacter::GetHealth() const
{
	return Health;
}

// 체력 회복 함수
void AMainCharacter::AddHealth(float  Amount)
{
	// 체력을 회복시킴. 최대 체력을 초과하지 않도록 제한함
	Health = FMath::Clamp(Health + Amount, 0.0f, MaxHealth);
	UpdateOverheadHP();
	//UE_LOG(LogTemp, Log, TEXT("Health increased to: %f"), Health);
	
	/*
	if (GEngine)
	{ 
		GEngine->AddOnScreenDebugMessage(
				-1, 2.f, FColor::Green,
				 TEXT("Health Increased to: %f"), Health);
	}*/
}

// 데미지 처리 함수
float  AMainCharacter::TakeDamage(float  DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	// 기본 데미지 처리 로직 호출 (필수는 아님)
	int32 ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

	// 체력을 데미지만큼 감소시키고, 0 이하로 떨어지지 않도록 Clamp
	Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
	UpdateOverheadHP();
	//UE_LOG(LogTemp, Warning, TEXT("Health decreased to: %f"), Health);
	/*if (GEngine)
	{ 
		GEngine->AddOnScreenDebugMessage(
				-1, 2.f, FColor::Yellow,
				 TEXT("Health decreased to: %f"), Health);
	}
	*/

	// 체력이 0 이하가 되면 사망 처리
	if (Health <= 0.0f)
	{
		OnDeath();
	}

	// 실제 적용된 데미지를 반환
	return ActualDamage;
}

// 사망 처리 함수
void AMainCharacter::OnDeath()
{
	//UE_LOG(LogTemp, Error, TEXT("Character is Dead!"));
	if (GEngine)
	{ 
		GEngine->AddOnScreenDebugMessage(
				-1, 2.f, FColor::Red,
				 TEXT("Character is Dead!"));
	}

	// 사망 후 로직
}

void AMainCharacter::UpdateOverheadHP()
{
	if (!OverheadWidget) return;
	
	UUserWidget* OverheadWidgetInstance = OverheadWidget->GetUserWidgetObject();
	if (!OverheadWidgetInstance) return;
	
	if (UTextBlock* HPText = Cast<UTextBlock>(OverheadWidgetInstance->GetWidgetFromName(TEXT("OverHeadHP"))))
	{
		HPText->SetText(FText::FromString(FString::Printf(TEXT("%.0f / %.0f"), Health, MaxHealth)));
	}
}

 

BP_MainCharacter 에 Overhead Widget이 잘 추가 되었습니다.

 


 

4️⃣ Character 클래스에서 WidgetComponent 위치 설정

  • BP_MainCharacter 클래스에 들어가서 User Interface 카테고리에서 Widget Class를 WBP_HP로 설정해줍니다.

 

 

 

WBP_HP 를 보면서 위치 조정 할때는 Space를 잠시 World로 하면 됩니다. (수정 후 Screen으로 하셔야 합니다)


 

5️⃣ 캐릭터 사망하였을 시 처리 로직 추가

#include "MainCharacter.h"
#include "EnhancedInputComponent.h"
//#include "InputActionValue.h"
#include "MainPlayerController.h"

// 카메라, 스프링 암 실제 구현이 필요한 경우라서 include
// 전방 선언(Forward Declaration) 한것 여기서 (실질적으로 사용하는곳) 포함
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h" 

//
#include "MainGameState.h"
#include "GameFramework/CharacterMovementComponent.h" // GetCharacterMovement() 사용을 위해
#include "GameFramework/Actor.h"
#include "Components/WidgetComponent.h"
#include "Components/TextBlock.h"
//#include "Kismet/GameplayStatics.h"

AMainCharacter::AMainCharacter()
{ 	
	PrimaryActorTick.bCanEverTick = false;	
	
	// (1) 스프링 암 생성
	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	// 스프링 암을 루트 컴포넌트 (CapsuleComponent)에 부착
	SpringArmComp->SetupAttachment(RootComponent);
	// 캐릭터와 카메라 사이의 거리 기본값 300으로 설정
	SpringArmComp->TargetArmLength = 300.0f;  
	// 컨트롤러 회전에 따라 스프링 암도 회전하도록 설정
	SpringArmComp->bUsePawnControlRotation = true;  
	
	
	//

	// (2) 카메라 컴포넌트 생성
	CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	// 스프링 암의 소켓 위치에 카메라를 부착
	CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
	// 카메라는 스프링 암의 회전을 따르므로 PawnControlRotation은 꺼둠
	CameraComp->bUsePawnControlRotation = false;
	
	OverheadWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverheadWidget"));
	OverheadWidget->SetupAttachment(GetMesh());
	OverheadWidget->SetWidgetSpace(EWidgetSpace::Screen);
	
	
	// Movements & Sprint Speed	
	NormalSpeed = 600.0f;
	SprintSpeedMultiplier = 1.75f;
	SprintSpeed = NormalSpeed * SprintSpeedMultiplier;

	
	// #include "GameFramework/CharacterMovementComponent.h" 추가해야함
	// MaxWalkSpeed 변경시 캐릭터 이동속도가 즉시 변경
	GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
	
	
	// 초기 체력 설정
	MaxHealth = 100.0f;
	Health = MaxHealth;
}

void AMainCharacter::BeginPlay()
{
	Super::BeginPlay();
	UpdateOverheadHP();
}

// Called to bind functionality to input
void AMainCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	// Enhanced InputComponent로 캐스팅
    if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        // IA를 가져오기 위해 현재 소유 중인 Controller를 AMainPlayerController로 캐스팅
        if (AMainPlayerController* PlayerController = Cast<AMainPlayerController>(GetController()))
        {
        	// null check
            if (PlayerController->MoveAction)
            {
                // IA_Move 액션 키를 "키를 누르고 있는 동안" Move() 호출
                EnhancedInput->BindAction(
                    PlayerController->MoveAction,
                    ETriggerEvent::Triggered,
                    this,
                    &AMainCharacter::Move
                );
            }
            
            if (PlayerController->JumpAction)
            {
                // IA_Jump 액션 키를 "키를 누르고 있는 동안" StartJump() 호출
                EnhancedInput->BindAction(
                    PlayerController->JumpAction,
                    ETriggerEvent::Triggered,
                    this,
                    &AMainCharacter::StartJump
                );
                
                // IA_Jump 액션 키에서 "손을 뗀 순간" StopJump() 호출
                EnhancedInput->BindAction(
                    PlayerController->JumpAction,
                    ETriggerEvent::Completed,
                    this,
                    &AMainCharacter::StopJump
                );
            }
            
            if (PlayerController->LookAction)
            {
                // IA_Look 액션 마우스가 "움직일 때" Look() 호출
                EnhancedInput->BindAction(
                    PlayerController->LookAction,
                    ETriggerEvent::Triggered,
                    this,
                    &AMainCharacter::Look
                );
            }
            
            if (PlayerController->SprintAction)
            {
                // IA_Sprint 액션 키를 "누르고 있는 동안" StartSprint() 호출
                EnhancedInput->BindAction(
                    PlayerController->SprintAction,
                    ETriggerEvent::Triggered,                   
                    this, 
                    &AMainCharacter::StartSprint
                );
                // IA_Sprint 액션 키에서 "손을 뗀 순간" StopSprint() 호출
                EnhancedInput->BindAction(
                    PlayerController->SprintAction, 
                    ETriggerEvent::Completed, 
                    this, 
                    &AMainCharacter::StopSprint
                );
            }    
        }
    }

}

void AMainCharacter::Move(const FInputActionValue& value)
{
	// 컨트롤러가 있어야 방향 계산이 가능
	if (!Controller) return;

	// Value는 Axis2D로 설정된 IA_Move의 입력값 (WASD)을 담고 있음
	// 예) (X=1, Y=0) → 전진 / (X=-1, Y=0) → 후진 / (X=0, Y=1) → 오른쪽 / (X=0, Y=-1) → 왼쪽
	const FVector2D MoveInput = value.Get<FVector2D>();

	// IsNearlyZero
	// 부동소수점들은 딱 0으로 안 떨어 질 수도 있기 때문에 작은 오차들은 0으로 처리 하기 위한 함수
	if (!FMath::IsNearlyZero(MoveInput.X))
	{		
		// 캐릭터가 바라보는 방향(정면)으로 X축 이동
		AddMovementInput(GetActorForwardVector(), MoveInput.X);
	}

	if (!FMath::IsNearlyZero(MoveInput.Y))
	{
		// 캐릭터의 오른쪽 방향으로 Y축 이동
		AddMovementInput(GetActorRightVector(), MoveInput.Y);
	}
}

void AMainCharacter::Look(const FInputActionValue& value)
{
	// 마우스의 X, Y 움직임을 2D 축으로 가져옴
	FVector2D LookInput = value.Get<FVector2D>();

	// X는 좌우 회전 (Yaw), Y는 상하 회전 (Pitch)
	// 좌우 회전
	AddControllerYawInput(LookInput.X);
	
	// 상하 회전
	// IA에서 반전해둔 상태
	// 여기엔 추후 변경 가능한 옵션 추가해주자
	AddControllerPitchInput(LookInput.Y);
}

void AMainCharacter::StartJump(const FInputActionValue& value)
{
	// Jump 함수는 Character가 기본 제공
	if (value.Get<bool>())
	{
		Jump();
	}
}
void AMainCharacter::StopJump(const FInputActionValue& value)
{	
	// StopJumping 함수도 Character가 기본 제공
	if (!value.Get<bool>())
	{
		StopJumping();
	}	
}

void AMainCharacter::StartSprint(const FInputActionValue& value)
{
	// Shift 키를 누른 순간 이 함수가 호출된다고 가정
	// 스프린트 속도를 적용
	if (GetCharacterMovement())
	{
		// 중간에 변경시 
		SprintSpeed = NormalSpeed * SprintSpeedMultiplier;		
		
		GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
	}

}
void AMainCharacter::StopSprint(const FInputActionValue& value)
{
	// Shift 키를 뗀 순간 이 함수가 호출
	// 평상시 속도로 복귀
	if (GetCharacterMovement())
	{
		GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
	}
}


float AMainCharacter::GetHealth() const
{
	return Health;
}

// 체력 회복 함수
void AMainCharacter::AddHealth(float  Amount)
{
	// 체력을 회복시킴. 최대 체력을 초과하지 않도록 제한함
	Health = FMath::Clamp(Health + Amount, 0.0f, MaxHealth);
	UpdateOverheadHP();
	//UE_LOG(LogTemp, Log, TEXT("Health increased to: %f"), Health);
	
	/*
	if (GEngine)
	{ 
		GEngine->AddOnScreenDebugMessage(
				-1, 2.f, FColor::Green,
				 TEXT("Health Increased to: %f"), Health);
	}*/
}

// 데미지 처리 함수
float  AMainCharacter::TakeDamage(float  DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	// 기본 데미지 처리 로직 호출 (필수는 아님)
	int32 ActualDamage = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);

	// 체력을 데미지만큼 감소시키고, 0 이하로 떨어지지 않도록 Clamp
	Health = FMath::Clamp(Health - DamageAmount, 0.0f, MaxHealth);
	UpdateOverheadHP();
	//UE_LOG(LogTemp, Warning, TEXT("Health decreased to: %f"), Health);
	/*if (GEngine)
	{ 
		GEngine->AddOnScreenDebugMessage(
				-1, 2.f, FColor::Yellow,
				 TEXT("Health decreased to: %f"), Health);
	}
	*/

	// 체력이 0 이하가 되면 사망 처리
	if (Health <= 0.0f)
	{
		OnDeath();
	}

	// 실제 적용된 데미지를 반환
	return ActualDamage;
}

// 사망 처리 함수
void AMainCharacter::OnDeath()
{
	//UE_LOG(LogTemp, Error, TEXT("Character is Dead!"));
	/*if (GEngine)
	{ 
		GEngine->AddOnScreenDebugMessage(
				-1, 2.f, FColor::Red,
				 TEXT("Character is Dead!"));
	}*/
	
	AMainGameState* MainGameState = GetWorld() ? GetWorld()->GetGameState<AMainGameState>() : nullptr;
	if (MainGameState)
	{
		MainGameState->OnGameOver();
	}
}

void AMainCharacter::UpdateOverheadHP()
{
	if (!OverheadWidget) return;
	
	UUserWidget* OverheadWidgetInstance = OverheadWidget->GetUserWidgetObject();
	if (!OverheadWidgetInstance) return;
	
	if (UTextBlock* HPText = Cast<UTextBlock>(OverheadWidgetInstance->GetWidgetFromName(TEXT("OverHeadHP"))))
	{
		HPText->SetText(FText::FromString(FString::Printf(TEXT("%.0f / %.0f"), Health, MaxHealth)));
	}
}

 

#include "MainGameState.h"
#include "MainGameInstance.h"
#include "MainPlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "SpawnVolume.h"
#include "CoinItem.h"
#include "Components/TextBlock.h"
#include "Blueprint/UserWidget.h"

AMainGameState::AMainGameState()
{
	Score = 0;
	SpawnedCoinCount = 0;
	CollectedCoinCount = 0;
	LevelDuration = 3.0f; // 한 레벨당 30초
	CurrentLevelIndex = 0;
	MaxLevels = 3;
}

void AMainGameState::BeginPlay()
{
	Super::BeginPlay();

	// 게임 시작 시 첫 레벨부터 진행
	StartLevel();
	
	//UpdateHUD();
	
	GetWorldTimerManager().SetTimer(
			HUDUpdateTimerHandle,
			this,
			&AMainGameState::UpdateHUD,
			0.1f,
			true
		);
}

int32 AMainGameState::GetScore() const
{
	return Score;
}

void AMainGameState::AddScore(int32 Amount)
{
	if (UGameInstance* GameInstance = GetGameInstance())
	{
		UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(GameInstance);
		if (MainGameInstance)
		{
			MainGameInstance->AddToScore(Amount);
		}
	}
}


void AMainGameState::StartLevel()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (AMainPlayerController* MainPlayerController = Cast<AMainPlayerController>(PlayerController))
		{
			MainPlayerController->ShowGameHUD();
		}
	}

	
	if (UGameInstance* GameInstance = GetGameInstance())
	{
		UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(GameInstance);
		if (MainGameInstance)
		{
			CurrentLevelIndex = MainGameInstance->CurrentLevelIndex;
		}
	}

	// 레벨 시작 시, 코인 개수 초기화
	SpawnedCoinCount = 0;
	CollectedCoinCount = 0;
	// 현재 맵에 배치된 모든 SpawnVolume을 찾아 아이템 40개를 스폰
	TArray<AActor*> FoundVolumes;
	UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnVolume::StaticClass(), FoundVolumes);
	const int32 ItemToSpawn = 40;

	for (int32 i = 0; i < ItemToSpawn; i++)
	{
		if (FoundVolumes.Num() > 0)
		{
			ASpawnVolume* SpawnVolume = Cast<ASpawnVolume>(FoundVolumes[0]);
			if (SpawnVolume)
			{
				AActor* SpawnedActor = SpawnVolume->SpawnRandomItem();
				// 만약 스폰된 액터가 코인 타입이라면 SpawnedCoinCount 증가
				if (SpawnedActor && SpawnedActor->IsA(ACoinItem::StaticClass()))
				{
					SpawnedCoinCount++;
				}
			}
		}
	}
	// 30초 후에 OnLevelTimeUp()가 호출되도록 타이머 설정
	GetWorldTimerManager().SetTimer(
		LevelTimerHandle,
		this,
		&AMainGameState::OnLevelTimeUp,
		LevelDuration,
		false
	);
	/*UE_LOG(LogTemp, Warning, TEXT("Level %d Start!, Spawned %d coin"),
	       CurrentLevelIndex + 1,
	       SpawnedCoinCount);*/
}

void AMainGameState::OnLevelTimeUp()
{
	// 시간이 다 되면 레벨을 종료
	EndLevel();
}

void AMainGameState::OnCoinCollected()
{
	CollectedCoinCount++;

	/*UE_LOG(LogTemp, Warning, TEXT("Coin Collected: %d / %d"),
	       CollectedCoinCount,
	       SpawnedCoinCount)*/

	// 현재 레벨에서 스폰된 코인을 전부 주웠다면 즉시 레벨 종료
	if (SpawnedCoinCount > 0 && CollectedCoinCount >= SpawnedCoinCount)
	{
		EndLevel();
	}
}

void AMainGameState::EndLevel()
{	
	
	// 타이머 해제
	GetWorldTimerManager().ClearTimer(LevelTimerHandle);
	// 다음 레벨 인덱스로
	//CurrentLevelIndex++;
	
	if (UGameInstance* GameInstance = GetGameInstance())
	{
		UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(GameInstance);
		if (MainGameInstance)
		{
			AddScore(Score);
			CurrentLevelIndex++;
			MainGameInstance->CurrentLevelIndex = CurrentLevelIndex;
		}
	}

	// 모든 레벨을 다 돌았다면 게임 오버 처리
	if (CurrentLevelIndex >= MaxLevels)
	{
		OnGameOver();
		return;
	}

	// 레벨 맵 이름이 있다면 해당 맵 불러오기
	if (LevelMapNames.IsValidIndex(CurrentLevelIndex))
	{
		UGameplayStatics::OpenLevel(GetWorld(), LevelMapNames[CurrentLevelIndex]);
	}
	else
	{
		// 맵 이름이 없으면 게임오버
		OnGameOver();
	}
}

void AMainGameState::OnGameOver()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (AMainPlayerController* MainPlayerController = Cast<AMainPlayerController>(PlayerController))
		{
			MainPlayerController->SetPause(true);
			MainPlayerController->ShowMainMenu(true);
		}
	}
	
	//UpdateHUD();
	//UE_LOG(LogTemp, Warning, TEXT("Game Over!!"));
	
}

void AMainGameState::UpdateHUD()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		AMainPlayerController* MainPlayerController = Cast<AMainPlayerController>(PlayerController);
		{
			if (UUserWidget* HUDWidget = MainPlayerController->GetHUDWidget())
			{
				// requires
				// #include "Components/TextBlock.h"
				// #include "Blueprint/UserWidget.h"
				
				//추후 변경 방안 
				// UPROPERTY(meta = (BindWidget))
				// class UButton* MyAwesomeButton;
				if (UTextBlock* TimeText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Time"))))
				{
					float RemainingTime = GetWorldTimerManager().GetTimerRemaining(LevelTimerHandle);
					TimeText->SetText(FText::FromString(FString::Printf(TEXT("Time: %.1f"), RemainingTime)));
				}
				
				//
				if (UTextBlock* ScoreText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Score"))))
				{
					if (UGameInstance* GameInstance = GetGameInstance())
					{
						UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(GameInstance);
						if (MainGameInstance)
						{
							ScoreText->SetText(FText::FromString(FString::Printf(TEXT("Score: %i"), MainGameInstance->TotalScore)));
						}
					}
				}
				
				if (UTextBlock* LevelIndexText = Cast<UTextBlock>(HUDWidget->GetWidgetFromName(TEXT("Level"))))
				{
					LevelIndexText->SetText(FText::FromString(FString::Printf(TEXT("Level: %d"), CurrentLevelIndex + 1)));
				}
			}
		}
	}
}

 

#include "MainPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputSubsystems.h" // Enhanced Input System의 Local Player Subsystem을 사용하기 위해 포함
#include "MainGameState.h"
#include "MainGameInstance.h"
#include "Blueprint/UserWidget.h"
#include "Kismet/GameplayStatics.h"
#include "Components/TextBlock.h"

// 어차피 블루프린트 상에서 전부 다 초기화를 하기 때문에 여기서는 전부 다 nullptr 처리
AMainPlayerController::AMainPlayerController()
	: InputMappingContext(nullptr),
	  MoveAction(nullptr),
	  JumpAction(nullptr),
	  LookAction(nullptr),
	  SprintAction(nullptr),
	  HUDWidgetClass(nullptr),
	  HUDWidgetInstance(nullptr),
	  MainMenuWidgetClass(nullptr),
	  MainMenuWidgetInstance(nullptr)

{
}

void AMainPlayerController::BeginPlay()
{
	Super::BeginPlay();

	// GetLocalPlayer():현재 PlayerController에 연결된 Local Player 객체를 가져옴     
	// Local Player 는 그 플레이어의 입력이나 화면 뷰 같은 것을 관리하는 어떤 객체
	if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
	{
		// Local Player에서 EnhancedInputLocalPlayerSubsystem을 획득
		// UEnhancedInputLocalPlayerSubsystem: 입력 시스템을 관리 (IMC 추가 혹은 삭제하는 역할)
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
			LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
		{
			if (InputMappingContext)
			{
				// Subsystem을 통해 우리가 할당한 IMC를 활성화
				// 우선순위(Priority)는 0이 가장 높은 우선순위
				Subsystem->AddMappingContext(InputMappingContext, 0);
			}
		}
	}

	FString CurrentMapName = GetWorld()->GetMapName();
	if (CurrentMapName.Contains("MenuLevel"))
	{
		ShowMainMenu(false);
	}


	/*// HUD 위젯 생성 및 표시
	if (HUDWidgetClass)
	{
		HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
		if (HUDWidgetInstance)
		{
			HUDWidgetInstance->AddToViewport();
		}
	}

	AMainGameState* MainGameState = GetWorld() ? GetWorld()->GetGameState<AMainGameState>() : nullptr;
	if (MainGameState)
	{
		MainGameState->UpdateHUD();
	}*/
}

UUserWidget* AMainPlayerController::GetHUDWidget() const
{
	return HUDWidgetInstance;
}


// 메뉴 UI 표시
void AMainPlayerController::ShowMainMenu(bool bIsRestart)
{
	// HUD가 켜져 있다면 닫기
	if (HUDWidgetInstance)
	{
		HUDWidgetInstance->RemoveFromParent();
		HUDWidgetInstance = nullptr;
	}

	// 이미 메뉴가 떠 있으면 제거
	if (MainMenuWidgetInstance)
	{
		MainMenuWidgetInstance->RemoveFromParent();
		MainMenuWidgetInstance = nullptr;
	}

	// 메뉴 UI 생성
	if (MainMenuWidgetClass)
	{
		MainMenuWidgetInstance = CreateWidget<UUserWidget>(this, MainMenuWidgetClass);
		if (MainMenuWidgetInstance)
		{
			MainMenuWidgetInstance->AddToViewport();

			bShowMouseCursor = true;
			SetInputMode(FInputModeUIOnly());
		}

		if (UTextBlock* ButtonText = Cast<UTextBlock>(
			MainMenuWidgetInstance->GetWidgetFromName(TEXT("StartButtonText"))))
		{
			if (bIsRestart)
			{
				ButtonText->SetText(FText::FromString(TEXT("Restart")));
				
				
			}
			else
			{
				ButtonText->SetText(FText::FromString(TEXT("Start")));
			}
		}
		
		if (bIsRestart)
		{
			UFunction* PlayAnimFunc = MainMenuWidgetInstance->FindFunction(FName("PlayGameOverAnim"));
			if (PlayAnimFunc)
			{
				MainMenuWidgetInstance->ProcessEvent(PlayAnimFunc, nullptr);
			}

			if (UTextBlock* TotalScoreText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName("TotalScoreText")))
			{
				if (UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(UGameplayStatics::GetGameInstance(this)))
				{
					TotalScoreText->SetText(FText::FromString(
						FString::Printf(TEXT("Total Score: %i"), MainGameInstance->TotalScore)
					));
				}
			}
		}
	}
}

// 게임 HUD 표시
void AMainPlayerController::ShowGameHUD()
{
	// HUD가 켜져 있다면 닫기
	if (HUDWidgetInstance)
	{
		HUDWidgetInstance->RemoveFromParent();
		HUDWidgetInstance = nullptr;
	}

	// 이미 메뉴가 떠 있으면 제거
	if (MainMenuWidgetInstance)
	{
		MainMenuWidgetInstance->RemoveFromParent();
		MainMenuWidgetInstance = nullptr;
	}

	if (HUDWidgetClass)
	{
		HUDWidgetInstance = CreateWidget<UUserWidget>(this, HUDWidgetClass);
		if (HUDWidgetInstance)
		{
			HUDWidgetInstance->AddToViewport();

			bShowMouseCursor = false;
			SetInputMode(FInputModeGameOnly());

			AMainGameState* MainGameState = GetWorld() ? GetWorld()->GetGameState<AMainGameState>() : nullptr;
			if (MainGameState)
			{
				MainGameState->UpdateHUD();
			}
		}
	}
}

// 게임 시작 - BasicLevel 오픈, GameInstance 데이터 리셋
void AMainPlayerController::StartGame()
{
	if (UMainGameInstance* MainGameInstance = Cast<UMainGameInstance>(UGameplayStatics::GetGameInstance(this)))
	{
		MainGameInstance->CurrentLevelIndex = 0;
		MainGameInstance->TotalScore = 0;
	}

	UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
	SetPause(false);
}

 


 

 

0

 


 

 

Conclustion

 

 

 

추천

 

UMG 위젯 애니메이팅 UE 공식 문서: https://dev.epicgames.com/documentation/unreal-engine/animating-umg-widgets-in-unreal-engine?lang=ko

 

Animating UMG Widgets in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community

How to create animated UI elements in UMG in Unreal Engine.

dev.epicgames.com

 

UMG UI 디자이너 퀵스타트 가이드 UE 공식 문서: https://dev.epicgames.com/documentation/unreal-engine/umg-ui-designer-quick-start-guide-in-unreal-engine?lang=ko

 

UMG UI Designer Quick Start Guide in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community

Getting started with using Unreal Motion Graphics in Unreal Engine.

dev.epicgames.com

 


[Unreal Engine/UE 기초] - 인터페이스 기반 아이템 클래스 설계하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

 

인터페이스 기반 아이템 클래스 설계하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

인터페이스 기반 아이템 클래스 설계하기 인터페이스 이해하기1️⃣ 인터페이스란?인터페이스 (Interface)란 클래스 (또는 오브젝트)가 반드시 구현해야 할 함수 목록만을 미리 정의해 두고, 실제

devcol.tistory.com

 

[Unreal Engine/UE 기초] - 충돌 이벤트로 획득되는 아이템 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

 

충돌 이벤트로 획득되는 아이템 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

충돌 이벤트로 획득되는 아이템 구현하기 이전 포스팅에서는 클래스 구조만 짜고 로직은 비여있던 상태.이전 포스팅: [Unreal Engine/UE 기초] - 인터페이스 기반 아이템 클래스 설계하기 | [언리얼

devcol.tistory.com

 

[Unreal Engine/UE 기초] - 아이템 스폰 및 레벨 데이터 관리하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

 

아이템 스폰 및 레벨 데이터 관리하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

아이템 스폰 및 레벨 데이터 관리하기 랜덤 위치에 아이템 스폰하기 1️⃣ 레벨 셋팅하기Resources 폴더에 Maps 폴더에는 3개의 레벨이 이미 존재합니다. 각각 난이도에 따라서 크기가 다른 맵이고,

devcol.tistory.com

 

[Unreal Engine/UE 기초] - 캐릭터 체력 및 점수 관리 시스템 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

 

캐릭터 체력 및 점수 관리 시스템 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

캐릭터 체력 및 점수 관리 시스템 구현하기 캐릭터 체력 시스템 구현하기 1️⃣ 캐릭터 클래스에 체력 변수 및 함수 선언PlayerState를 쓰지 않는 이유PlayerState: 각 플레이어마다의 어떤 정보를 관

devcol.tistory.com

 

[Unreal Engine/UE 기초] - 게임 루프 설계를 통한 게임 흐름 제어하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

 

게임 루프 설계를 통한 게임 흐름 제어하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

게임 루프 설계를 통한 게임 흐름 제어하기 GameState를 이용한 게임 루프 구현하기 게임루프: 보통 게임의 핵심적인 흐름을 얘기합니다. 즉 게임이 시작할 때부터 종료까지 수행하는 단계들게임

devcol.tistory.com

 


[Unreal Engine/UE 기초] - UI 위젯 설계와 실시간 데이터 연동하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

 

UI 위젯 설계와 실시간 데이터 연동하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

UI 위젯 설계와 실시간 데이터 연동하기 | [언리얼 엔진 C++ (Unreal Engine C++)] UMG (User Widget) 위젯 기초 디자인 이해하기 1️⃣ HUD (Heads-Up Display)란?HUD는 게임 내에서 플레이어에게 정보를 제공하기 위

devcol.tistory.com

 

[Unreal Engine/UE 기초] - 게임 흐름에 맞춘 메뉴 UI 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

 

게임 흐름에 맞춘 메뉴 UI 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]

게임 흐름에 맞춘 메뉴 UI 구현하기 게임 메뉴 UI 디자인하기 1️⃣ 메뉴 위젯 생성하고 버튼 추가하기그동안은 게임 중에 나오는 위젯을 설계했다면, 이번에는 게임을 일시적으로 중단하고 시작

devcol.tistory.com

 

 

 

[페이지] Unreal Engine | 언리얼 엔진

 

 

저작자표시 동일조건 (새창열림)
'Unreal Engine/UE 기초' 카테고리의 다른 글
  • 파티클과 사운드로 게임 효과 연출하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
  • 게임 흐름에 맞춘 메뉴 UI 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
  • UI 위젯 설계와 실시간 데이터 연동하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
  • 게임 루프 설계를 통한 게임 흐름 제어하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
DevCol
DevCol
DevCol (Development Collaboration). 함께 개발 & 공부 & IT 정보 나눔장소
  • DevCol
    DevCol (Development Collaboration)
    DevCol
  • 블로그 메뉴

    • Unreal Engine
    • TIL
    • 게임국가기술자격검정 게임프로그래밍전문가 [한국콘텐츠진흥원]
    • 분류 전체보기 (73) N
      • Unreal Engine (31) N
        • Project (2) N
        • Dev Log (0)
        • Debugging (2) N
        • Blueprint (1)
        • UE 기초 (25) N
        • UE 심화 (0)
        • TA (1) N
      • Programming Language (0)
        • C++ (0)
        • C# (0)
      • Unity Engine (0)
      • 자격증 (3)
        • 게임국가기술자격검정 [한국콘텐츠진흥원] (3)
      • Coding Test | 코딩테스트 (0)
        • 프로그래머스 기초 (0)
        • 프로그래머스 입문 (0)
      • TIL (38) N
        • Boot Camp (32) N
      • Git & Github (1)
  • 링크

    • Youtube
    • GitHub
    • itch.io
    • Blog (En)
  • 공지사항

  • 인기 글

  • 태그

    UE
    C++
    Unreal engine
    프로그래밍
    cpp
    언리얼 엔진
    til
    Programming
    Boot Camp
    게임 개발
    게임개발
    Devlog
    코드카타
    내일배움캠프
    Code Kata
    c
    UE5
    코드 카타
    Game Dev
    기초
  • 최근 글

  • GitHub Youtube itch
  • hELLO · Designed By 정상우.v4.10.6
  • DevCol
    UI 애니메이션 효과 및 3D 위젯 UI 구현하기 | [언리얼 엔진 C++ (Unreal Engine C++)]
    상단으로

    티스토리툴바