UI 입력모드랑 마우스 커서 함수로 만들기

김여울·2025년 11월 20일

내일배움캠프

목록 보기
118/139

기존에 HUD에서 위젯마다 하나하나 적었던 입력 모드 설정을 함수로 빼서 필요할 때 호출하기

이렇게 하면 나중에 다른 곳에서도 입력 모드 바꿔야 할 때 UIManager 통해서 편하게 호출하면 된다

입력 모드

  • SetInputModeUIOnly
    UI 메뉴용 (마우스 커서 표시 + UI만 입력 받음)

  • SetInputModeGameOnly
    게임 플레이용 (마우스 커서 숨김 + 게임만 입력 받음)

  • SetInputModeGameAndUI
    인벤토리 같은 곳용 (게임과 UI 둘 다 입력 받음)

마우스 커서

  • bShowMouseCursor
    마우스 커서를 보일지 숨길지 결정함

    • true : 커서가 화면에 보임
    • false : 커서가 안 보임
  • bLockMouseToViewport (SetLockMouseToViewportBehavior)
    마우스를 게임 창 안에 가둘지 결정함

    • true : 마우스가 게임 창 밖으로 못 나감
    • false : 마우스가 창 밖으로 나갈 수 있음
  • bHideCursorDuringCapture
    Capture : 마우스 입력을 게임이 받아서 처리한다
    게임 화면 클릭 → 마우스가 캡쳐됨

    • true : 기본값 - 캡처되면 커서 숨김
      • 게임 화면 클릭하는 순간 커서 사라짐
      • FPS 게임처럼 화면 클릭하면 카메라 돌리는 상화에 적합
      • 커서가 보이면 방해되니까 숨김
    • false : 캡처되어도 커서 계속 보임
      - RTS 게임, 인벤토리 UI, 로그라이크 게임 등에 적합
      - 마우스로 게임도 조작하고 UI 버튼도 눌러야 할 때 사용

// 기본 동작
FInputModeGameAndUI InputMode;
InputMode.SetHideCursorDuringCapture(true);   // 기본값
InputMode.SetHideCursorDuringCapture(false);  // 커서 계속 보임

// 실제 사용
// 인벤토리 열 때: 커서 계속 보임
UIManager->SetInputModeGameAndUI(PC, nullptr, true, false);
//                                             ↑커서보임  ↑클릭해도 커서 안 숨김

// FPS 조준 모드: 클릭하면 커서 숨김
UIManager->SetInputModeGameAndUI(PC, nullptr, true, true);
//                                             ↑커서보임  ↑클릭하면 커서 숨김

함수 만들기

UIManageSubsystem.h

public:
    // 입력 모드: UI 전용, 마우스 커서 ON
    void SetInputModeUIOnly(APlayerController* PC, UWidget* WidgetToFocus = nullptr, bool bLockMouseToViewport = false);
    
    // 입력 모드: Game 전용, 마우스 커서 OFF
    void SetInputModeGameOnly(APlayerController* PC);
    
    // 입력 모드: Game & UI
    void SetInputModeGameAndUI(APlayerController* PC, UWidget* WidgetToFocus = nullptr, bool bShowCursor = true, bool bHideCursorDuringCapture = false);

bShowMouseCursor

UI Only 모드는 마우스 커서가 무조건 보여야 하니까 Look 여부만 선택
bShowMouseCursor 는 항상 true 로 고정

bLockMouseToViewport

마우스를 창 안에 가둘지는 상황마다 다름

  • 메인 메뉴 : 창 밖으로 나갈 수 있어야 함 (false) - 다른 프로그램 켜거나 Alt+Tab 할 때
  • 인게임 인벤토리 : 창 안에 가둬야함 (true) - 마우스가 화면 밖으로 안 나가게
    → 만약 마우스 커서도 켜고 끄고 싶으면 SetInputModeGameAndUI 함수 사용해야 함
    bShowCursor 매개변수를 따로 받음

UWidget* WidgetToFocus

UWidget* WidgetToFocus = nullptr

  1. UWidget*
  • UWidget의 포인터 타입이야
  • UWidget은 언리얼 엔진의 모든 UI 위젯의 기본 클래스
  • 버튼, 텍스트, 이미지 등 모든 UI 요소가 UWidget을 상속받음
  1. WidgetToFocus
  • 변수 이름이야
  • "포커스를 줄 위젯"
  • 키보드 입력이나 컨트롤러 입력을 받을 UI를 지정
  1. = nullptr
  • 기본값이 nullptr(아무것도 가리키지 않음)
  • 이 매개변수는 선택사항이라는 의미

// 실제 사용 

// 함수 정의
void SetInputModeUIOnly(APlayerController* PC, UWidget* WidgetToFocus = nullptr);

// 사용할 때 - 두 가지 방법 가능
UIManager->SetInputModeUIOnly(PC);  // WidgetToFocus 안 넘김 → nullptr 사용됨
UIManager->SetInputModeUIOnly(PC, MyButton);  // MyButton에 포커스 줌

// 코드 동작
void ULNUIManageSubsystem::SetInputModeUIOnly(APlayerController* PC, UWidget* WidgetToFocus, bool bLockMouseToViewport)
{
    FInputModeUIOnly InputMode;
    
    // WidgetToFocus가 nullptr이 아닐 때만 실행돼
    if (WidgetToFocus)
    {
        InputMode.SetWidgetToFocus(WidgetToFocus->TakeWidget());
    }
    // nullptr이면 이 부분을 건너뛰고, 포커스 없이 진행
}
  • 특정 위젯에 포커스를 주고 싶을 때 (예: 로그인 버튼)
  • 포커스 없이 그냥 마우스만 보이면 될 때
  • 다양한 상황에 선택적으로 넘길 수 있게 기본값을 nullptr로 설정

UIManageSubsystem.cpp

void ULNUIManageSubsystem::SetInputModeUIOnly(APlayerController* PC, UWidget* WidgetToFocus, bool bLockMouseToViewport)
{
    // PlayerController가 유효한지 확인
    if (!PC)
    {
        return;
    }

    // 마우스 커서를 표시
    PC->bShowMouseCursor = true;

    // UI 전용 입력 모드를 생성
    FInputModeUIOnly InputMode;
    
    // 마우스를 뷰포트에 고정할지 설정
    if (bLockMouseToViewport)
    {
        InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::LockAlways);
    }
    else
    {
        InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
    }

    // 포커스를 줄 위젯이 있다면 설정
    if (WidgetToFocus)
    {
        InputMode.SetWidgetToFocus(WidgetToFocus->TakeWidget());
    }
    
    // 입력 모드를 적용
    PC->SetInputMode(InputMode);
}

void ULNUIManageSubsystem::SetInputModeGameOnly(APlayerController* PC)
{
    // PlayerController가 유효한지 확인
    if (!PC)
    {
        return;
    }

    // 마우스 커서를 숨김
    PC->bShowMouseCursor = false;

    // 게임 전용 입력 모드를 생성하고 적용
    FInputModeGameOnly InputMode;
    PC->SetInputMode(InputMode);
}

void ULNUIManageSubsystem::SetInputModeGameAndUI(APlayerController* PC, UWidget* WidgetToFocus, bool bShowCursor, bool bHideCursorDuringCapture)
{
    // PlayerController가 유효한지 확인
    if (!PC)
    {
        return;
    }

    // 마우스 커서 표시 여부를 설정
    PC->bShowMouseCursor = bShowCursor;

    // 게임과 UI 둘 다 사용하는 입력 모드를 생성
    FInputModeGameAndUI InputMode;
    
    // 캡처 중 커서를 숨길지 설정
    InputMode.SetHideCursorDuringCapture(bHideCursorDuringCapture);
    
    // 마우스를 뷰포트에 고정하지 않음
    InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);

    // 포커스를 줄 위젯이 있다면 설정
    if (WidgetToFocus)
    {
        InputMode.SetWidgetToFocus(WidgetToFocus->TakeWidget());
    }
    
    // 입력 모드를 적용
    PC->SetInputMode(InputMode);
}
  • if (!PC)
    다른 곳에서 이 함수를 호출할 때 PC가 nullptr일 수 있는 정상적인 상황
    → 게임 시작되기 전 or 멀티플레이에서 일부 클라이언트에만 PC가 있을 수 있음

HUD에서 함수 호출하기

HUD.cpp

void ALNHUD::OnRootLayoutReady()
{
    // UIManager Subsystem을 가져옴
    ULNUIManageSubsystem* UIManager = GetGameInstance()->GetSubsystem<ULNUIManageSubsystem>();
    if (!UIManager)
    {
        return;
    }

    // 현재 레벨 이름을 가져옴
    FString CurrentLevelName = GetWorld()->GetName();
    
    // PlayerController를 가져옴
    APlayerController* PC = GetOwningPlayerController();

    if (CurrentLevelName.Contains("MainMenu"))
    {
        // 메인메뉴 UI 띄우기
        UIManager->ShowWidget(FGameplayTag::RequestGameplayTag(TEXT("UI.Request.MainMenu")));

        // UI 전용 입력 모드로 설정 (마우스 커서 표시 + RootLayout에 포커스)
        UIManager->SetInputModeUIOnly(PC, RootLayoutInstance, false);
    }
    else
    {
        // 인게임 HUD 띄우기
        UIManager->ShowWidget(FGameplayTag::RequestGameplayTag(TEXT("UI.Request.ShowHUD")));

        // 게임 전용 입력 모드로 설정 (마우스 커서 숨김)
        UIManager->SetInputModeGameOnly(PC);
    }
}

유효성 검사

📎 Epic Games Developer - Asserts
📎 [Unreal] 언리얼 유효성 검사 - IsValid, == nullptr, ensure

if (!PC) - 일반적인 상황

if (!PC)
{
    return;
}
  • 부드러운 실패 : 조용히 함수 빠져나감
  • 정상적으로 발생 가능한 상황에서 사용
    (예) 플레이어가 아직 생성 안 됨
  • 게임 계속 실행

check(PC) - 절대 일어나면 안 되는 상황

check(PC != nullptr);
  • 하드 크래시 : 게임이 아예 멈춤
  • 절대 일어나면 안 되는 버그를 잡을 때 사용
    (예) 필수로 있어야 하는데 없음
  • 디버깅에 사용하고 Shipping 빌드에서는 제외됨

Shipping 빌드에서는 제외됨

언리얼 엔진은 상황에 따라 다른 방식으로 컴파일 가능

  • Development (개발용)

    • 개발할 때 쓰는 버전
    • 디버깅 가능, 콘솔 명령어, 로그 다 들어가 있음
    • check(), ensure() 같은 검사 코드가 실제로 실행됨
  • Shipping (배포용)

    • 유저한테 줄 최종 게임
    • 성능을 최대로 올리기 위해 불필요한 코드를 컴파일 할 때 아예 제거함
    • check(), ensure() 같은 매크로가 코드에서 아예 사라짐

작성한 코드

void MyFunction()
{
    check(PC != nullptr);  // 이 줄이
    PC->DoSomething();
}

Development 빌드로 컴파일

void MyFunction()
{
    if (PC == nullptr) { 크래시! }  // check가 실제로 검사
    PC->DoSomething();
}

Shipping 빌드로 컴파일

// 실제 실행되는 코드
void MyFunction()
{
    // check 줄이 완전히 사라짐
    PC->DoSomething();
}

개발할 때는 check() 로 버그를 빡빡하게 잡고, 최종 배포할 때는 자동으로 제거

if문 대신 check 썼던 경우

void ULNUIManageSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	// 1) Config(UIMapDataAssetPath)에 경로가 제대로 들어왔는지 확인
	check(LNMapDataAssetPath.IsValid());
    
    // ...
}

LNMapDataAssetPath 는 Config 파일이나 프로젝트 세팅에서 개발자가 반드시 설정해야 하는 값

이게 없으면

  1. 개발자가 세팅을 깜빡함
  2. 이 Subsystem이 아예 동작할 수 없음
  3. 개발 중에 바로 크래시 내서 알려줘야 함

check() 사용해서 개발 할 때 즉시 크래시로 세팅 안 했다고 알려줌

if (!LNMapDataAsset)로 하면
TryLoad() 결과는 런타임에 다양한 이유로 실패할 수 있음

  1. 에셋 파일이 삭제됨
  2. 경로가 변경됨
  3. 로딩 실패 (메모리 부족 등)
  4. 다른 외부 요인

→ 정상적으로 발생할 수 있는 외부 상황에 의한 실패
→ 부드럽게 실패 처리해서 게임 크래시 안 내고 계속 돌아감

ensure(PC) - 경고하고 계속 진행

if (!ensure(PC))
{
	return;
}
  • 콜스택 출력 : 에러 로그 남기고 게임은 계속 돌아감
  • 일어나면 안 되지만 치명적이지 않은 상황에 사용
  • 크래시 리포터에 자동으로 보고

0개의 댓글