본문 바로가기

UI 입력 처리 및 애니메이션

PlayerController의 입력 모드를 전환하고 위젯 이벤트와 UMG 애니메이션으로 상호작용을 완성합니다.

인벤토리를 열었는데 마우스를 움직이면 캐릭터의 시점이 따라 돌고, 아이템을 클릭하면 총이 발사된다면 UI를 쓸 수 없습니다. 반대로 인벤토리를 닫았는데도 마우스 커서가 남아 있고 키 입력이 게임에 전달되지 않는다면 게임으로 돌아올 수 없습니다. UI가 화면에 있는 동안 입력을 누가 먼저 받을지 정하는 일은 UI를 만드는 것만큼 중요합니다. 그리고 창이 갑자기 나타나는 것보다 부드럽게 나타나면 플레이어는 무슨 일이 일어났는지 더 쉽게 알아챕니다.

데이터 바인딩과 HUD 시스템에서 게임 값을 화면에 표시했으니, 이 글에서는 반대 방향으로 플레이어의 입력을 UI가 받는 방법을 다룹니다. 입력 모드(input mode)로 입력의 전달 대상을 정하고, 버튼 클릭 같은 위젯 이벤트를 C++에서 받고, UMG 애니메이션을 C++에서 재생하는 방법을 차례로 살펴봅니다.


입력 모드: 입력을 누가 받을까

UI는 정보를 보여 주는 데서 그치지 않고 플레이어의 클릭, 마우스 오버, 키 입력에 반응해야 합니다. 언리얼 엔진은 플레이어 컨트롤러를 통해 UI 입력을 관리하며, 그 중심에는 어떤 쪽이 입력을 받을지 정하는 입력 모드가 있습니다. 입력 모드는 세 가지입니다.

입력 전달 모드와 커서·포커스 설정을 구분합니다

GameAndUI 한 입력→UI 먼저 처리→소비 또는 미처리 게임 기회라는 실제 전달 경로이다. 게임 기회가 공격 완료나 UIOnly 동작을 보장하지 않는다.

UNREAL C++ · CH6-4

입력 전달 모드와 커서·포커스 설정을 구분합니다

세 모드가 입력을 전달하는 대상부터 선택합니다. UIOnly에서는 UI 안에서 닫는 경로와 포커스 대상을 준비해야 하며, 커서 표시와 월드 일시정지는 별도입니다.

GameAndUI는 UI 처리 여부에 따라 게임 입력 기회가 달라진다GameAndUI입력모드에서한입력을UI에먼저보낸뒤UI가처리했는지구별한다. 처리했으면해당입력을소비하며,처리하지않았으면플레이어입력/컨트롤러에처리기회가있다. 게임처리기회가실제공격이나이동완료를보장하지않고UIOnly의미처리경로와도다르다.한 입력GameAndUI 선택UI 먼저 처리포커스 · 대상 확인처리했는가?입력 처리 결과예아니요UI에서 소비같은 게임 동작 보장X게임 입력 기회매핑 · 상태에 따라 처리
GameAndUI는 UI 처리 여부에 따라 게임 입력 기회가 달라진다GameAndUI입력모드에서한입력을UI에먼저보낸뒤UI가처리했는지구별한다. 처리했으면해당입력을소비하며,처리하지않았으면플레이어입력/컨트롤러에처리기회가있다. 게임처리기회가실제공격이나이동완료를보장하지않고UIOnly의미처리경로와도다르다.한 입력GameAndUI 선택UI 먼저 처리포커스 · 대상 확인처리했는가?입력 처리 결과예아니요UI에서 소비같은 게임 동작 보장X게임 입력 기회매핑 · 상태에 따라 처리

이 분기는 GameAndUI만 비교합니다. UIOnly에서는 UI가 처리하지 않아도 같은 게임 전달 경로를 갖지 않습니다. 커서 표시·월드 일시정지·실제 공격 구현은 별도입니다.

입력 전달과 별도 설정
모드입력 전달별도로 정할 것
GameOnly플레이어 입력·컨트롤러커서 숨김 여부와 게임 뷰포트 복귀
UIOnlyUI만 처리포커스 가능한 대상과 UI 안의 닫기 경로
GameAndUIUI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책
GameOnly
입력 전달: 플레이어 입력·컨트롤러
별도로 정할 것: 커서 숨김 여부와 게임 뷰포트 복귀
UIOnly
입력 전달: UI만 처리
별도로 정할 것: 포커스 가능한 대상과 UI 안의 닫기 경로
GameAndUI
입력 전달: UI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능
별도로 정할 것: 같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책

bShowMouseCursor는 모드와 별도입니다. GameAndUI는 UI가 처리하지 않은 입력을 게임 쪽에 전달할 수 있습니다. 본문 AttackButton 핸들러는 로그만 출력하며 실제 공격 호출은 주석입니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명GameAndUI 입력 모드에서 한 입력을 UI에 먼저 보낸 뒤 UI가 처리했는지 구별한다. 처리했으면 해당 입력을 소비하며, 처리하지 않았으면 플레이어 입력/컨트롤러에 처리 기회가 있다. 게임 처리 기회는 실제 공격이나 이동 완료를 보장하지 않으며, UIOnly의 미처리 경로와도 다르다.
노드·연결선 라벨같은 게임 동작 보장 X
예의 값과 변화
관찰·상황값·보충 정보변화·이유
입력 진입

GameAndUI 설정에서 한 입력을 보낸다.

  • 모드: GameAndUI
  • 먼저: UI 처리 기회
  • UIOnly의 별도 계약을 이 흐름에 섞지 않는다.
UI 판정

UI가 소비하거나 처리하지 않는다.

  • 소비: 게임 기회 없음
  • 미처리: 게임 기회
  • 전달 기회와 실제 게임 행동 완료는 다르다.
후속 경계

미처리 때 게임 입력 경로가 반응할 수 있다.

  • 현재: 게임 처리 기회
  • 공격 완료: 보장 아님
  • 포커스와 바인딩 등 다른 조건이 남을 수 있다.

입력 전달 모드와 커서·포커스 설정을 구분합니다

GameAndUI 한 입력→UI 먼저 처리→소비 또는 미처리 게임 기회라는 실제 전달 경로이다. 게임 기회가 공격 완료나 UIOnly 동작을 보장하지 않는다.

UNREAL C++ · CH6-4

입력 전달 모드와 커서·포커스 설정을 구분합니다

세 모드가 입력을 전달하는 대상부터 선택합니다. UIOnly에서는 UI 안에서 닫는 경로와 포커스 대상을 준비해야 하며, 커서 표시와 월드 일시정지는 별도입니다.

GameAndUI는 UI 처리 여부에 따라 게임 입력 기회가 달라진다GameAndUI입력모드에서한입력을UI에먼저보낸뒤UI가처리했는지구별한다. 처리했으면해당입력을소비하며,처리하지않았으면플레이어입력/컨트롤러에처리기회가있다. 게임처리기회가실제공격이나이동완료를보장하지않고UIOnly의미처리경로와도다르다.한 입력GameAndUI 선택UI 먼저 처리포커스 · 대상 확인처리했는가?입력 처리 결과예아니요UI에서 소비같은 게임 동작 보장X게임 입력 기회매핑 · 상태에 따라 처리
GameAndUI는 UI 처리 여부에 따라 게임 입력 기회가 달라진다GameAndUI입력모드에서한입력을UI에먼저보낸뒤UI가처리했는지구별한다. 처리했으면해당입력을소비하며,처리하지않았으면플레이어입력/컨트롤러에처리기회가있다. 게임처리기회가실제공격이나이동완료를보장하지않고UIOnly의미처리경로와도다르다.한 입력GameAndUI 선택UI 먼저 처리포커스 · 대상 확인처리했는가?입력 처리 결과예아니요UI에서 소비같은 게임 동작 보장X게임 입력 기회매핑 · 상태에 따라 처리

이 분기는 GameAndUI만 비교합니다. UIOnly에서는 UI가 처리하지 않아도 같은 게임 전달 경로를 갖지 않습니다. 커서 표시·월드 일시정지·실제 공격 구현은 별도입니다.

입력 전달과 별도 설정
모드입력 전달별도로 정할 것
GameOnly플레이어 입력·컨트롤러커서 숨김 여부와 게임 뷰포트 복귀
UIOnlyUI만 처리포커스 가능한 대상과 UI 안의 닫기 경로
GameAndUIUI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책
GameOnly
입력 전달: 플레이어 입력·컨트롤러
별도로 정할 것: 커서 숨김 여부와 게임 뷰포트 복귀
UIOnly
입력 전달: UI만 처리
별도로 정할 것: 포커스 가능한 대상과 UI 안의 닫기 경로
GameAndUI
입력 전달: UI가 먼저 처리하며 미처리 입력은 플레이어 쪽에 전달 가능
별도로 정할 것: 같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 정책

bShowMouseCursor는 모드와 별도입니다. GameAndUI는 UI가 처리하지 않은 입력을 게임 쪽에 전달할 수 있습니다. 본문 AttackButton 핸들러는 로그만 출력하며 실제 공격 호출은 주석입니다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
관계·조건 설명GameAndUI 입력 모드에서 한 입력을 UI에 먼저 보낸 뒤 UI가 처리했는지 구별한다. 처리했으면 해당 입력을 소비하며, 처리하지 않았으면 플레이어 입력/컨트롤러에 처리 기회가 있다. 게임 처리 기회는 실제 공격이나 이동 완료를 보장하지 않으며, UIOnly의 미처리 경로와도 다르다.
노드·연결선 라벨같은 게임 동작 보장 X
예의 값과 변화
관찰·상황값·보충 정보변화·이유
입력 진입

GameAndUI 설정에서 한 입력을 보낸다.

  • 모드: GameAndUI
  • 먼저: UI 처리 기회
  • UIOnly의 별도 계약을 이 흐름에 섞지 않는다.
UI 판정

UI가 소비하거나 처리하지 않는다.

  • 소비: 게임 기회 없음
  • 미처리: 게임 기회
  • 전달 기회와 실제 게임 행동 완료는 다르다.
후속 경계

미처리 때 게임 입력 경로가 반응할 수 있다.

  • 현재: 게임 처리 기회
  • 공격 완료: 보장 아님
  • 포커스와 바인딩 등 다른 조건이 남을 수 있다.

위 그림의 표는 세 입력 모드가 입력을 어디로 전달하는지와, 그와 별도로 정해야 할 것을 정리합니다. GameOnly는 플레이어 입력과 컨트롤러에 입력을 전달하며, 커서를 숨길지와 게임 뷰포트로 돌아오는 처리는 따로 정합니다. UIOnly는 UI만 입력을 처리하므로, 포커스를 받을 수 있는 대상과 UI 안에서 닫는 경로를 준비해야 합니다. GameAndUI는 UI가 먼저 처리하고, 처리하지 않은 입력은 플레이어 쪽으로 전달될 수 있으므로, 같은 입력이 어느 쪽에서 소비되는지와 마우스 캡처 방식을 정해야 합니다. 그림 아래의 설명처럼 bShowMouseCursor는 입력 모드와 별개의 설정입니다.

그림의 위쪽은 GameAndUI에서 입력 하나가 어떻게 흐르는지 보여 줍니다. 입력은 먼저 UI로 가서 포커스와 대상을 확인하고, UI가 그 입력을 처리했으면 UI에서 소비되어 같은 입력으로 게임 동작이 일어나지 않습니다. UI가 처리하지 않았다면 플레이어 입력과 컨트롤러에 처리할 기회가 생기는데, 기회가 생겼다고 실제로 공격이나 이동이 이루어진다는 보장은 없고 입력 매핑과 게임 상태가 맞아야 합니다. 이 분기는 GameAndUI만의 것이며, UIOnly에서는 UI가 처리하지 않은 입력이라도 같은 방식으로 게임에 전달되지 않습니다. 단계 그림도 입력 하나가 들어와 UI가 소비하거나 처리하지 않는 과정, 그리고 처리하지 않았을 때 게임 입력 경로가 반응할 수 있는 과정을 차례로 따라갑니다.

C++에서 입력 모드 바꾸기

입력 모드는 APlayerController에서 설정합니다. 다음은 인벤토리를 열고 닫으며 입력 모드를 바꾸는 컨트롤러입니다.

MyPlayerController.h
#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;
};
MyPlayerController.cpp
#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."));
            }
        }
    }
}

ToggleInventoryUI는 로컬 컨트롤러에서만 동작합니다. 인벤토리가 화면에 붙어 있으면 떼어 내고 참조를 비운 뒤, FInputModeGameOnly로 바꾸고 커서를 숨깁니다. 붙어 있지 않으면 새로 만들어 화면에 붙이고, FInputModeUIOnly로 바꾸고 커서를 보이게 합니다.

FInputModeUIOnly의 SetWidgetToFocus는 키보드 포커스를 받을 위젯을 정하는 함수입니다. 대상이 실제로 포커스를 받을 수 있어야 하므로, 이 예제의 인벤토리 루트 위젯에는 Is Focusable을 켜거나 버튼이나 입력란 같은 실제 위젯을 지정합니다. SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock)은 마우스를 뷰포트 안에 가두지 않도록 하는 선택 설정입니다.

UIOnly로 바뀐 뒤에는 게임 입력 바인딩이 동작하지 않으므로, I 키 같은 게임 입력만으로 인벤토리를 닫으려 해서는 안 됩니다. 인벤토리의 닫기 버튼이나 포커스를 받은 위젯의 키 처리에서 소유 컨트롤러의 ToggleInventoryUI를 부르도록 연결합니다. 또 입력 모드를 바꾸는 것만으로 게임이 일시정지되지는 않습니다.


위젯 이벤트를 C++에서 받기

버튼 클릭이나 슬라이더 값 변경 같은 상호작용은 보통 UMG 디자이너의 그래프 탭에서 블루프린트 이벤트 노드로 처리합니다. 하지만 C++에서도 위젯의 이벤트에 직접 함수를 바인딩할 수 있습니다.

MyPlayerHUD.h (이전 예시에서 이어서)
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyPlayerHUD.generated.h"

class UButton; // UButton 전방 선언 (헤더는 .cpp에서 포함)

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으로 선언되어야 함
};
MyPlayerHUD.cpp
#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(); }
}

헤더에서는 meta=(BindWidget)으로 디자이너의 AttackButton을 연결하고, 클릭되었을 때 부를 OnAttackButtonClicked를 UFUNCTION()으로 선언했습니다. NativeOnInitialized에서 버튼이 유효한지 확인한 뒤 OnClicked.AddDynamic으로 이 함수를 버튼의 클릭 델리게이트에 바인딩합니다.

구독을 NativeOnInitialized에서 하는 데는 이유가 있습니다. 앞 글들에서 보았듯이 NativeConstruct는 같은 인스턴스에서 화면을 다시 구성할 때마다 다시 불릴 수 있으므로, 거기서 구독을 추가하면 클릭 한 번에 함수가 여러 번 불리게 됩니다. 반면 NativeOnInitialized는 인스턴스마다 한 번만 불리므로 구독이 겹치지 않습니다.

이 방식은 특정 위젯의 이벤트를 그 위젯의 C++ 클래스에서 직접 처리할 때 쓸모가 있습니다. 하지만 복잡한 게임플레이 로직은 보통 플레이어 컨트롤러나 폰, 캐릭터에서 처리하므로, UI 위젯은 받은 이벤트를 컨트롤러 같은 곳으로 다시 전달하는 역할을 합니다. 이 예제의 OnAttackButtonClicked가 실제로 하는 일은 로그를 남기는 것뿐이며, 주석의 PerformAttack 호출은 구현된 공격이 아닙니다.


UMG 애니메이션 만들기

UMG에는 위젯의 위치, 크기, 투명도, 회전 같은 속성을 시간에 따라 바꾸는 애니메이션 기능이 들어 있습니다. 창이 서서히 나타나거나 버튼을 누를 때 살짝 작아지는 반응은 플레이어가 UI의 변화를 알아차리게 도와줍니다.

애니메이션은 UMG 디자이너에서 만듭니다. 디자이너 아래쪽의 애니메이션(Animations) 패널을 열고 + Animation 버튼으로 새 애니메이션을 만들어 이름(예: FadeIn, ButtonPress)을 정합니다. 애니메이션할 위젯을 선택한 뒤 + Track 버튼으로 그 위젯을 애니메이션에 추가하면, Render Transform의 위치, 크기, 회전 각도, Render Opacity(투명도), Color and Opacity(색), 그리고 지원되는 진행률 바 속성 같은 여러 속성을 트랙으로 만들 수 있습니다.

트랙을 만든 뒤에는 키프레임(keyframe)을 넣습니다. 타임라인에서 원하는 시간을 고르고 위젯의 속성을 조절한 다음 속성 옆의 키 아이콘을 눌러 키프레임을 추가합니다. 다른 시간으로 옮겨 속성을 바꾸고 다시 키프레임을 추가하면, 언리얼 엔진이 두 키프레임 사이의 값을 자동으로 보간해 애니메이션을 만듭니다. 반복 횟수와 재생 속도는 애니메이션 자체보다는, 재생할 때 Play Animation 노드나 PlayAnimation 함수의 인자로 정합니다.

C++에서 애니메이션 재생하기

UMG 애니메이션은 위젯 블루프린트 안에 있으므로, C++에서 재생하려면 그 애니메이션의 참조를 얻어야 합니다. BindWidgetAnim 메타데이터를 붙이면 디자이너에서 같은 이름으로 만든 애니메이션이 연결되고, 그 포인터를 PlayAnimation에 넘겨 재생합니다. 아래 두 클래스 예시는 앞의 버튼 예시와 같은 UMyPlayerHUD이므로, 필요한 멤버와 함수를 기존 HUD 클래스에 합쳐 씁니다.

MyPlayerHUD.h (이전 예시에서 이어서)
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyPlayerHUD.generated.h"

class UWidgetAnimation; // UWidgetAnimation 전방 선언 (헤더는 .cpp에서 포함)

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;

};

BindWidgetAnim으로 연결하는 애니메이션 변수에는 Transient를 함께 붙여, 저장하지 않는 값으로 둡니다. 변수 이름은 디자이너에서 만든 애니메이션 이름 FadeInAnimation과 같아야 합니다.

MyPlayerHUD.cpp
#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); // 기본 재생 속도와 한 번 재생
//     }
// }

NativeConstruct에서 FadeInAnimation이 유효하면 재생하므로, 위젯이 화면에 구성될 때마다 페이드인이 재생됩니다. 애니메이션을 다루는 UUserWidget의 주요 함수는 다음과 같습니다.

함수하는 일
PlayAnimation(UWidgetAnimation* InAnimation, float StartAtTime = 0.0f, int32 NumLoopsToPlay = 1, EUMGSequencePlayMode::Type PlayMode = EUMGSequencePlayMode::Forward, float PlaybackSpeed = 1.0f, bool bRestoreState = false)애니메이션을 시작 시간, 반복 횟수, 재생 방향, 속도를 정해 재생합니다. bRestoreState를 true로 주면 애니메이션이 멈출 때 위젯을 재생 전 상태로 되돌립니다.
StopAnimation(UWidgetAnimation* InAnimation), StopAllAnimations()특정 애니메이션이나 모든 애니메이션을 멈춥니다.
PauseAnimation(UWidgetAnimation* InAnimation)애니메이션을 일시 정지합니다.
IsAnimationPlaying(UWidgetAnimation* InAnimation)특정 애니메이션이 지금 재생 중인지 확인합니다.

이 예제의 FadeInAnimation은 위젯이 구성될 때만 재생되며, 공격 함수 호출, 닫을 때의 애니메이션, 애니메이션이 끝난 뒤의 제거는 구현하지 않았습니다. 닫는 연출이 필요하다면 애니메이션의 완료 이벤트에 위젯 제거와 입력 모드 복귀를 연결합니다. 모듈에는 UMG 의존성이 필요합니다.


입력과 화면 재구성을 따로 확인하기

인벤토리를 UIOnly로 열고 커서를 보이게 했더라도, 게임의 닫기 키 바인딩이 다시 실행된다고 가정하지 않습니다. 닫기 버튼의 UI 이벤트에서 Get Owning Player로 소유 컨트롤러를 얻어 ToggleInventoryUI를 부르는 경로를 연결합니다. 별도로 GameAndUI를 고른 설계라면, UI가 처리한 입력과 UI가 처리하지 않아 플레이어 입력에 기회가 생긴 입력을 구별합니다. 커서가 보인다는 사실만으로 두 모드의 전달 방식이 같아지지는 않습니다.

NativeOnInitialized의 한 번뿐인 구독과 NativeConstruct의 표시, 애니메이션 재구성을 나눠 두면, 화면을 다시 구성할 때마다 클릭 콜백을 중복으로 추가하는 일을 피할 수 있습니다. 반대로 매번 다시 보여 줘야 하는 페이드인 같은 연출은 NativeConstruct에 두는 것이 맞습니다.


정리

  • 입력 모드는 입력의 전달 대상을 정합니다. GameOnly는 게임에, UIOnly는 UI에만, GameAndUI는 UI가 먼저 처리하고 남은 입력을 게임에 전달할 수 있게 합니다.
  • 커서 표시(bShowMouseCursor)와 게임 일시정지는 입력 모드와 별개의 설정입니다.
  • UIOnly에서는 포커스를 받을 수 있는 대상을 SetWidgetToFocus로 지정하고, 닫기 버튼처럼 UI 안에서 게임으로 돌아오는 경로를 만듭니다.
  • 위젯 이벤트는 meta=(BindWidget)으로 연결한 위젯의 델리게이트에 AddDynamic으로 바인딩하며, 구독은 한 번만 불리는 NativeOnInitialized에서 합니다.
  • UMG 애니메이션은 디자이너의 애니메이션 패널에서 트랙과 키프레임으로 만들고, C++에서는 Transient와 BindWidgetAnim으로 연결해 PlayAnimation으로 재생합니다.
  • 반복 횟수와 재생 속도는 재생할 때의 인자로 정합니다.

연습 문제

1. 세 번 구성된 위젯

같은 위젯 인스턴스에서 NativeConstruct가 세 번 불렸고, NativeOnInitialized에서는 구독을 한 번만 했으며, FadeInAnimation 포인터는 매번 유효하다고 합시다. 이 글의 코드에서 클릭 구독 추가와 PlayAnimation 호출은 각각 몇 번 일어날까요?

해설 보기

구독 추가는 한 번이고, PlayAnimation 호출은 세 번입니다. 구독은 인스턴스마다 한 번 불리는 NativeOnInitialized에, 재생은 구성될 때마다 불리는 NativeConstruct에 있기 때문입니다.

이것은 주어진 호출 조건으로 계산한 결과이며, 화면에 다시 붙일 때마다 NativeConstruct가 꼭 한 번씩 불린다는 측정 결과는 아닙니다. 또 새 위젯 인스턴스를 만들면 그 인스턴스는 자신의 초기화와 구독을 따로 갖습니다.

2. UI가 처리한 입력

GameAndUI 모드에서 UI가 어떤 클릭 입력을 처리했습니다. 같은 클릭으로 게임의 공격도 실행되었다고 판단해도 될까요? UI가 처리하지 않았다면 공격이 반드시 실행될까요?

해설 보기

UI가 처리한 입력은 UI에서 소비되므로, 같은 입력으로 게임 공격까지 실행되었다고 판단할 수 없습니다. UI가 처리하지 않은 경우에도 플레이어 쪽에 처리할 기회가 생길 뿐이며, 실제로 공격이 이루어지려면 입력 매핑과 게임 상태가 맞아야 합니다. 두 경우 모두 결과를 따로 확인해야 합니다.

3. 닫히지 않는 인벤토리

인벤토리를 UIOnly로 연 뒤, 게임 입력 매핑의 I 키에 ToggleInventoryUI를 연결해 두었는데 I 키를 눌러도 인벤토리가 닫히지 않습니다. 원인과 해결 방법은 무엇일까요?

해설 보기

UIOnly에서는 UI만 입력을 처리하므로, 게임 입력 매핑의 I 키 바인딩이 실행되지 않습니다. 그래서 게임 입력으로 연결한 닫기 경로가 동작하지 않습니다.

인벤토리 안에 닫기 버튼을 두고 그 클릭 이벤트에서 소유 컨트롤러의 ToggleInventoryUI를 부르거나, 포커스를 받은 위젯의 키 처리에서 I 키를 받아 같은 함수를 부르도록 만듭니다. I 키로도 닫고 싶다면 포커스 대상이 실제로 포커스를 받을 수 있는지도 확인합니다.

위젯 애니메이션을 만드는 방법은 Epic의 Animating UMG Widgets 문서에서, 애니메이션 재생 함수는 UUserWidget API 문서에서 확인할 수 있습니다.

이것으로 6장을 마칩니다. 6장에서는 UMG의 위젯 계층과 위젯 블루프린트, C++에서 위젯을 만들고 붙이고 보관하는 방법, 게임 값을 HUD에 맞추는 바인딩과 직접 갱신, 그리고 입력 모드와 위젯 이벤트, UMG 애니메이션으로 UI 상호작용을 완성하는 방법을 익혔습니다. 다음 장의 SaveGame 시스템부터는 SaveGame으로 진행 상황을 저장하고, 직렬화와 파일 입출력, JSON, 사용자 설정 저장으로 게임 데이터를 다룹니다.