본문으로 건너뛰기

안동민 개발노트

본문 시작

UI 입력 처리 및 애니메이션

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

이전 절에서 UMG의 데이터 바인딩HUD 시스템을 통해 게임 데이터를 UI에 표시하는 방법을 알아보았습니다.

이제 플레이어의 입력(Input)을 받아 UI와 상호작용하고, UI에 애니메이션을 적용하여 시각적 피드백과 생동감을 더하는 방법에 대해 살펴보겠습니다.

메뉴 입력은 열기와 닫기를 한 상태 전이로 설계한다

Widget을 띄우는 것만으로는 부족하다. 입력 대상과 복귀 상태까지 PlayerController가 책임진다.

  1. 1
    1. 생성

    생성 Owning Player 지정

  2. 2
    2. 모드

    모드 Game · UI 분배

  3. 3
    3. Focus

    Focus 첫 조작 Widget

  4. 4
    4. 상호작용

    상호작용 입력·Animation 잠금

  5. 5
    5. 복귀

    복귀 Focus·Cursor·모드 복원

  6. 6
    GameOnly

    캐릭터와 카메라 조작

  7. 7
    UIOnly

    메뉴가 입력을 독점 · Back 경로 필수

  8. 8
    GameAndUI

    UI가 처리하지 않으면 PlayerController로 전달


UI 입력 처리: 플레이어와의 상호작용

게임에서 UI는 단순히 정보를 보여주는 것을 넘어, 플레이어의 클릭, 마우스 오버, 키 입력 등을 받아 반응해야 합니다.

언리얼 엔진은 플레이어 컨트롤러(Player Controller)를 통해 UI 입력을 관리합니다.

입력 모드(Input Mode) 변경

가장 중요한 개념은 입력 모드입니다.

언리얼 엔진은 어떤 종류의 입력을 받아들일지 세 가지 주요 모드를 제공합니다.

  • FInputModeGameOnly
  • 설명: 오직 게임 월드의 입력(캐릭터 이동, 카메라 회전 등)만 받습니다. 마우스 커서는 일반적으로 숨겨집니다.
  • 사용 시기: 일반적인 게임 플레이 중.
  • FInputModeUIOnly
  • 설명: 오직 UI의 입력(버튼 클릭, 슬라이더 조작 등)만 받습니다. 게임 월드 입력은 비활성화되고 마우스 커서가 표시됩니다.
  • 사용 시기: 일시정지 메뉴, 인벤토리, 옵션 화면 등 UI가 전면에 나와 게임 플레이를 잠시 중단해야 할 때.
  • FInputModeGameAndUI
  • 설명: 게임 월드 입력과 UI 입력 모두를 받습니다. 마우스 커서가 표시되며, UI를 클릭하면 게임 월드 입력이 일시적으로 비활성화될 수 있습니다.
  • 사용 시기: HUD 위에 오버레이되는 짧은 알림창, 특정 오브젝트와 상호작용 시 나타나는 UI 등.
C++에서 입력 모드 설정

APlayerController 클래스에서 입력 모드를 설정합니다.

AMyPlayerController.h
#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;
};
AMyPlayerController.cpp
#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++에서 버튼 클릭 이벤트 처리 예시
UMyPlayerHUD.h (이전 예시에서 이어서)
#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으로 선언되어야 함
};
UMyPlayerHUD.cpp
#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 입력 처리는 입력 모드, 포커스, 위젯 이벤트, 게임플레이 전달을 분리해 두면 메뉴와 게임 조작이 서로 꼬이지 않습니다.

메뉴 입력과 게임 입력을 분리한다

PlayerController는 입력 모드와 커서를 관리하고, 위젯은 이벤트를 받아 의도를 전달하며, 실제 게임 상태 변경은 게임플레이 계층에서 처리합니다.

  1. SetInputMode
    PlayerController

    SetInputMode 위젯 생성, 포커스 지정, 마우스 커서 표시 여부를 한곳에서 전환합니다.

  2. AddDynamic
    UserWidget

    AddDynamic 버튼 클릭, 슬라이더 변경 같은 UI 이벤트를 위젯 클래스에서 받습니다.

  3. Intent
    Gameplay

    Intent 공격, 장비 변경, 옵션 적용 같은 실제 상태 변경을 별도 객체로 넘깁니다.

  4. PlayAnimation
    Animation

    PlayAnimation 입력 성공, 메뉴 열림, 버튼 반응을 짧은 UMG 애니메이션으로 확인시킵니다.


UI 애니메이션: 시각적 피드백과 생동감

UI 애니메이션은 이벤트, 재생, 속성 변화, 완료, 상태 갱신으로 이어진다

장식이 아니라 사용자의 행동에 시스템이 반응했다는 신호를 주는 인터랙션 장치다.

단계역할품질 기준
User Event클릭, hover, damage 같은 UI 트리거가 발생한다입력과 상태 변화가 분명하다
PlayAnimationC++ 또는 Blueprint에서 WidgetAnimation을 재생한다중복 클릭 정책이 있다
Property Track위치, 투명도, 크기 값을 시간에 따라 바꾼다짧고 명확한 피드백이다
Finished / State완료 후 열림, 닫힘, 강조 상태를 갱신한다보이는 상태와 내부 상태가 어긋나지 않는다

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은 위젯 블루프린트에 정의된 애니메이션을 이름으로 가져올 수 있는 함수를 제공합니다.

UMyPlayerHUD.h (이전 예시에서 이어서)
#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;
};
UMyPlayerHUD.cpp
#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): 특정 애니메이션이 현재 재생 중인지 확인합니다.

입력과 애니메이션은 따로 떨어진 기능이 아니라 하나의 상호작용 루프로 보는 편이 좋습니다.

메뉴를 열 때 입력 모드와 포커스를 바꾸고, 위젯 이벤트는 게임 명령으로 전달하며, 성공과 실패는 짧은 UMG 애니메이션으로 되돌려 주면 UI가 게임플레이를 방해하지 않으면서도 반응적으로 느껴집니다.

입력 모드 전환 루프

UI가 열렸다는 사실만으로 충분하지 않습니다. 어떤 입력을 받을지, 어디로 명령을 넘길지, 어떤 애니메이션으로 결과를 보여줄지까지 한 번에 정리해야 합니다.

  1. GameOnly

    월드 입력 ON 커서 OFF

  2. UIOnly

    위젯 포커스 커서 ON

  3. GameAndUI

    HUD 입력 월드 입력 유지

  4. PlayerController
    메뉴 열기부터 닫기까지

    PlayerController 1 PlayerController CreateWidget 과 AddToViewport 로 메뉴 수명을 시작합니다. 2 InputMode SetInputMode , 위젯 포커스, 커서 표시를 한 번에 맞춥니다. 3 UserWidget 클릭은 UI가 받고 공격·옵션 적용은 게임플레이 객체로 넘깁니다. 4 Close Path 종료 애니메이션 뒤 RemoveFromParent 와 GameOnly를 함께 처리합니다. 메뉴 열림: UI가 입력을 소유 메뉴 닫힘: 플레이 입력을 다시 소유

  5. 위젯 생성

    PlayerController CreateWidget 과 AddToViewport 로 메뉴 수명을 시작합니다.

  6. 포커스 전환

    InputMode SetInputMode , 위젯 포커스, 커서 표시를 한 번에 맞춥니다.

  7. 이벤트 위임

    UserWidget 클릭은 UI가 받고 공격·옵션 적용은 게임플레이 객체로 넘깁니다.

  8. 원래 상태 복귀

    Close Path 종료 애니메이션 뒤 RemoveFromParent 와 GameOnly를 함께 처리합니다.

  9. UserWidget
    애니메이션이 들어갈 자리

    UserWidget Open 포커스가 UI로 넘어간 순간을 짧게 보여줍니다. Click AddDynamic 으로 받은 입력에 즉시 반응합니다. Fail 거부된 명령은 게임 상태 대신 UI 피드백으로 분리합니다. Close 재생 완료 콜백에서 위젯 제거와 입력 복귀를 맞춥니다. 분리 기준 애니메이션은 사용자의 피드백이고, 입력 모드 복구는 PlayerController의 책임입니다.

  10. FadeIn / SlideIn

    Open 포커스가 UI로 넘어간 순간을 짧게 보여줍니다.

  11. ButtonPress

    Click AddDynamic 으로 받은 입력에 즉시 반응합니다.

  12. Shake / ErrorFlash

    Fail 거부된 명령은 게임 상태 대신 UI 피드백으로 분리합니다.

  13. FadeOut 완료

    Close 재생 완료 콜백에서 위젯 제거와 입력 복귀를 맞춥니다.

마지막으로 메뉴 전환 로직은 입력 모드, 포커스, 애니메이션 종료 시점까지 함께 점검하면 플레이 흐름이 흔들리지 않습니다.

UI 상호작용은 입력 모드, 버튼 이벤트, 애니메이션, 복귀 처리를 한 세트로 본다

UI는 열 때보다 닫을 때 표시, 입력, 포커스, 애니메이션 상태가 어긋나기 쉽다.

구간확인할 것실패 신호
Input ModeGameOnly, UIOnly, GameAndUI를 현재 화면 목적에 맞춘다게임 입력이 메뉴 뒤에서 계속 먹힌다
Button EventUButton OnClicked를 C++ 함수에 바인딩한다중복 바인딩과 null 포인터
AnimationDesigner의 WidgetAnimation을 찾아 PlayAnimation으로 재생한다변수 연결 또는 완료 상태 누락
Return닫을 때 입력 모드, 커서, focus를 게임 상태로 되돌린다닫은 뒤 카메라나 이동 입력이 깨진다

입력 처리는 플레이어가 게임과 상호작용하는 통로를 제공하고, 애니메이션은 UI에 생동감과 시각적 피드백을 부여하여 사용자 경험을 크게 향상시킵니다.

APlayerController의 입력 모드 전환을 통해 게임 플레이와 UI 상호작용 간의 전환을 매끄럽게 하고, 위젯 이벤트 처리와 UMG 애니메이션 제어는 여러분의 UI를 더욱 풍부하고 반응적으로 만들 것입니다.