언리얼 엔진 본캠프 7주차-4 언리얼 엔진 C++ : PlayerController와 Enhanced Input System

정재훈·2025년 1월 29일
0

unreal engine

목록 보기
20/45

PlayerController와 Enhanced Input System

입력을 처리하고 맵핑하는 방법
사용자 입력에 대한 캐릭터의 움직임을 어떻게 구조적으로 관리할 것인가

PlayerController 클래스

: 언리얼 엔진에서 플레이어 입력을 처리하고, 폰(Pawn) 또는 캐릭터(Character)를 제어하는 핵심 클래스로
언리얼의 철학 중 하나로 플레이어의 입력은 따로 관리한다 => 입력이 어떤 입력인지 판단하는 클래스, 이후 PlayerController 클래스가 판단한 것에 대해 [내부적으로 or 캐릭터의 클래스]가 처리를 함

역할

  • 플레이어의 입력 처리
    • 키보드, 마우스, 컨트롤러 입력에 따라 Character 또는 Pawn을 제어
    • 입력 매핑을 설정하고, 특정 키를 눌렀을 때 이벤트(처리 로직)를 실행
  • Pawn 또는 Character 제어
    • PlayerControllerPossess(Pawn* Pawn) 함수를 이용해 해당 Pawn을 소유할 수 있으며, 이를 조작, 제어할 수 있음
    • => 각 플레이어 캐릭터는 개별적인 PlayerController를 가지게 됨
  • HUD 및 UI와의 상호작용
    • 게임 화면에 표시되는 UI, 마우스 커서 등을 관리
    • UMG(User Widget) 시스템과 연동하여 UI 입력을 처리할 수도 있습니다.
      • Esc를 누르면 게임 메뉴 창이 뜨는 것
      • 거기에서 환경 설정 버튼을 누르면 설정 창이 뜨는 것
  • 네트워크 멀티플레이어 관리
    • PlayerController는 서버와 클라이언트 간의 입력 및 데이터 동기화에서 중요한 역할을 합니다.
    • RPC(Remote Procedure Call)를 활용해 서버와 데이터를 주고받을 수 있습니다.

주요 함수

  • 입력 처리
    • SetupInputComponent() : 입력에 의해 호출될 함수를 바인딩하는 함수
      • Character 클래스에 비슷한 이름을 가진 SetupPlayerInputComponent()라는 함수가 있는데 SetupPlayerInputComponent()함수는 플레이어 캐릭터의 움직임(이동, 점프, 공격 등)을 제어하는 입력에 대한 바인딩을 하고 SetupInputComponent()함수는 그 외에 UI를 조작한다거나, [[[카메라를 제어]]]하는 입력에 대한 바인딩을 하면됨
  • Pawn 또는 Character 제어
    • Possess(APawn* Pawn) : 특정 Pawn에 빙의하여 조작할 수 있도록 하는 함수
    • UnPossess() : 빙의를 해제
  • HUD 및 UI와의 상호작용
    • SetShowMouseCursor(bool bShow) : 마우스 커서 보이기/숨기기
    • SetInputMode(FInputModeGameAndUI()) : 게임 + UI 입력 모드 설정
    • SetInputMode(FInputModeGameOnly()) : 게임 전용 입력 모드 설정
    • SetInputMode(FInputModeUIOnly()) : UI 입력 모드 전용 설정

Enhanced Input System

: 기존의 Input 시스템을 대체하는 새로운 입력 처리 시스템으로, 컨텍스트 기반 입력처리, 입력 매핑 확장성, 다양한 입력 장치 지원 등의 기능을 제공하여 더욱 유연한 입력 매핑 및 처리가 가능하도록 설계되었다

  • 컨텍스트 기반 입력 처리
    • 게임 내 전투 모드, 탐험 모드, 메뉴 모드 등 각각에 대해 IMC를 만들고, 상황에 맞게 IMC를 동적으로 활성화/비활성화하는 것(ex)로아:전투↔생활, 메이플:키보드 프리셋, 이 예시가 틀렸을 수도 있지만 현재는 이런 느낌)

핵심 개념

  • Input Mapping System(IMC) : IA들의 모음집, IA를 특정 키, 특정 마우스 버튼, 특정 컨트롤러 버튼 등에 매핑하는 역할을 한다
    • 사람 전용 IMC
    • 탈것 전용 IMC
    • 게임 플레이 중 동적으로 추가하거나 제거할 수 있다
  • Input Action(IA) : 특정 동작에 대해 추상화한 것(?), 인터페이스 정도로 볼 수 있고, Enhanced Input System과 코드간의 통신 링크 역할을 한다.
    • 점프 키 입력 → IA_Jump → Jump()
    • 마우스 회전 → IA_Look → Look()
    • 이동 키 입력 → IA_Move → Move()
    • Details-Value Type : 입력이 발생되었을 때 어떤 유형의 값을 변화시킬 것인지 설정
    • Details-Triggers : 입력의 형태가 어떤 것인지 선택(눌렀을 때, 땠을 때, 누르고 있는 동안 등)
  • Input Modifier : 입력 트리거로 보내기 전에 UE에서 받는 입력 값을 변경
    • 축 변경, 반전(Invert) 등
  • Input Trigger : 입력 액션이 활성화될 조건을 정의
    • Pressed : 버튼이 눌렸을 때 실행
    • Released : 버튼이 떼어졌을 때 실행
    • Hold : 일정 시간 이상 버튼을 누르면 실행
  • Player Mappable Input : 플레이어가 직접 키 매핑을 변경할 수 있도록 지원하는 기능

IMC 활성화

  • LocalPlayer : 로컬 플레이어(한 명의 사용자)를 나타내는 클래스
    • 하나의 PlayerController와 연결
    • 플레이어 캐릭터의 Input Subsystem(EnhancedInputLocalPlayerSubsystem)을 가지고 있음
    • 그 외에 UGameViewportClientCommonUI등을 사용해서 HUD/UI를 관리하기도 한다
  • EnhancedInputLocalPlayerSubsystem : LocalPlayer 안에서 IMC를 관리하는 객체
    • IMC를 동적으로 추가/제거할 수 있는 기능 제공
    • AddMappingContext(IMC, Priority) : 특정 IMC 추가
    • RemoveMappingContext(IMC) : 특정 IMC 제거
    • ClearAllMappings() : 모든 IMC 제거
    • GetPlayerInput() : 플레이어의 입력 상태를 가져옴

<플레이어 캐릭터의 클래스>

  • 함수 바인딩

    • 캐릭터의 클래스의 SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)함수에서 IA에 대한 이벤트가 발생했을 때 호출될 함수를 바인딩한다
    • UInputComponent : 다양한 형태의 입력 이벤트를 델리게이트 함수에 바인딩할 수 있도록 하는 컴포넌트
    • BindAction(const UInputAction* Action, ETriggerEvent TriggerEvent, UObject* object, FName FunctionName) : 입력 액션(IA)를 특정 함수와 연결하는 함수
  • 함수 구현

    • 함수 구현은 캐릭터의 클래스에서 해야하는데, Character 클래스에는 이 구현을 다 해둔 CharacterMovementComponent가 있음.
      • 이미 구현해둔 함수를 호출하는 방식으로 아주 간단하게 함수를 만들 수 있다
    • 대신 캐릭터의 클래스가 Pawn 클래스이면 하나하나 구현해야함

    • FInputActionValueUEnhancedInputComponent 안에 있음
  • CharacterMovementComponent

    • 주요 프로퍼티
      • MaxWalkSpeed : 캐릭터가 걸을 때의 최대 속도를 설정
      • MaxWalkSpeedCrouched : 웅크릴 때(Crouching) 캐릭터의 최대 속도를 설정
      • JumpZVelocity : 점프할 때의 초기 속도를 설정
      • GravityScale : 캐릭터에 적용되는 중력의 세기를 조절
      • AirControl : 캐릭터를 공중에서 얼마나 조작할 수 있는지를 설정. 이 값이 클수록 공중에서 캐릭터의 움직임을 더 많이 제어할 수 있음
      • BrakingDecelerationWalking : 걷기 중에 캐릭터가 멈출 때 얼마나 빨리 속도가 줄어드는지 설정
      • FallingLateralFriction : 캐릭터가 떨어질 때 얼마나 좌우로 공기 마찰을 느끼는지를 설정
    • 주요 함수
      • 함수 구현에서 CharacterMovementComponent의 함수를 사용하는 줄 알았는데 그게 아니라 Character 클래스의 함수를 사용했다. 대신 이 함수들의 내부에서 다른 클래스의 함수가 사용된 것
        • AddMovementInput()PawnMovementComponentAddInputVector() 사용
          • 함수 매개변수를 보면 FVector로 World 기준 벡터 값을 받음
        • Jump()StopJumping()CharacterMovementComponentDoJump() 사용
          • Character클래스에서는 점프를 할 때 점프를 해야되나 말아야되나를 매 프레임마다 CheckJumpInput()라는 것으로 확인을 하고 점프를 해야하면 내부적으로 CharacterMovementComponentDoJump()를 호출
        • AddControllerYawInput()PlayerControllerAddYawInput() 사용
          • Look 같은 경우는 카메라랑 연관된 것이라 PlayerController의 함수를 호출
        • IsFalling()


점프 횟수 출력 및 IMC 전환

점프 횟수 출력

캐릭터가 점프를 할 때마다 점프 횟수를 출력하기 위해 기존에 Jump()함수만 있던 코드에서

점프 횟수를 셀 변수 JumpCount와 뷰포트 출력 코드 AddonScreenDebugMessage()를 추가하고 실행해서 점프를 했는데

점프키인 Space bar를 한 번 눌렀을 뿐인데도 StartJump()함수가 여러번 호출되어 회수를 제대로 세지 못하였다

  • StartJump()IA_Jump와 바인딩할 때 ETriggerEventTriggered로 지정해서 Space bar가 눌러지기 시작하고 때어질 때까지 계속 StartJump()가 호출됨
  • Jump() 함수는 캐릭터가 점프 가능하면 점프를 한 다음 캐릭터의 MovementModeMOVE_Falling으로 바꾸고 이 상태일 때는 점프가 안되도록 구현되어 있음
  • => StartJump()가 여러번 호출되어도 2단, 3단 ... 다단 점프는 안되지만, 출력 메시지는 저렇게 여러번 출력됨

그래서 StopJump()에서 점프 횟수를 누적하고 출력하려고 했는데 StopJump()는 Space bar가 때졌을 때 호출되는 거라 Space bar를 계속 누르고 있으면 점프 횟수가 제대로 누적되지 않는 문제가 있었다.

그래서 엔진 함수인 Jump()StopJumping()코드를 시작으로 내부 코드로 타고 들어가보기도 하고 검색도 해보다가 MovementMode라는 게 있고 이걸 이용하면 되겠다는 생각이 들었다

Space bar가 눌려져 있고 플레이어 캐릭터의 MovementModeMOVE_Falling이 아닐 때만 횟수를 누적하고 출력하도록 하니 제대로 작동하였다

(Space bar를 3번 눌렀다 떼고, 3번 점프할 동안 계속 누르고 있다가 뗐음)

  • CharacterMovementComponent 클래스에서 플레이어 캐릭터의 MovementMode를 관리
    • 매 프레임 StartNewPhysics()에서 MovementMode에 따라 플레이어 캐릭터의 상황을 체크하는데, MOVE_Falling일 때는 PhysFalling()를 호출
    • PhysFalling()에서 FindFloor()를 호출해서 플레이어 캐릭터가 바닥을 만나는지 확인하고 바닥에 닿으면 ProcessLanded()를 호출
    • ProcessLanded()는 내부에서 GroundMovementModeMOVE_Walking으로 바꾸고 SetPostLandedPhysics()를 호출
    • SetPostLandedPhysics()GroundMovementModeMovementMode에 대입해서 모드를 변경시킴
  • StartNewPhysics()PhysFalling()FindFloor()ProcessLanded()SetPostLandedPhysics()
  • => 플레이어가 Space bar를 꾹 누르고 있어도 처음 점프를 한 이후로는 MOVE_Falling 모드라 횟수 누적과 출력을 하지 않고, 바닥에 닿아서 MOVE_FallingMOVE_Walking이 되면 횟수를 누적하고 출력

IMC 전환

기존의 IMC_Character이외의 IMC를 추가로 만들고, Tab 키 액션을 만들어서 Tab 키를 누를 때 마다 두 IMC를 전환하도록 만들어 보았다

새로운 IMC인 IMC_UI와 Tab 키 액션인 IA_Tab을 에디터에서 생성

  • IA_Tab은 키를 눌렀다 떼기만 하면 되서 Value Type은 bool
  • IMC_UI에는 IA_Tab외에 IMC_Character와 다른 동작을 하도록 IA_Jump만 추가로 넣고 Left Alt 키로 동작하도록 변경
    • IMC_Character에도 IA_Tab을 추가

추가한 IMC_UI와 IA_Tab을 코드에서 받을 수 있도록 PlayerController의 프로퍼티를 추가하고 에디터에서 연결

IMC를 전환하는 것은 플레이어 캐릭터를 이동시키는 것과는 거리가 멀기 때문에 PlayerControllerSetupInputComponent()에서 바인딩

  • 부모 클래스에서 상속받은 함수를 오버라이딩할 때는 꼭 부모 클래스의 함수를 호출해줘야 한다.
    • 해당 코드를 빼고 실행했을 때 바인딩이 제대로 되지 않아서 Tab 키를 눌러도 아무동작을 하지 않음

바인딩되는 ChangeIMC() 구현

현재 적용되는 IMC를 CurrIMC에 넣고 Tab을 누를 때 마다 CurrIMC를 보고 TabInputMappingContext(IMC_UI)를 적용할 지 InputMappingContext(IMC_Character)를 적용할 지를 결정

  • 하지만 Tab을 눌러 TabInputMappingContext로 전환을 해도 여전히 InputMappingContext를 사용할 수 있었다.

뭐가 문제였냐면 문제라기보다 AddMappingContext(IMC, Priority)에 대해 잘못 이해하고 있었다.

  • AddMappingContext(IMC, Priority)로 추가된 IMC들 중 Priority가 높은 것'만' 적용되는 게 아니라 Priority가 높은 것이 '우선' 적용되는 것이었다

그래서 Tab을 누르면 InputMappingContext를 제거하고 TabInputMappingContext를 추가하고 다시 Tab을 누르면 TabInputMappingContext를 제거하고 InputMappingContext를 추가하는 식으로 바꿨다

profile
드가자

0개의 댓글