UI 입력 처리 및 애니메이션
PlayerController의 입력 모드를 전환하고 위젯 이벤트와 UMG 애니메이션으로 상호작용을 완성합니다.
이전 절에서 UMG의 데이터 바인딩과 HUD 시스템을 통해 게임 데이터를 UI에 표시하는 방법을 알아보았습니다.
이제 플레이어의 입력(Input)을 받아 UI와 상호작용하고, UI에 애니메이션을 적용하여 시각적 피드백과 생동감을 더하는 방법에 대해 살펴보겠습니다.
UI 입력 처리: 플레이어와의 상호작용
게임에서 UI는 단순히 정보를 보여주는 것을 넘어, 플레이어의 클릭, 마우스 오버, 키 입력 등을 받아 반응해야 합니다.
언리얼 엔진은 플레이어 컨트롤러(Player Controller)를 통해 UI 입력을 관리합니다.
입력 모드(Input Mode) 변경
가장 중요한 개념은 입력 모드입니다.
언리얼 엔진은 어떤 종류의 입력을 받아들일지 정하는 세 가지 주요 모드를 제공합니다.
입력 전달 모드와 커서·포커스 설정을 구분합니다
GameAndUI 한 입력→UI 먼저 처리→소비 또는 미처리 게임 기회라는 실제 전달 경로이다. 게임 기회가 공격 완료나 UIOnly 동작을 보장하지 않는다.
UNREAL C++ · CH6-4
입력 전달 모드와 커서·포커스 설정을 구분합니다
세 모드가 입력을 전달하는 대상부터 선택합니다. UIOnly에서는 UI 안에서 닫는 경로와 포커스 대상을 준비해야 하며, 커서 표시와 월드 일시정지는 별도입니다.
이 분기는 GameAndUI만 비교합니다. UIOnly에서는 UI가 처리하지 않아도 같은 게임 전달 경로를 갖지 않습니다. 커서 표시·월드 일시정지·실제 공격 구현은 별도입니다.
| 모드 | 입력 전달 | 별도로 정할 것 |
|---|---|---|
| GameOnly | 플레이어 입력·컨트롤러 | 커서 숨김 여부와 게임 뷰포트 복귀 |
| UIOnly | UI만 처리 | 포커스 가능한 대상과 UI 안의 닫기 경로 |
| GameAndUI | UI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능 | 같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책 |
- GameOnly
- 입력 전달: 플레이어 입력·컨트롤러별도로 정할 것: 커서 숨김 여부와 게임 뷰포트 복귀
- UIOnly
- 입력 전달: UI만 처리별도로 정할 것: 포커스 가능한 대상과 UI 안의 닫기 경로
- GameAndUI
- 입력 전달: UI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능별도로 정할 것: 같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책
bShowMouseCursor는 모드와 별도입니다. GameAndUI는 UI가 처리하지 않은 입력을 게임 쪽에 전달할 수 있습니다. 본문 AttackButton 핸들러는 로그만 출력하며 실제 공격 호출은 주석입니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | GameAndUI 입력 모드에서 한 입력을 UI에 먼저 보낸 뒤 UI가 처리했는지 구별한다. 처리했으면 해당 입력을 소비하며, 처리하지 않았으면 플레이어 입력/컨트롤러에 처리 기회가 있다. 게임 처리 기회는 실제 공격이나 이동 완료를 보장하지 않으며, UIOnly의 미처리 경로와도 다르다. |
| 노드·연결선 라벨 | 같은 게임 동작 보장 X |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 입력 진입 GameAndUI 설정에서 한 입력을 보낸다. |
|
|
| UI 판정 UI가 소비하거나 처리하지 않는다. |
|
|
| 후속 경계 미처리 때 게임 입력 경로가 반응할 수 있다. |
|
|
입력 전달 모드와 커서·포커스 설정을 구분합니다
GameAndUI 한 입력→UI 먼저 처리→소비 또는 미처리 게임 기회라는 실제 전달 경로이다. 게임 기회가 공격 완료나 UIOnly 동작을 보장하지 않는다.
UNREAL C++ · CH6-4
입력 전달 모드와 커서·포커스 설정을 구분합니다
세 모드가 입력을 전달하는 대상부터 선택합니다. UIOnly에서는 UI 안에서 닫는 경로와 포커스 대상을 준비해야 하며, 커서 표시와 월드 일시정지는 별도입니다.
이 분기는 GameAndUI만 비교합니다. UIOnly에서는 UI가 처리하지 않아도 같은 게임 전달 경로를 갖지 않습니다. 커서 표시·월드 일시정지·실제 공격 구현은 별도입니다.
| 모드 | 입력 전달 | 별도로 정할 것 |
|---|---|---|
| GameOnly | 플레이어 입력·컨트롤러 | 커서 숨김 여부와 게임 뷰포트 복귀 |
| UIOnly | UI만 처리 | 포커스 가능한 대상과 UI 안의 닫기 경로 |
| GameAndUI | UI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능 | 같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책 |
- GameOnly
- 입력 전달: 플레이어 입력·컨트롤러별도로 정할 것: 커서 숨김 여부와 게임 뷰포트 복귀
- UIOnly
- 입력 전달: UI만 처리별도로 정할 것: 포커스 가능한 대상과 UI 안의 닫기 경로
- GameAndUI
- 입력 전달: UI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능별도로 정할 것: 같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책
bShowMouseCursor는 모드와 별도입니다. GameAndUI는 UI가 처리하지 않은 입력을 게임 쪽에 전달할 수 있습니다. 본문 AttackButton 핸들러는 로그만 출력하며 실제 공격 호출은 주석입니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | GameAndUI 입력 모드에서 한 입력을 UI에 먼저 보낸 뒤 UI가 처리했는지 구별한다. 처리했으면 해당 입력을 소비하며, 처리하지 않았으면 플레이어 입력/컨트롤러에 처리 기회가 있다. 게임 처리 기회는 실제 공격이나 이동 완료를 보장하지 않으며, UIOnly의 미처리 경로와도 다르다. |
| 노드·연결선 라벨 | 같은 게임 동작 보장 X |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 입력 진입 GameAndUI 설정에서 한 입력을 보낸다. |
|
|
| UI 판정 UI가 소비하거나 처리하지 않는다. |
|
|
| 후속 경계 미처리 때 게임 입력 경로가 반응할 수 있다. |
|
|
APlayerController 클래스에서 입력 모드를 설정합니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MyPlayerController.generated.h"
class UUserWidget;
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 (!IsLocalController()) return;
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를 사용할 때, 키보드 포커스를 받을 위젯을 지정합니다. 대상이 포커스를 받을 수 있어야 하므로 이 예제의 인벤토리 루트에는 Is Focusable을 켜거나 실제 버튼·입력 위젯을 지정합니다.
인벤토리의 닫기 버튼이나 포커스를 받은 위젯의 키 처리에서 소유 컨트롤러의 ToggleInventoryUI를 호출하도록 연결합니다. UIOnly가 된 뒤 게임 입력 바인딩만으로 닫는 경로는 충분하지 않습니다. 입력 모드 전환 자체가 게임을 일시정지시키지는 않습니다.
위젯 이벤트 처리
버튼 클릭, 슬라이더 값 변경 등 사용자 상호작용은 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 NativeOnInitialized() override;
// 버튼 클릭 이벤트에 바인딩될 함수
UFUNCTION()
void OnAttackButtonClicked(); // UFUNCTION으로 선언되어야 함
};#include "MyPlayerHUD.h"
#include "Components/Button.h" // UButton 헤더
void UMyPlayerHUD::NativeOnInitialized()
{
Super::NativeOnInitialized();
// 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 위젯은 이벤트를 플레이어 컨트롤러 등으로 다시 전달하는 역할을 할 수 있습니다.
버튼 예제는 NativeOnInitialized에서 한 번 구독합니다. NativeConstruct마다 구독을 추가하면 화면 재구성 때 중복될 수 있습니다.
UI 애니메이션: 시각적 피드백과 생동감
UMG는 위젯의 속성(위치, 크기, 투명도, 회전 등)을 시간에 따라 변화시키는 강력한 애니메이션 시스템을 내장하고 있습니다.
이는 UI에 생동감과 시각적 피드백을 제공하여 사용자 경험을 향상시킵니다.
UMG 디자이너에서 애니메이션 생성
애니메이션 탭 열기: UMG UI 디자이너의 Designer 탭에서 화면 하단의 Animations 패널을 찾습니다.
새 애니메이션 추가: + Animation 버튼을 클릭하여 새 애니메이션을 생성하고 이름을 지정합니다 (예: FadeIn, ButtonPress).
트랙 추가: 애니메이션을 만들 위젯을 선택하고, + Track 버튼을 클릭하여 해당 위젯을 애니메이션에 추가합니다.
위젯의 Transform (위치, 회전, 크기), Render Opacity (투명도), Color and Opacity (색상), Render Transform의 Angle, 지원되는 진행률 바 속성 등 다양한 속성을 애니메이션화할 수 있습니다.
- 타임라인에서 원하는 시간 지점을 선택합니다.
- 위젯의 해당 속성을 조절하고, 속성 옆의 키 아이콘을 클릭하여 키 프레임을 추가합니다.
- 다른 시간 지점으로 이동하여 속성을 변경하고 다시 키 프레임을 추가하면, 언리얼 엔진이 두 키 프레임 사이의 값을 자동으로 보간하여 애니메이션을 생성합니다.
재생 설정: 애니메이션 패널에서 Looping (반복 재생), Play Rate (재생 속도) 등을 조절할 수 있습니다.
C++에서 UMG 애니메이션 제어
UMG 애니메이션은 위젯 블루프린트 내에 존재하므로, C++에서 애니메이션을 재생하려면 해당 애니메이션에 대한 참조를 얻어야 합니다.
BindWidgetAnim 메타데이터로 디자이너의 같은 이름 애니메이션을 연결하고, 그 포인터를 PlayAnimation에 전달합니다. 아래 두 클래스 예시는 필요한 멤버와 함수를 이전 HUD 클래스에 합쳐 사용합니다.
#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++에서 참조하기 위한 변수
// 디자이너의 같은 이름 애니메이션을 BindWidgetAnim으로 연결합니다.
UPROPERTY(Transient, meta = (BindWidgetAnim), BlueprintReadWrite, Category = "Animations")
UWidgetAnimation* FadeInAnimation; // 디자이너에서 'FadeInAnimation'이라는 이름으로 애니메이션을 만들었다고 가정
protected:
virtual void NativeConstruct() override;
};#include "MyPlayerHUD.h"
#include "Animation/WidgetAnimation.h" // UWidgetAnimation 헤더
void UMyPlayerHUD::NativeConstruct()
{
Super::NativeConstruct();
// BindWidgetAnim으로 연결된 애니메이션을 재생합니다.
if (FadeInAnimation)
{
// 위젯이 생성될 때 페이드인 애니메이션 재생
PlayAnimation(FadeInAnimation);
}
}
// 애니메이션 재생 예시 함수
// 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): 특정 애니메이션을 재생합니다.StopAnimation(UWidgetAnimation* InAnimation)/StopAllAnimations(): 애니메이션을 중지합니다.PauseAnimation(UWidgetAnimation* InAnimation): 애니메이션을 일시 정지합니다.IsAnimationPlaying(UWidgetAnimation* InAnimation): 특정 애니메이션이 현재 재생 중인지 확인합니다.
예시: 버튼 클릭과 화면 재구성을 따로 확인하기
인벤토리를 UIOnly로 열어 커서를 표시했더라도 게임의 닫기 키 바인딩이 다시 실행된다고 가정하지 않습니다. 닫기 버튼의 UI 이벤트에서 Owning Player를 가져와 원 ToggleInventoryUI를 호출하는 경로를 연결합니다. GameAndUI를 선택한 별도 설계에서는 UI가 처리한 입력과 처리하지 않아 플레이어 입력에 기회가 생긴 입력을 구별합니다. 커서가 보인다는 사실만으로 이 두 모드의 전달 방식이 같아지지는 않습니다.
AttackButton 예제의 활성 동작은 로그입니다. 주석의 PerformAttack 호출까지 실제 구현된 공격으로 읽지 않습니다. 또한 NativeOnInitialized의 한 번 구독과 NativeConstruct의 표시·애니메이션 재구성을 나누면, 화면을 다시 구성할 때마다 클릭 콜백을 중복 추가하는 일을 피할 수 있습니다.
한 인스턴스에서 반복되는 것 판단하기
같은 위젯 인스턴스에서 NativeConstruct가 세 번 호출되었고, NativeOnInitialized에서는 한 번만 구독했으며 FadeInAnimation 포인터는 매번 유효하다고 가정합니다. 원 코드의 구독 추가와 PlayAnimation 호출은 각각 몇 번일까요? 또한 GameAndUI에서 UI가 한 입력을 처리했다는 사실만으로 같은 입력의 게임 공격도 실행됐다고 판단해도 될까요?
초기화·구성·입력 소비를 구별한 풀이
구독 추가는 한 번이고 PlayAnimation 호출은 세 번입니다. 이는 주어진 훅 호출 조건의 계산이며 재부착마다 NativeConstruct가 반드시 한 번 발생한다는 실측 주장이 아닙니다. 새 위젯 인스턴스는 자신의 초기화와 구독을 따로 갖습니다. UI가 처리한 입력이 게임 공격까지 갔다고 단정하지 않으며, UI가 처리하지 않은 경우에도 게임 입력 매핑·상태가 맞아야 실제 공격이 이루어집니다.
이 예제의 FadeInAnimation은 구성 시 재생되며, 공격 함수 호출·닫기 애니메이션·완료 후 제거는 구현하지 않았습니다. 종료 연출이 필요하면 완료 이벤트에 제거와 입력 복귀를 연결합니다. 모듈에는 UMG 의존성이 필요합니다.
입력 처리는 플레이어가 게임과 상호작용하는 통로를 제공하고, 애니메이션은 UI에 생동감과 시각적 피드백을 부여하여 사용자 경험을 크게 향상시킵니다.
APlayerController의 입력 모드 전환을 통해 게임 플레이와 UI 상호작용 간의 전환을 매끄럽게 하고, 위젯 이벤트 처리와 UMG 애니메이션 제어는 여러분의 UI를 더욱 풍부하고 반응적으로 만들 것입니다.