입력을 처리하고 맵핑하는 방법
사용자 입력에 대한 캐릭터의 움직임을 어떻게 구조적으로 관리할 것인가
: 언리얼 엔진에서 플레이어 입력을 처리하고, 폰(Pawn)
또는 캐릭터(Character)
를 제어하는 핵심 클래스로
언리얼의 철학 중 하나로 플레이어의 입력은 따로 관리한다 => 입력이 어떤 입력인지 판단하는 클래스, 이후 PlayerController
클래스가 판단한 것에 대해 [내부적으로 or 캐릭터의 클래스]가 처리를 함
Character
또는 Pawn
을 제어Pawn
또는 Character
제어PlayerController
는 Possess(Pawn* Pawn)
함수를 이용해 해당 Pawn
을 소유할 수 있으며, 이를 조작, 제어할 수 있음PlayerController
를 가지게 됨SetupInputComponent()
: 입력에 의해 호출될 함수를 바인딩하는 함수Character
클래스에 비슷한 이름을 가진 SetupPlayerInputComponent()
라는 함수가 있는데 SetupPlayerInputComponent()
함수는 플레이어 캐릭터의 움직임(이동, 점프, 공격 등)을 제어하는 입력에 대한 바인딩을 하고 SetupInputComponent()
함수는 그 외에 UI를 조작한다거나, [[[카메라를 제어]]]하는 입력에 대한 바인딩을 하면됨Pawn
또는 Character
제어Possess(APawn* Pawn)
: 특정 Pawn에 빙의하여 조작할 수 있도록 하는 함수UnPossess()
: 빙의를 해제SetShowMouseCursor(bool bShow)
: 마우스 커서 보이기/숨기기SetInputMode(FInputModeGameAndUI())
: 게임 + UI 입력 모드 설정SetInputMode(FInputModeGameOnly())
: 게임 전용 입력 모드 설정SetInputMode(FInputModeUIOnly())
: UI 입력 모드 전용 설정: 기존의 Input 시스템을 대체하는 새로운 입력 처리 시스템으로, 컨텍스트 기반 입력처리, 입력 매핑 확장성, 다양한 입력 장치 지원 등의 기능을 제공하여 더욱 유연한 입력 매핑 및 처리가 가능하도록 설계되었다
핵심 개념
IMC 활성화
LocalPlayer
: 로컬 플레이어(한 명의 사용자)를 나타내는 클래스PlayerController
와 연결UGameViewportClient
나 CommonUI
등을 사용해서 HUD/UI를 관리하기도 한다EnhancedInputLocalPlayerSubsystem
: LocalPlayer 안에서 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
클래스이면 하나하나 구현해야함FInputActionValue
는 UEnhancedInputComponent
안에 있음CharacterMovementComponent
MaxWalkSpeed
: 캐릭터가 걸을 때의 최대 속도를 설정MaxWalkSpeedCrouched
: 웅크릴 때(Crouching) 캐릭터의 최대 속도를 설정JumpZVelocity
: 점프할 때의 초기 속도를 설정GravityScale
: 캐릭터에 적용되는 중력의 세기를 조절AirControl
: 캐릭터를 공중에서 얼마나 조작할 수 있는지를 설정. 이 값이 클수록 공중에서 캐릭터의 움직임을 더 많이 제어할 수 있음BrakingDecelerationWalking
: 걷기 중에 캐릭터가 멈출 때 얼마나 빨리 속도가 줄어드는지 설정FallingLateralFriction
: 캐릭터가 떨어질 때 얼마나 좌우로 공기 마찰을 느끼는지를 설정CharacterMovementComponent
의 함수를 사용하는 줄 알았는데 그게 아니라 Character
클래스의 함수를 사용했다. 대신 이 함수들의 내부에서 다른 클래스의 함수가 사용된 것AddMovementInput()
은 PawnMovementComponent
의 AddInputVector()
사용FVector
로 World 기준 벡터 값을 받음Jump()
와 StopJumping()
는 CharacterMovementComponent
의 DoJump()
사용Character
클래스에서는 점프를 할 때 점프를 해야되나 말아야되나를 매 프레임마다 CheckJumpInput()
라는 것으로 확인을 하고 점프를 해야하면 내부적으로 CharacterMovementComponent
의 DoJump()
를 호출AddControllerYawInput()
은 PlayerController
의 AddYawInput()
사용IsFalling()
캐릭터가 점프를 할 때마다 점프 횟수를 출력하기 위해 기존에 Jump()
함수만 있던 코드에서
점프 횟수를 셀 변수 JumpCount
와 뷰포트 출력 코드 AddonScreenDebugMessage()
를 추가하고 실행해서 점프를 했는데
점프키인 Space bar를 한 번 눌렀을 뿐인데도 StartJump()
함수가 여러번 호출되어 회수를 제대로 세지 못하였다
StartJump()
를 IA_Jump
와 바인딩할 때 ETriggerEvent
를 Triggered
로 지정해서 Space bar가 눌러지기 시작하고 때어질 때까지 계속 StartJump()
가 호출됨Jump()
함수는 캐릭터가 점프 가능하면 점프를 한 다음 캐릭터의 MovementMode
를 MOVE_Falling
으로 바꾸고 이 상태일 때는 점프가 안되도록 구현되어 있음StartJump()
가 여러번 호출되어도 2단, 3단 ... 다단 점프는 안되지만, 출력 메시지는 저렇게 여러번 출력됨그래서 StopJump()
에서 점프 횟수를 누적하고 출력하려고 했는데 StopJump()
는 Space bar가 때졌을 때 호출되는 거라 Space bar를 계속 누르고 있으면 점프 횟수가 제대로 누적되지 않는 문제가 있었다.
그래서 엔진 함수인 Jump()
와 StopJumping()
코드를 시작으로 내부 코드로 타고 들어가보기도 하고 검색도 해보다가 MovementMode라는 게 있고 이걸 이용하면 되겠다는 생각이 들었다
Space bar가 눌려져 있고 플레이어 캐릭터의 MovementMode
가 MOVE_Falling
이 아닐 때만 횟수를 누적하고 출력하도록 하니 제대로 작동하였다
(Space bar를 3번 눌렀다 떼고, 3번 점프할 동안 계속 누르고 있다가 뗐음)
CharacterMovementComponent
클래스에서 플레이어 캐릭터의 MovementMode
를 관리StartNewPhysics()
에서 MovementMode
에 따라 플레이어 캐릭터의 상황을 체크하는데, MOVE_Falling
일 때는 PhysFalling()
를 호출PhysFalling()
에서 FindFloor()
를 호출해서 플레이어 캐릭터가 바닥을 만나는지 확인하고 바닥에 닿으면 ProcessLanded()
를 호출ProcessLanded()
는 내부에서 GroundMovementMode
를 MOVE_Walking
으로 바꾸고 SetPostLandedPhysics()
를 호출SetPostLandedPhysics()
는 GroundMovementMode
를 MovementMode
에 대입해서 모드를 변경시킴StartNewPhysics()
→ PhysFalling()
→ FindFloor()
→ ProcessLanded()
→ SetPostLandedPhysics()
MOVE_Falling
모드라 횟수 누적과 출력을 하지 않고, 바닥에 닿아서 MOVE_Falling
이 MOVE_Walking
이 되면 횟수를 누적하고 출력기존의 IMC_Character이외의 IMC를 추가로 만들고, Tab 키 액션을 만들어서 Tab 키를 누를 때 마다 두 IMC를 전환하도록 만들어 보았다
새로운 IMC인 IMC_UI와 Tab 키 액션인 IA_Tab을 에디터에서 생성
추가한 IMC_UI와 IA_Tab을 코드에서 받을 수 있도록 PlayerController
의 프로퍼티를 추가하고 에디터에서 연결
IMC를 전환하는 것은 플레이어 캐릭터를 이동시키는 것과는 거리가 멀기 때문에 PlayerController
의 SetupInputComponent()
에서 바인딩
바인딩되는 ChangeIMC() 구현
현재 적용되는 IMC를 CurrIMC에 넣고 Tab을 누를 때 마다 CurrIMC를 보고 TabInputMappingContext(IMC_UI)를 적용할 지 InputMappingContext(IMC_Character)를 적용할 지를 결정
뭐가 문제였냐면 문제라기보다 AddMappingContext(IMC, Priority)
에 대해 잘못 이해하고 있었다.
AddMappingContext(IMC, Priority)
로 추가된 IMC들 중 Priority가 높은 것'만' 적용되는 게 아니라 Priority가 높은 것이 '우선' 적용되는 것이었다그래서 Tab을 누르면 InputMappingContext를 제거하고 TabInputMappingContext를 추가하고 다시 Tab을 누르면 TabInputMappingContext를 제거하고 InputMappingContext를 추가하는 식으로 바꿨다