UI 입력 처리 및 애니메이션
이전 절에서 UMG의 데이터 바인딩과 HUD 시스템을 통해 게임 데이터를 UI에 표시하는 방법을 알아보았습니다. 이제 플레이어의 입력(Input) 을 받아 UI와 상호작용하고, UI에 애니메이션을 적용하여 시각적 피드백과 생동감을 더하는 방법에 대해 살펴보겠습니다.
UI 입력 처리: 플레이어와의 상호작용
게임에서 UI는 단순히 정보를 보여주는 것을 넘어, 플레이어의 클릭, 마우스 오버, 키 입력 등을 받아 반응해야 합니다. 언리얼 엔진은 플레이어 컨트롤러(Player Controller) 를 통해 UI 입력을 관리합니다.
입력 모드(Input Mode) 변경
가장 중요한 개념은 입력 모드입니다. 언리얼 엔진은 어떤 종류의 입력을 받아들일지 세 가지 주요 모드를 제공합니다.
FInputModeGameOnly
- 설명: 오직 게임 월드의 입력(캐릭터 이동, 카메라 회전 등)만 받습니다. 마우스 커서는 일반적으로 숨겨집니다.
- 사용 시기: 일반적인 게임 플레이 중.
FInputModeUIOnly
- 설명: 오직 UI의 입력(버튼 클릭, 슬라이더 조작 등)만 받습니다. 게임 월드 입력은 비활성화되고 마우스 커서가 표시됩니다.
- 사용 시기: 일시정지 메뉴, 인벤토리, 옵션 화면 등 UI가 전면에 나와 게임 플레이를 잠시 중단해야 할 때.
FInputModeGameAndUI
- 설명: 게임 월드 입력과 UI 입력 모두를 받습니다. 마우스 커서가 표시되며, UI를 클릭하면 게임 월드 입력이 일시적으로 비활성화될 수 있습니다.
- 사용 시기: HUD 위에 오버레이되는 짧은 알림창, 특정 오브젝트와 상호작용 시 나타나는 UI 등.
C++에서 입력 모드 설정
APlayerController
클래스에서 입력 모드를 설정합니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"
UCLASS()
class MYPROJECT_API AMyPlayerController : public APlayerController
{
GENERATED_BODY()
public:
// 인벤토리 UI를 열고 닫는 함수
UFUNCTION(BlueprintCallable, Category = "UI")
void ToggleInventoryUI();
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
TSubclassOf<UUserWidget> InventoryWidgetClass;
UPROPERTY()
UUserWidget* CurrentInventoryWidget;
};
#include "MyPlayerController.h"
#include "Blueprint/UserWidget.h"
void AMyPlayerController::ToggleInventoryUI()
{
if (CurrentInventoryWidget && CurrentInventoryWidget->IsInViewport())
{
// 인벤토리 UI가 열려 있다면 닫기
CurrentInventoryWidget->RemoveFromParent();
CurrentInventoryWidget = nullptr;
// 게임 입력 모드로 전환
FInputModeGameOnly InputMode;
SetInputMode(InputMode);
bShowMouseCursor = false; // 마우스 커서 숨기기
UE_LOG(LogTemp, Warning, TEXT("Inventory UI Closed. Game Input Mode."));
}
else
{
// 인벤토리 UI가 닫혀 있다면 열기
if (InventoryWidgetClass)
{
CurrentInventoryWidget = CreateWidget<UUserWidget>(this, InventoryWidgetClass);
if (CurrentInventoryWidget)
{
CurrentInventoryWidget->AddToViewport();
// UI 입력 모드로 전환
FInputModeUIOnly InputMode;
InputMode.SetWidgetToFocus(CurrentInventoryWidget->TakeWidget()); // 포커스할 위젯 지정 (선택 사항)
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); // 마우스 커서 뷰포트 잠금 해제 (선택 사항)
SetInputMode(InputMode);
bShowMouseCursor = true; // 마우스 커서 표시
UE_LOG(LogTemp, Warning, TEXT("Inventory UI Opened. UI Input Mode."));
}
}
}
}
SetWidgetToFocus
: FInputModeUIOnly
를 사용할 때, 특정 위젯에 키보드 포커스를 강제로 줄 수 있습니다. 이는 특히 텍스트 입력 필드나 특정 버튼에 즉시 상호작용해야 할 때 유용합니다.
위젯 이벤트 처리
버튼 클릭, 슬라이더 값 변경 등 사용자 상호작용은 UMG 디자이너의 그래프 탭에서 블루프린트 이벤트 노드를 통해 처리하는 것이 일반적입니다. 하지만 C++에서도 이벤트에 직접 함수를 바인딩할 수 있습니다.
C++에서 버튼 클릭 이벤트 처리 예시
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyPlayerHUD.generated.h"
class UButton; // UButton을 사용하기 위해 포함
UCLASS()
class MYPROJECT_API UMyPlayerHUD : public UUserWidget
{
GENERATED_BODY()
public:
UPROPERTY(meta = (BindWidget))
UButton* AttackButton; // UMG 디자이너에서 이 버튼의 이름도 'AttackButton'으로 설정하고 Is Variable 체크
protected:
virtual void NativeConstruct() override;
// 버튼 클릭 이벤트에 바인딩될 함수
UFUNCTION()
void OnAttackButtonClicked(); // UFUNCTION으로 선언되어야 함
};
#include "MyPlayerHUD.h"
#include "Components/Button.h" // UButton 헤더
void UMyPlayerHUD::NativeConstruct()
{
Super::NativeConstruct();
// AttackButton이 유효한지 확인하고 클릭 이벤트에 함수 바인딩
if (AttackButton)
{
// AddDynamic: 델리게이트에 C++ 함수 바인딩
AttackButton->OnClicked.AddDynamic(this, &UMyPlayerHUD::OnAttackButtonClicked);
}
}
void UMyPlayerHUD::OnAttackButtonClicked()
{
UE_LOG(LogTemp, Warning, TEXT("Attack Button Clicked!"));
// 여기에 공격 로직 또는 플레이어 컨트롤러에 공격 명령 전달 로직 구현
// 예: AMyPlayerController* PC = Cast<AMyPlayerController>(GetOwningPlayer());
// if (PC) { PC->PerformAttack(); }
}
이 방식은 특정 위젯의 이벤트를 해당 위젯 C++ 클래스에서 직접 처리할 때 유용합니다. 복잡한 게임플레이 로직은 여전히 플레이어 컨트롤러나 폰(Pawn)/캐릭터에서 처리하는 것이 일반적이므로, UI 위젯은 이벤트를 플레이어 컨트롤러 등으로 다시 전달하는 역할을 할 수 있습니다.
UI 애니메이션: 시각적 피드백과 생동감
UMG는 위젯의 속성(위치, 크기, 투명도, 회전 등)을 시간에 따라 변화시키는 강력한 애니메이션 시스템을 내장하고 있습니다. 이는 UI에 생동감과 시각적 피드백을 제공하여 사용자 경험을 향상시킵니다.
UMG 디자이너에서 애니메이션 생성
애니메이션 탭 열기: UMG UI 디자이너의 Designer
탭에서 화면 하단의 Animations
패널을 찾습니다.
새 애니메이션 추가: + Animation
버튼을 클릭하여 새 애니메이션을 생성하고 이름을 지정합니다 (예: FadeIn
, ButtonPress
).
트랙 추가: 애니메이션을 만들 위젯을 선택하고, + Track
버튼을 클릭하여 해당 위젯을 애니메이션에 추가합니다. 위젯의 Transform
(위치, 회전, 크기), Render Opacity
(투명도), Color and Opacity
(색상), Angle
(슬라이더), Percent
(진행률 바) 등 다양한 속성을 애니메이션화할 수 있습니다.
키 프레임 설정
- 타임라인에서 원하는 시간 지점을 선택합니다.
- 위젯의 해당 속성을 조절하고, 속성 옆의 키 아이콘을 클릭하여 키 프레임을 추가합니다.
- 다른 시간 지점으로 이동하여 속성을 변경하고 다시 키 프레임을 추가하면, 언리얼 엔진이 두 키 프레임 사이의 값을 자동으로 보간하여 애니메이션을 생성합니다.
재생 설정: 애니메이션 패널에서 Looping
(반복 재생), Play Rate
(재생 속도) 등을 조절할 수 있습니다.
C++에서 UMG 애니메이션 제어
UMG 애니메이션은 위젯 블루프린트 내에 존재하므로, C++에서 애니메이션을 재생하려면 해당 애니메이션에 대한 참조를 얻어야 합니다. UUserWidget
은 위젯 블루프린트에 정의된 애니메이션을 이름으로 가져올 수 있는 함수를 제공합니다.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyPlayerHUD.generated.h"
class UWidgetAnimation; // UWidgetAnimation을 사용하기 위해 포함
UCLASS()
class MYPROJECT_API UMyPlayerHUD : public UUserWidget
{
GENERATED_BODY()
public:
// UMG 디자이너에서 만든 애니메이션을 C++에서 참조하기 위한 변수
// 이 변수는 meta=(BindWidgetAnim)이 아니라 GetWidgetAnimationByName 함수로 가져옵니다.
// UPROPERTY로 노출하여 블루프린트에서 디폴트 값으로 할당할 수도 있습니다.
UPROPERTY(Transient, meta = (BindWidgetAnim), BlueprintReadWrite, Category = "Animations")
UWidgetAnimation* FadeInAnimation; // 디자이너에서 'FadeInAnimation'이라는 이름으로 애니메이션을 만들었다고 가정
protected:
virtual void NativeConstruct() override;
private:
// 애니메이션을 이름으로 찾는 헬퍼 함수
UWidgetAnimation* GetAnimationByName(FName AnimationName) const;
};
#include "MyPlayerHUD.h"
#include "Animation/WidgetAnimation.h" // UWidgetAnimation 헤더
void UMyPlayerHUD::NativeConstruct()
{
Super::NativeConstruct();
// UMG 디자이너에서 정의된 애니메이션을 이름으로 찾아서 바인딩합니다.
// BindWidgetAnim 메타데이터는 UMG 에디터에서 자동으로 바인딩해주는 기능입니다.
// 일반적으로는 GetWidgetAnimationByName 함수를 사용하는 것이 더 명확합니다.
FadeInAnimation = GetAnimationByName(TEXT("FadeInAnimation"));
if (FadeInAnimation)
{
// 위젯이 생성될 때 페이드인 애니메이션 재생
PlayAnimation(FadeInAnimation);
}
}
UWidgetAnimation* UMyPlayerHUD::GetAnimationByName(FName AnimationName) const
{
UWidgetAnimation* FoundAnimation = nullptr;
// 모든 위젯 애니메이션을 순회하며 이름으로 찾습니다.
UProperty* Prop = GetClass()->FindPropertyByName(TEXT("WidgetAnimationsMap"));
if (Prop)
{
FMapProperty* MapProp = CastField<FMapProperty>(Prop);
if (MapProp)
{
// 이 부분은 엔진 내부 구조에 의존하므로, 직접 접근하는 것보다
// PlayAnimation(FName AnimationName)을 사용하는 것이 권장됩니다.
// 언리얼 엔진 4.22+ 부터 UUserWidget::GetAnimation(FName AnimationName) 함수가 추가되었습니다.
// 또는 UMG 에디터에서 애니메이션을 변수로 승격(Promote to Variable) 시킬 수 있습니다.
// 가장 간단한 방법 (UE 4.22+):
// return GetAnimation(AnimationName);
// 또는 UPROPERTY(meta=(BindWidgetAnim)) 사용
}
}
// Fallback for older engine versions or if meta=(BindWidgetAnim) is not used.
// Manually iterating properties to find UWidgetAnimation:
for (TFieldIterator<FProperty> PropertyIt(GetClass()); PropertyIt; ++PropertyIt)
{
if (UObjectProperty* ObjectProperty = CastField<UObjectProperty>(*PropertyIt))
{
if (ObjectProperty->PropertyClass->IsChildOf(UWidgetAnimation::StaticClass()))
{
UWidgetAnimation* WidgetAnim = Cast<UWidgetAnimation>(ObjectProperty->GetObjectPropertyValue_InContainer(this));
if (WidgetAnim && WidgetAnim->GetFName() == AnimationName)
{
FoundAnimation = WidgetAnim;
break;
}
}
}
}
return FoundAnimation;
}
// 애니메이션 재생 예시 함수
// UFUNCTION(BlueprintCallable, Category = "UI")
// void PlayMyFadeInAnimation()
// {
// if (FadeInAnimation)
// {
// PlayAnimation(FadeInAnimation); // 기본 재생 속도와 한 번 재생
// }
// }
애니메이션 제어 함수 (UUserWidget
)
PlayAnimation(UWidgetAnimation* InAnimation, float StartAtTime = 0.0f, int32 NumLoopsToPlay = 1, EUMGSequencePlayMode::Type PlayMode = EUMGSequencePlayMode::Forward, float PlaybackSpeed = 1.0f)
: 특정 애니메이션을 재생합니다.PlayAnimation(FName InAnimationName, ...)
: 애니메이션 이름을 직접 전달하여 재생합니다.StopAnimation(UWidgetAnimation* InAnimation)
/StopAllAnimations()
: 애니메이션을 중지합니다.PauseAnimation(UWidgetAnimation* InAnimation)
: 애니메이션을 일시 정지합니다.IsAnimationPlaying(UWidgetAnimation* InAnimation)
: 애니메이션이 재생 중인지 확인합니다.
UPROPERTY(Transient, meta = (BindWidgetAnim))
: 이 메타데이터는 UMG 에디터에서 생성된 애니메이션을 C++ 변수에 자동으로 바인딩해주는 기능을 제공합니다. 이 변수들은 위젯 블루프린트에서 애니메이션을 "변수로 승격(Promote to Variable)"할 필요 없이 자동으로 찾아집니다. Transient
는 이 변수가 저장되지 않고 런타임에만 존재한다는 의미입니다.
마치며
UMG UI의 입력 처리는 플레이어가 게임과 상호작용하는 통로를 제공하고, 애니메이션은 UI에 생동감과 시각적 피드백을 부여하여 사용자 경험을 크게 향상시킵니다. APlayerController
의 입력 모드 전환을 통해 게임 플레이와 UI 상호작용 간의 전환을 매끄럽게 하고, 위젯 이벤트 처리와 UMG 애니메이션 제어는 여러분의 UI를 더욱 풍부하고 반응적으로 만들 것입니다.