C++과 Unreal Engine으로 3D 게임 개발 13

김여울·2025년 7월 22일
0

내일배움캠프

목록 보기
49/114

📍 4주차 3강

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

12.1 UI 애니메이션 효과 디자인

a. GameOverText, TotalScoreText 추가

위치, 사이즈, 텍스트 수정하고 가운데 정렬까지

b. Text 위젯(UMG) 에 애니메이션 적용

Behavior > Visibility : Hidden
처음에는 안 보이는 형태로 설정
Render Opacity
투명도 조절

Animations

시간 (초)
01
0.50.5
11
1.50.5
21

→ 깜빡이는 애니메이션 생성

시간 (초)
01
10.2
21
30.2
41

→ 더 잘 보이게 변경

c. 애니메이션을 Blueprint 재생 함수로 묶어두기

1️⃣ 애니메이션을 재생시키기 위한 함수 만들기 - PlayGameOverAnim
2️⃣ GameOve TextButton을 변수로 만들기 - ☑ Is Variable
3️⃣ Set Visilbility 보이게 처리함
4️⃣ TotalScore도 Hidden, Is Variable → 숨기고 변수 처리
5️⃣ 함수 완성
6️⃣ 코드에서 PlayerController.cpp 수정

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

a. GameOver 애니메이션 호출

// PlayerController.cpp
// 메인메뉴가 Start? Restart?
if (UTextBlock* ButtonText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName(TEXT("StartButtonText"))))
{
	if (bIsRestart)
	{
		ButtonText->SetText(FText::FromString(TEXT("Restart")));	// 이 때 GameOver Animation 호출해야 함
		
		// 애니메이션 실행
		UFunction* PlayAnimFunc = MainMenuWidgetInstance->FindFunction(FName("PlayGameOverAnim"));
		if (PlayAnimFunc)
		{
			MainMenuWidgetInstance->ProcessEvent(PlayAnimFunc, nullptr);	//PlayAnimFunc: 매개 변수 없는 함수는 두 번째 인자에 nullptr로 호출
		}
	}
	else
	{
		ButtonText->SetText(FText::FromString(TEXT("Start")));
	}
}

b. 최종 점수 표시 제대로 반영

TotalScore 점수 제대로 출력 안 됨

// PlayerController.cpp
/ 메인메뉴가 Start? Restart?
		if (UTextBlock* ButtonText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName(TEXT("StartButtonText"))))
		{
			if (bIsRestart)
			{
				ButtonText->SetText(FText::FromString(TEXT("Restart")));	// 이 때 GameOver Animation 호출해야 함
			}
			else
			{
				ButtonText->SetText(FText::FromString(TEXT("Start")));
			}
		}

		if (bIsRestart)
		{
			// 애니메이션 실행
			UFunction* PlayAnimFunc = MainMenuWidgetInstance->FindFunction(FName("PlayGameOverAnim"));
			if (PlayAnimFunc)
			{
				MainMenuWidgetInstance->ProcessEvent(PlayAnimFunc, nullptr);	//PlayAnimFunc: 매개 변수 없는 함수는 두 번째 인자에 nullptr로 호출
			}

			// 점수 갖고오기
			if (UTextBlock* TotalScoreText = Cast<UTextBlock>(MainMenuWidgetInstance->GetWidgetFromName("TotalScoreText")))
			{
				if (USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(UGameplayStatics::GetGameInstance(this)))
				{
					TotalScoreText->SetText(FText::FromString(
						FString::Printf(TEXT("Total Score: %d"), SpartaGameInstance->TotalScore)
					));
				}
			}
		}

12.3 WidgetComponent로 캐릭터 체력 표시하기

a. WidgetComponent 개념

WidgetComponet

  • UMG로 만든 UI를 3D 월드에 표시할 수 있게 해주는 컴포넌트

특징

  • 2D UI를 3D 공간에 배치하고, 카메라 각도에 따라 UI가 회전하거나 크기가 변함
  • UUserWidget을 3D 공간에 표시 가능

사용법

  • SetWidgetSpace(EWidgetSpace::World): 월드 공간에서 UI 표시
  • SetVisibility(bool bVisible): UI의 가시성 설정 (초기에는 숨기고, 필요시 표시)

활용 예시

  • NPC 머리 위 체력바, 아이템 텍스트 등 3D 공간에 UI 추가

b. Widget Blueprint 준비

위젯 블루프린트 만들기

c. Character 클래스에 WidgetComponent 추가

위젯을 위젯 컴포넌트에 붙이기

  • 2D UI를 3D 공간의 특정 객체 위에다 붙임
  • 2D UI를 3D화 시킴
  1. 스크린 모드 ✅ 이걸 사용!
  • UI가 항상 화면에 고정된 것처럼 보임
  • 플레이어의 카메라 방향과 상관없이 항상 정면에서 보임
  1. 월드 모드
  • 캐릭터의 움직임에 따라 글씨도 이동
  • 옆모습 보고 있으면 글씨 안보임
// Character.h
// CameraComponent 아래에 붙이기
UCLASS()
class SPARTAPROJECT_API ASpartaCharacter : public ACharacter
{ 
public: 
	// ...
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI")
	UWidgetComponent* OverheadWidget;
    
   // ...
   void UpdateOverheadHP();
};

// Character.cpp
ASpartaCharacter::ASpartaCharacter()
{
	// ...
    // Widget 붙이기
	OverheadWidget = CreateDefaultSubobject<UWidgetComponent>(TEXT("OverheadWidget"));
	OverheadWidget->SetupAttachment(GetMesh());	// 코드에는 Mesh 없으니까 가져오기
	OverheadWidget->SetWidgetSpace(EWidgetSpace::Screen);	// screen 모드
}

UpdateOverheadHP(); 아래 함수들에 추가        
void ASpartaCharacter::BeginPlay()
void ASpartaCharacter::AddHealth(float Amount)
float ASpartaCharacter::TakeDamage()


void ASpartaCharacter::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)));
		}
}

d. Character 클래스에서 WidgetComponent 위치 설정

Overhead Widget Component - Widget Class - WBP_HP

User Interface - Space - World으로 변경

위치, 회전, 크기 조절

User Interface - Space - Screen으로 다시 변경

e. 캐릭터 사망하였을 시 처리 로직 추가

// Character.cpp
#include "SpartaGameState.h"

void ASpartaCharacter::OnDeath()
{
	ASpartaGameState* SpartaGameState = GetWorld() ? GetWorld()->GetGameState<ASpartaGameState>() : nullptr;
	if (SpartaGameState)
	{
		SpartaGameState->OnGameOver();
	}
}

// GameState.h
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Level")
float LevelDuration;

// GameState.cpp
void ASpartaGameState::OnGameOver()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (ASpartaPlayerController* SpartaPlayerController = Cast<ASpartaPlayerController>(PlayerController))
		{
			SpartaPlayerController->SetPause(true);	// ✅ 게임이 완전히 정지될 수 있게 게임 자체를 멈추기
			SpartaPlayerController->ShowMainMenu(true);	// Restart bool : true
		}
	}
}

// PlayerController.cpp
void ASpartaPlayerController::StartGame()
{
	// GameInstance에 있는 정보들 초기화
	if (USpartaGameInstance* SpartaGameInstance = Cast<USpartaGameInstance>(UGameplayStatics::GetGameInstance(this)))
	{
		SpartaGameInstance->CurrentLevelIndex = 0;
		SpartaGameInstance->TotalScore = 0;
	}

	// 지금은 UI 전용 Level에 있음
	// BasicLevel 오픈해라~
	UGameplayStatics::OpenLevel(GetWorld(), FName("BasicLevel"));
	SetPause(false);	// ✅ 추가
}

💥 HP 감소 문제 해결

MineItem 오버랩 해도 HP 반영 안 됨
Character.h / Character.cpp 에서 BeginPlay() 없었음....

0개의 댓글