본캠프 33일차_언리얼엔진을 이용한 게임개발 입문_Character 이동 구현

YU YEON WON·2025년 9월 19일

1. 오늘 배운 내용(키워드)

오늘은 어제에 이어서 캐릭터 구현 후 이동을 구현하였다. 이동에는 크게 사방향이동(WASD키로 이동(걷기)), 점프, 달리기가 있다. 이 동작을 수행하기 위해 키 맵핑과 해당 함수를 입력하였다.

1-1. PlayerController 이해

  • 플레이어 컨트롤러(PlayerController)란?
    • PlayerController는 사용자가 키보드, 마우스, 게임패드 등에서 입력을 받으면, 그 입력을 해석하여 캐릭터나 다른 오브젝트에게 동작을 명력하는 핵심 클래스
    • 언리얼 엔진의 중요한 철학 중 하나는 "플레이어 입력은 PlayerController에서 처리한다"는 것, 이를 통해 입력 처리 로직과 실제 캐릭터의 동작 로직을 분리할 수 있어 코드를 구조적으로 관리하기 쉬워짐
    • 입력이 처리되는 기본 흐름
      1. 키보드, 마우스, 게임패드 등 입력 장치로부터 사용자 조작 신호가 입력됨
      2. 이 신호는 PlayerController가 받아서 해석
      3. PlayerController가 현재 소유(Possess)하고 있는 Pawn에게 이동, 회전, 공격 등의 구체적인 명령을 내림
    • 멀티플레이어 환경에서 각 플레이어마다 개별 PlayerController가 생성되므로, 여러 사용자의 입력을 충돌 없이 분리하고 관리할 수 있음
  • PlayerController의 주요 기능
    • 입력 처리
      • 키보드, 마우스, 게임패드, 터치 등 다양한 입력 장치의 이벤트 처리
      • 언리얼 엔진 5에서 제공하는 Enhanced Input 시스템을 사용하면, 액션/축 매핑을 보다 체계적으로 설정할 수 있음
      • C++에서는 SetupInputComponent()함수를 오버라이드하여, 블루프린트에서는 이벤트 그래프를 통해 입력 로직을 구현
    • 카메라 제어 로직 : 마우스나 게임패드의 축 입력을 받아 캐릭터의 시점 회전이나 줌 인/아웃 같은 카메라 통작을 수행
    • HUD 및 UI와의 상호작용
      • 언리얼의 언리얼 모션 그래픽 기반 UI를 통해 버튼 클릭, 드래그, 터치 등의 이벤트를 PlayerController에서 받을 수 있음
      • 예로 인벤토리 열기, 스킬 사용 등의 명령을 UI에서 트리거하면 PlayerController가 이를 해석해 Pawn 또는 GameMode 등 다른 시스템으로 전달 할 수 있음
    • Possess / UnPossess
      • PlayerController는 특정 Pawn에 "빙의(Possess)"하여 해당 Pawn을 제어
      • 필요할 때 UnPossess() 함수를 호출하여 Pawn과의 연결을 해제한 뒤, 다른 Pawn으로 바꿔 탈 수 있음
      • 멀티플레이 시 각 플레이어마다 고유의 PlayerController가 있고, 이 컨트롤러가 특정 Pawn을 소유함으로써 서로 다른 캐릭터 조작이 가능

1-2. PlayerController C++ 클래스 생성

  • 아래와 같은 코드로 APlayerController 클래스를 생성하였다.

헤더파일

#pragma once

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

UCLASS()
class SPARTA_PROJECT_API ASparta_PlayerController : public APlayerController
{
	GENERATED_BODY()

}

cpp파일

#include "Sparta_PlayerController.h"
  • PlayerController를 GameMode에 적용하였다.

GameMode 헤더파일

#pragma once

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

UCLASS()
class SPARTA_PROJECT_API ASparta_GameMode : public AGameMode
{
	GENERATED_BODY()


public:
	ASparta_GameMode();

};

GameMode cpp파일

#include "Sparta_GameMode.h"
#include "Sparta_Character.h"
#include "Sparta_PlayerController.h" // 플레이어컨트롤 클래스를 사용

ASparta_GameMode::ASparta_GameMode()
{
	DefaultPawnClass = ASparta_Character::StaticClass();
	PlayerControllerClass = ASparta_PlayerController::StaticClass();
} 
  • PlayerControllerClass : GameMode가 제공하는 속성, 게임 시작 시 - PlayerController 타입을 지정

  • ASparta_PlayerController::StaticClass() : 언리얼 엔진이 ASparta_PlayerController 클래스의 정보를 런타임에 참조할 수 있도록 제공하는 정적 함수

  • PlayerController Blueprint 생성 : C++로 만든 Sparta_PlayerController를 더 손쉽게 편집할 수 있도록, 블루프린트로 래핑

  • 아래 설정에서 PlayerConroller Class를 BP_Sparta_PlayerController로 지정

  • PlayerController 동작 확인

1-3. Enhanced Input System의 이해 및 Input 설정

  • 인핸스드 인풋 시스템(Enhanced Input System)이란?
    • 언리얼 엔진5에는 이전 버전(UE4 등)에서 사용하던 전역 Project Settings -> Input 시스템을 대체하거나 확장하기 위해 Enhanced Input System이 제공
    • Enhanced Input은 입력 설정을 "입력 맵(Input Mapping Context, IMC)"과 "입력 액션(Input Action, IA)"이라는 개념으로 나뉨
    • Input Action : 캐릭터의 이동, 점프, 발사, 줌 등과 같이 특정 동작을 추상화한 단위

1-4. Input Mapping Context 설정

  • 인풋 매핑 컨텍스트(Input Mapping Context, IMC)
    • 여러 개의 IA들을 한데 모아놓은 매핑 설정 파일

1-5. PlayerController에서 IMC 활성화

  • 플레이어컨트롤러 헤더 파일에 IMC 활성화 하기 위해 플레이어 컨트롤러 클래스에 IA, IMC 멤버 변수 선언 및 리플렉션 처리

플레이어컨트롤러 헤더파일

#pragma once

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

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

UCLASS()
class SPARTA_PROJECT_API ASparta_PlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	ASparta_PlayerController();

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

플레이어컨트롤러 cpp파일

#include "Sparta_PlayerController.h"
#include "EnhancedInputSubsystems.h"

ASparta_PlayerController::ASparta_PlayerController()
	:InputMappingContext(nullptr),
	MoveAction(nullptr),
	JumpAction(nullptr),
	LookAction(nullptr),
	SprintAction(nullptr)
{

}
  • 플레이어 컨트롤러의 디테일 패널에서 IMC, IA 지정
  • IMC 활성화를 위한 코드 작성 : 플레이어컨트롤러 클래스에 BeginPlay() 함수 추가 CPP파일에 구현 내용 작성
    • Local Player Subsystem을 통해 Input Mapping Context를 활성화
    • Local Player Subsystem이란?
      • 게임이 실행되면 언리얼은 각 플레이어를 표현하기 위해 Local Player 객체를 생성
      • UEnhancedInputLocalPlayerSubsystem은 Local Player에 부착되어, 해당 플레이어가 사용할 입력 매핑(IMC)를 관리

플레이어컨트롤러 헤더파일

#pragma once

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

class UInputMappingContext;
class UInputAction;

UCLASS()
class SPARTA_PROJECT_API ASparta_PlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	ASparta_PlayerController();

	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;

protected:
	virtual void BeginPlay() override; //IMC 활성화를 위한 함수 추가
	
};

플레이어 컨트롤러 cpp파일

#include "Sparta_PlayerController.h"
#include "EnhancedInputSubsystems.h"

ASparta_PlayerController::ASparta_PlayerController()
	:InputMappingContext(nullptr),
	MoveAction(nullptr),
	JumpAction(nullptr),
	LookAction(nullptr),
	SprintAction(nullptr)
{

}

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

	// 현재 플레이어컨트롤러에 연결된 로컬 플레이어 객체를 가져오기
	if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
	{
    	// 로컬 플레이어에서 인핸드스 인풋 로컬 플레이어 서브시스템을 획득
		if (UEnhancedInputLocalPlayerSubsystem* SubSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
		{
			if(InputMappingContext)
			{
            	// 서브시스템을 통해 우리가 할당한 IMC 활성화
                // 우선순위 0으로 가장 높은 순위
				SubSystem->AddMappingContext(InputMappingContext, 0);
			}
		}
	}
}
  • 테스트 영상
    스크린샷을 클릭하면 영상을 볼 수 있습니다.

2. 느낀점

키 입력 매핑 코드가 많이 복잡하다. 따라해보면서 대소문자 구분, 오타를 잘 보고 작성해야겠다. 키 설정은 언리얼 엔진 안에서 셋팅하는게 좋은 것 같다.

3. 차주 학습할 것

강의 듣기(아이템 생성), 7번째 과제 진행 및 6번째 과제 도전 기능 추가하기

profile
🕹️🎮🍱✈️📸 안녕하세요! 게임개발에 도전하고 있습니다!

0개의 댓글