PlayerController란?

  • PlayerController는 사용자가 키보드, 마우스, 게임패드 등에서 입력을 받으면, 그 입력을 해석하여 캐릭터나 다른 오브젝트에게 동작을 명령하는 핵심 클래스입니다.
  • 언리얼 엔진의 중요한 철학 중 하나는 “플레이어 입력은 PlayerController에서 처리한다”는 것입니다. 이를 통해 입력 처리 로직과 실제 캐릭터의 동작 로직을 분리할 수 있어, 코드를 구조적으로 관리하기가 훨씬 수월해집니다.
  • 입력이 처리되는 기본 흐름
    1. 키보드, 마우스, 게임패드 등 입력 장치로부터 사용자 조작 신호가 들어옵니다.
    2. 이 신호는 PlayerController가 받아서 해석합니다.
    3. PlayerController가 현재 소유 (Possess)하고 있는 Pawn에게 이동, 회전, 공격 등의 구체적인 명령을 내립니다.
  • 특히 멀티플레이 환경에서는, 각 플레이어마다 개별 PlayerController가 생성되므로, 여러 사용자의 입력을 충돌 없이 분리하고 관리할 수 있습니다.

 

PlayerController의 주요 기능

  • 입력 처리
    • 키보드, 마우스, 게임패드, 터치 등 다양한 입력 장치의 이벤트를 처리합니다.
    • 언리얼 엔진 5에서 제공하는 Enhanced Input 시스템을 사용하면, 액션/축 매핑을 보다 체계적으로 설정할 수 있습니다.
    • C++에서는 SetupInputComponent() 함수를 오버라이드하여, 블루프린트에서는 이벤트 그래프를 통해 입력 로직을 구현합니다.
  • 카메라 제어 로직
    • 마우스나 게임패드의 축 입력을 받아 캐릭터의 시점 회전이나 줌 인/아웃 같은 카메라 동작을 수행할 수 있습니다.
  • HUD 및 UI와의 상호작용
    • 언리얼의 UMG (언리얼 모션 그래픽) 기반 UI를 통해 버튼 클릭, 드래그, 터치 등의 이벤트를 PlayerController에서 받을 수 있습니다.
    • 예를 들어 인벤토리 열기, 스킬 사용 등의 명령을 UI에서 트리거하면 PlayerController가 이를 해석해 Pawn 또는 GameMode 등 다른 시스템으로 전달할 수 있습니다.
  • Possess / UnPossess
    • PlayerController는 특정 Pawn에 “빙의 (Possess)”하여 해당 Pawn을 제어합니다.
    • 필요할 때 UnPossess() 함수를 호출하여 Pawn과의 연결을 해제한 뒤, 다른 Pawn으로 바꿔 탈 수도 있습니다.
    • 멀티플레이 시 각 플레이어마다 고유의 PlayerController가 있고, 이 컨트롤러가 특정 Pawn을 소유함으로써 서로 다른 캐릭터 조작이 가능합니다.

 

C++ 클래스 APlayerController 생성

언리얼 엔진에서 커스텀 PlayerController를 만들기 위해서는, 보통 PlayerController 클래스를 상속받은 후 필요한 기능을 추가·확장합니다.

  • 에디터 상단 메뉴에서 Tools → New C++ Class를 선택
  • Common Classes 목록에서 Player Controller를 선택하거나, 검색 창에 PlayerController를 입력해 해당 클래스를 선택
  • 클래스 이름을 지정하고, Class Type은 Public으로 설정한 뒤 Create Class 

 

PlayerController를 GameMode에 적용

헤더

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameMode.h"
#include "SpartaGameMode.generated.h"

UCLASS()
class SPARTAPROJECT_API ASpartaGameMode : public AGameMode
{
		GENERATED_BODY()
		
public:
		ASpartaGameMode();
};

 

cpp

#include "SpartaGameMode.h"
#include "SpartaCharacter.h"
#include "SpartaPlayerController.h" // PlayerController 클래스를 사용

ASpartaGameMode::ASpartaGameMode()
{
		DefaultPawnClass = ASpartaCharacter::StaticClass();
    PlayerControllerClass = ASpartaPlayerController::StaticClass();
}

 

  • PlayerControllerClass는 GameMode가 제공하는 속성으로, 게임 시작 시 사용할 PlayerController 타입을 지정합니다.
  • ASpartaPlayerController::StaticClass()는 언리얼 엔진이 ASpartaPlayerController 클래스의 정보를 런타임에 참조할 수 있도록 제공하는 정적 함수입니다.
  • 위 코드를 저장 후 프로젝트 빌드를 완료하면, 언리얼 에디터에서 GameMode가 SpartaPlayerController를 기본 PlayerController로 인식하게 됩니다.

 

블루프린트로 랩핑해주기

게임 모드로 가서 default pawn class를 블루프린트로 감싼 플레이어 컨트롤러로 지정하기

 

 

 

Enhanced Input System

 

1. input action(IA) - 추상적인 행동(일종의 전선들)

- 특정 동작을 추상화하는 추상 단위

점프 -> IA_Jump -> Jump 함수

마우스 회전 -> IA_Look -> Look 함수

이동 -> IA_Move

 

2. input mapping system(IMC) - (스위치 같은 것)

- IA들을 총괄해서 관리하는...

ex. a, b, c라는 스위치가 있으면 a는 점프, b는 마우스 회전 등등으로 설정하는 것

버튼을 바꾸고 싶으면 a, b, c 같은 imc들만 바꾸고 점프를 구현한 IA_Jump의 내부 로직같은 것들은 안 바꿔도 돼서 편함

 

그리고 캐릭터를 움직이다가 자동차 같은 거에 탐

그럼 이제 자동차의 입력으로 바뀔 텐데 그럴 때 IMC를 갈아끼우면 됨.

자동차 전용 IMC로!

그때그때 상황에 따라 어떤 걸 활성화 해줄지만 if문으로 켜주면 됨! 

 

IA 파일을 더블 클릭해 열면 아래와 같은 주요 속성을 설정할 수 있습니다:

  • Value Type은 Input Action (IA)이 입력 동작을 발생시킬 때, 어떤 유형의 값을 제공할지 결정하는 옵션입니다.
    • Bool (참/거짓)
      • 단순 On/Off 토글 입력에 사용됩니다.
      • 예) 점프(스페이스바), 공격(마우스 왼쪽 버튼)
    • Axis1D (1차원 축 값)
      • 단일 축 (-1~1 범위)의 입력에 사용됩니다.
      • 예) 게임패드 트리거(가속 페달), 전진/후진(W/S)
    • Axis2D (2차원 축 값)
      • X, Y 두 축을 동시에 처리할 때 사용됩니다.
      • 예) 캐릭터 이동(WASD), 마우스 이동(가로+세로)
    • Axis3D (3차원 축 값)
      • X, Y, Z 세 축을 동시에 처리합니다.
      • 예) 비행 시뮬레이션에서 3축 제어
  • 트리거 (Trigger)는 입력이 활성화되는 특정 조건을 말합니다.
    • Pressed Trigger: 키를 누르는 순간에만 작동.
    • Hold Trigger: 키를 일정 시간 눌렀을 때 작동.
    • Released Trigger: 키를 뗄 때 작동.
  • 모디파이어 (Modifier)는 입력 값을 수정하거나 변환하기 위한 설정입니다.
    • Scale : 입력 값에 일정 배율을 곱해줌 (마우스 이동 속도 2배)
    • Swizzle Input Axis Values : 입력 값의 축을 재구성 해주는 것 (XYZ축을 YXZ축으로 변경한다던지...)
    • Invert : 입력 값을 반전 (상하 반전 카메라)
    • Deadzone : 일정 임계값보다 작은 입력은 무시 (게임패드 조이스틱 미세 떨림 방지)

 

Input Action (IA) 생성과 설정

- 캐릭터 행동

1. 이동 WASD (IA_Move)

인풋 폴더 생성 -> 우클릭 -> 위와 같이 만들기 -> IA_Move로 이름 생성

  • 이동은 일반적으로 "앞/뒤"와 "왼/오른쪽" 두 방향을 동시에 처리하므로, Value Type은 Axis2D로 설정합니다.
  • IA_Move는 항상 활성화된 상태에서 WASD 입력만으로 동작해야 하므로 트리거나 모디파이어가 필요하지 않습니다. 예를 들어, "Shift 키를 눌러야만 이동 가능" 같은 조건이 없다면 생략해도 됩니다.

 

2. 점프 (IA_Jump)

스페이스바를 누르면 점프하는 간단한 방식이므로, Value Type을 Bool로 설정합니다. 점프 동작은 단순히 "On/Off"로 동작하기 때문에 키가 눌렸는지 여부만 중요하며, 추가적인 수치나 축 값이 필요하지 않습니다.

 

3. 스프린트 (shift 누르면 캐릭터가 빨리 달림) (IA_Sprint)

스프린트 또한 단순히 Shift 키 값으로 속도를 늘리거나 줄이고를 설정해주므로, value type을 bool로 (On/Off) 로 설정하고 저장해줍니다.

 

4. 마우스 회전 (IA_Look)

마우스 입력은 항상 가로 (X축, 좌/우)와 세로 (Y축, 위/아래) 움직임을 동시에 포함합니다. 따라서 Value Type을 Axis2D로 설정해줍니다.

 

 

 

Input Mapping Context (IMC) 생성과 매

 

  • Content Browser의 Inputs 폴더에서 우클릭 → Input → Input Mapping Context를 선택합니다. 생성된 파일의 이름을 지정합니다.
  • 생성된 IMC_ 에셋을 더블 클릭하여 열고, Mappings 목록에 앞서 만든 IA_Move, IA_Jump, IA_Look 등을 각각 추가합니다.

  • W 키 (전진)
    • W 키를 누르면 입력 값이 X축 (앞뒤 방향)에 맞춰 정렬됩니다.
    • 전진은 X축 +1 방향이므로 추가적인 변환은 필요 없습니다.

  • S 키 (후진)
    • S 키의 입력 값도 Swizzle을 통해 X축으로 정렬됩니다.
    • 후진은 전진 (W)의 반대 방향이므로 Negate를 사용해 입력 값을 뒤집습니다. (X축 +1 → X축 -1)

  • A 키 (왼쪽 이동)
    • A 키를 누르면 입력 값이 Y축 (좌우 방향)에 맞춰 정렬됩니다.
    • A키는 오른쪽 D 이동의 반대 방향이므로 Negate를 사용해 Y축의 값을 반전합니다. (Y축 +1 → Y축 -1)

  • D 키 (오른쪽 이동)
    • D 키의 입력값도 Swizzle을 통해 Y축으로 정렬됩니다.
    • D 키는 Y축 (좌우 방향)의 +1 방향에 매핑되므로 추가 변환이 필요 없습니다.

 

  • 점프는 스페이스바를 눌렀을 때 On/Off 형태로 동작합니다.
  • 별도의 트리거나 모디파이어가 필요 없으므로, 기본 상태로 둡니다.

 

  • 마우스 움직임을 통해 Yaw (좌우 회전)과 Pitch (상하 회전) 값을 동시에 전달받습니다.
  • Axis2D로 지정한 X축, Y축 값을 게임 내 좌표계에 맞춰 회전에 반영합니다.
  • 기본적으로 마우스 Y축의 움직임은 "위로 움직임 = 양수, 아래로 움직임 = 음수"로 전달됩니다.
  • 하지만, 카메라의 상하 회전 (Pitch)은 엔진의 좌표계에서 "위로 = 음수, 아래로 = 양수"로 작동하는 경우가 많습니다. 따라서, Y축 값을 Negate Modifier를 통해 반전하여야 합니다.
    • 위로 움직임 → 음수(-) (카메라 위로).
    • 아래로 움직임 → 양수(+) (카메라 아래로).
  • X축 (좌우 회전, Yaw)의 경우, 기본적인 엔진 좌표계와 마우스 움직임의 방향이 일치합니다.
    • 오른쪽 이동 → 양수(+).
    • 왼쪽 이동 → 음수(-).

 

  • 스프린트는 왼쪽 Shift 키를 눌렀을 때 On/Off 형태로 동작합니다.
  • Key를 “Left Shift”로 지정하고 트리거나 모디파이어는 기본 상태로 두고 다시 저장을 합니다.

 

PlayerController에서 IMC 활성화하기

<player controller 헤더>

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "SpartaPlayerController.generated.h"

class UInputMappingContext; // IMC 관련 전방 선언
class UInputAction; // IA 관련 전방 선언

UCLASS()
class 솔루션이름_API A플레이어컨트롤러이름 : public APlayerController
{
	GENERATED_BODY()

public:
	A플레이어컨트롤러이름();

  // 에디터에서 세팅할 IMC
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
  UInputMappingContext* InputMappingContext;
	// IA_Move를 지정할 변수
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
  UInputAction* MoveAction;
  // IA_Jump를 지정할 변수
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
  UInputAction* JumpAction;
  // IA_Look를 지정할 변수
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Input")
  UInputAction* LookAction;
  // IA_Sprint를 지정할 변수
  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
  UInputAction* SprintAction;
};

 

<cpp 파일>

#include "SpartaPlayerController.h"

ASpartaPlayerController::ASpartaPlayerController()
    : InputMappingContext(nullptr),
      MoveAction(nullptr),
      JumpAction(nullptr),
      LookAction(nullptr),
      SprintAction(nullptr)
{
}

 

-> 어차피 블루프린트에서 초기화 해줄 거기 때문에, 코드에선 널포인터(nullptr) 처리를 해줌!

아무것도 할당 안 하는 것보단 널포인터 해주는 게 더 안전함.

 

-> 에셋 할당!

 

IMC 활성화 코드 작성

  • 이번에는 C++에서 실제로 IMC를 활성화하는 코드를 작성해보겠습니다. 언리얼 5의 Enhanced Input System은 Local Player Subsystem을 통해 Input Mapping Context를 활성화하거나 비활성화합니다.
  • Local Player Subsystem이란?
    • 게임이 실행되면 언리얼은 각 플레이어를 표현하기 위해 Local Player 객체를 생성합니다.
      • 싱글플레이어 상황에서는 하나의 Local Player
      • 로컬 멀티플레이 (하나의 화면에서 여러 명이 플레이)라면 플레이어 수만큼 Local Player가 생성됩니다.
    • UEnhancedInputLocalPlayerSubsystem 은 Local Player에 부착되어, 해당 플레이어가 사용할 입력 매핑 (IMC)을 관리합니다.
      • 이를 통해, 플레이 중에 동적으로 다른 IMC를 추가·제거하여 입력 모드를 전환할 수 있습니다.
      • 예) 전투 중 (IMC_Character) → UI창 열림 (IMC_UI) → 전투 종료 후 다시 (IMC_Character)
  • ASpartaPlayerController 클래스에서 BeginPlay() 함수를 오버라이드하여, Blueprint에서 지정해둔 IMC를 활성화하는 코드를 추가하겠습니다.

 

<헤더 파일>

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "SpartaPlayerController.generated.h"

class UInputMappingContext;
class UInputAction;

UCLASS()
class SPARTAPROJECT_API ASpartaPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
    ASpartaPlayerController();

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
    UInputMappingContext* InputMappingContext;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
    UInputAction* MoveAction;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
    UInputAction* JumpAction;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
    UInputAction* LookAction;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
    UInputAction* SprintAction;

		virtual void BeginPlay() override;
};

 

<cpp 파일>

#include "SpartaPlayerController.h"
#include "EnhancedInputSubsystems.h" // Enhanced Input System의 Local Player Subsystem을 사용하기 위해 포함

ASpartaPlayerController::ASpartaPlayerController()
    : InputMappingContext(nullptr),
      MoveAction(nullptr),
      JumpAction(nullptr),
      LookAction(nullptr),
      SprintAction(nullptr)
{
}

void ASpartaPlayerController::BeginPlay()
{
    Super::BeginPlay();

		// 현재 PlayerController에 연결된 Local Player 객체를 가져옴    
    if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
    {
		    // Local Player에서 EnhancedInputLocalPlayerSubsystem을 획득
        if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
        {
	          if (InputMappingContext)
            {
            		// Subsystem을 통해 우리가 할당한 IMC를 활성화
		            // 우선순위(Priority)는 0이 가장 높은 우선순위
                Subsystem->AddMappingContext(InputMappingContext, 0);
            }
        }
    }
}

 

  • GetLocalPlayer()
    • 현재 PlayerController가 관리하는 Local Player를 반환합니다.
  • GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()
    • 해당 Local Player에 부착된 Enhanced Input Subsystem을 반환합니다.
    • 이를 통해 AddMappingContext나 RemoveMappingContext 등을 호출하여 입력 매핑을 동적으로 제어할 수 있습니다.
  • AddMappingContext()
    • 주어진 IMC를 Subsystem에 추가하여 입력 매핑을 활성화합니다.
    • SpartaInputMappingContext: 활성화할 IMC.
    • 0: 우선순위. 낮을수록 높은 우선순위를 가집니다.
    • 이 함수를 여러 번 호출해 여러 IMC를 활성화할 수도 있습니다.
      • 우선순위를 달리 부여해, 특정 IMC가 다른 IMC보다 우선순위가 높도록 설정할 수도 있습니다.

이 로직이 실행되면, IMC에 정의된 모든 IA와 키 매핑이 이 PlayerController에 적용됩니다.

 

 

IMC 적용 확인 및 테스트

 

블루프린트 위쪽에 open full blueprint editor로 들어가서 event graph 열기!

 

이 IA 이벤트들을 Blueprint 상에서 연결하여 간단한 테스트를 해볼 수 있습니다. 예를 들어 Print String 노드를 붙여서, 키나 마우스를 움직일 때마다 문자열이 출력되는지 확인할 수 있습니다.

 

게임을 플레이하고 직접 WASD, Spacebar, 마우스 이동 등을 해보면, 해당 이벤트 노드가 정상적으로 호출되며 문자열이 출력되는 것을 볼 수 있습니다.

+ Recent posts