입력 시스템 (Input) 처리
언리얼 엔진의 입력 시스템은 다양한 입력 장치로부터의 사용자 입력을 효과적으로 처리할 수 있게 해줍니다.
이 절에서는 C++에서 입력 시스템을 구현하고 활용하는 방법을 살펴보겠습니다.
입력 처리 과정 및 InputComponent
언리얼 엔진의 입력 처리는 다음과 같은 과정을 거칩니다.
- 입력 이벤트 발생
- PlayerController가 입력 이벤트 수신
- InputComponent를 통해 바인딩된 함수 호출
InputComponent는 입력 이벤트와 게임 로직을 연결하는 중요한 역할을 합니다.
입력 액션 및 축 매핑 설정
프로젝트 설정에서 입력 매핑을 정의한 후 C++에서 다음과 같이 사용할 수 있습니다.
// MyCharacter.h
UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
protected:
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void MoveForward(float Value);
void MoveRight(float Value);
void StartJump();
void StopJump();
};
// MyCharacter.cpp
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// 축 매핑
PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);
// 액션 매핑
PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AMyCharacter::StartJump);
PlayerInputComponent->BindAction("Jump", IE_Released, this, &AMyCharacter::StopJump);
}
void AMyCharacter::MoveForward(float Value)
{
if (Controller != nullptr && Value != 0.0f)
{
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(Direction, Value);
}
}
void AMyCharacter::MoveRight(float Value)
{
if (Controller != nullptr && Value != 0.0f)
{
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(Direction, Value);
}
}
void AMyCharacter::StartJump()
{
bPressedJump = true;
}
void AMyCharacter::StopJump()
{
bPressedJump = false;
}
다양한 입력 유형 처리
- 키보드 입력
PlayerInputComponent->BindAction("Action", IE_Pressed, this, &AMyCharacter::PerformAction);
- 마우스 입력
PlayerInputComponent->BindAxis("LookUp", this, &AMyCharacter::AddControllerPitchInput);
PlayerInputComponent->BindAxis("Turn", this, &AMyCharacter::AddControllerYawInput);
- 게임패드 입력
PlayerInputComponent->BindAxis("GamepadLookUp", this, &AMyCharacter::GamepadLookUp);
PlayerInputComponent->BindAxis("GamepadTurn", this, &AMyCharacter::GamepadTurn);
터치 입력 처리
터치 입력은 다음과 같이 처리할 수 있습니다.
// MyPlayerController.h
UCLASS()
class MYGAME_API AMyPlayerController : public APlayerController
{
GENERATED_BODY()
public:
virtual void SetupInputComponent() override;
private:
void OnTouchBegin(ETouchIndex::Type FingerIndex, FVector Location);
void OnTouchEnd(ETouchIndex::Type FingerIndex, FVector Location);
void OnTouchMove(ETouchIndex::Type FingerIndex, FVector Location);
};
// MyPlayerController.cpp
void AMyPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindTouch(IE_Pressed, this, &AMyPlayerController::OnTouchBegin);
InputComponent->BindTouch(IE_Released, this, &AMyPlayerController::OnTouchEnd);
InputComponent->BindTouch(IE_Repeat, this, &AMyPlayerController::OnTouchMove);
}
void AMyPlayerController::OnTouchBegin(ETouchIndex::Type FingerIndex, FVector Location)
{
// 터치 시작 처리
}
void AMyPlayerController::OnTouchEnd(ETouchIndex::Type FingerIndex, FVector Location)
{
// 터치 종료 처리
}
void AMyPlayerController::OnTouchMove(ETouchIndex::Type FingerIndex, FVector Location)
{
// 터치 이동 처리
}
사용자 정의 입력 장치 지원
사용자 정의 입력 장치를 지원하려면 IInputDevice 인터페이스를 구현하고 입력 처리 로직을 추가해야 합니다.
class FMyCustomInputDevice : public IInputDevice
{
public:
virtual void Tick(float DeltaTime) override;
virtual void SendControllerEvents() override;
virtual void SetMessageHandler(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) override;
virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override;
virtual void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) override;
virtual void SetChannelValues(int32 ControllerId, const FForceFeedbackValues& Values) override;
};
입력 설정의 런타임 변경
런타임에 입력 설정을 변경하려면 다음과 같은 방법을 사용할 수 있습니다.
void AMyPlayerController::ChangeInputMapping(FName ActionName, FKey NewKey)
{
UInputSettings* InputSettings = UInputSettings::GetInputSettings();
TArray<FInputActionKeyMapping> ActionMappings;
InputSettings->GetActionMappingByName(ActionName, ActionMappings);
if (ActionMappings.Num() > 0)
{
ActionMappings[0].Key = NewKey;
InputSettings->RemoveActionMapping(ActionMappings[0]);
InputSettings->AddActionMapping(ActionMappings[0]);
InputSettings->SaveKeyMappings();
}
}
성능 고려사항
1. 입력 처리 최적화
- 불필요한 입력 체크 줄이기
- 입력 처리 로직을 간결하게 유지
2. 입력 폴링 대신 이벤트 기반 처리 사용
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
PlayerInputComponent->BindAction("Action", IE_Pressed, this, &AMyCharacter::OnActionPressed);
// 폴링 방식 대신 이벤트 기반 처리 사용
}
3. 입력 디바운싱 구현
void AMyCharacter::OnActionPressed()
{
static float LastPressedTime = 0.0f;
float CurrentTime = GetWorld()->GetTimeSeconds();
if (CurrentTime - LastPressedTime > 0.2f) // 200ms 디바운스
{
PerformAction();
LastPressedTime = CurrentTime;
}
}
다중 플레이어 환경에서의 입력 처리
다중 플레이어 게임에서는 각 플레이어의 입력을 독립적으로 처리해야 합니다.
void AMyPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
// 로컬 플레이어의 입력만 처리
if (IsLocalPlayerController())
{
InputComponent->BindAxis("MoveForward", this, &AMyPlayerController::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AMyPlayerController::MoveRight);
}
}
서버에서는 클라이언트로부터 받은 입력을 검증하고 처리해야 합니다.
UFUNCTION(Server, Reliable, WithValidation)
void AMyCharacter::ServerMove(FVector Direction);
bool AMyCharacter::ServerMove_Validate(FVector Direction)
{
// 입력 유효성 검사
return Direction.IsNormalized();
}
void AMyCharacter::ServerMove_Implementation(FVector Direction)
{
// 서버에서 캐릭터 이동 처리
AddMovementInput(Direction);
}
입력 시스템과 UI 상호작용
UI와 게임 입력을 구분하여 처리하는 것이 중요합니다.
void AMyPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("ToggleInventory", IE_Pressed, this, &AMyPlayerController::ToggleInventory);
}
void AMyPlayerController::ToggleInventory()
{
if (InventoryWidget)
{
if (InventoryWidget->IsInViewport())
{
InventoryWidget->RemoveFromViewport();
SetInputMode(FInputModeGameOnly());
}
else
{
InventoryWidget->AddToViewport();
SetInputMode(FInputModeGameAndUI());
}
}
}
Best Practices
- 입력 매핑 문서화
// 프로젝트 설정의 입력 매핑을 코드에서 명시적으로 설명
/** 전후 이동 입력 축. W/S 키 또는 게임패드 왼쪽 스틱 Y축에 매핑됨 */
void AMyCharacter::MoveForward(float Value);
- 입력 처리의 모듈화
// 별도의 컴포넌트로 입력 처리 로직 분리
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UMyInputComponent : public UActorComponent
{
GENERATED_BODY()
public:
void SetupInputBindings(UInputComponent* PlayerInputComponent);
private:
void MoveForward(float Value);
void MoveRight(float Value);
};
- 디버그 모드에서의 입력 로깅
void AMyCharacter::MoveForward(float Value)
{
##if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
UE_LOG(LogTemp, Verbose, TEXT("MoveForward: %f"), Value);
##endif
// 이동 로직
}
- 입력 설정의 데이터 주도적 접근
// 데이터 테이블을 사용하여 입력 설정 관리
USTRUCT(BlueprintType)
struct FInputMappingData : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FName ActionName;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FKey DefaultKey;
};
언리얼 엔진의 입력 시스템은 다양한 입력 장치와 게임 플레이 요구사항을 효과적으로 지원합니다.
C++에서 입력 시스템을 구현할 때는 InputComponent를 통한 바인딩, 다양한 입력 유형 처리, 터치 입력 지원, 사용자 정의 입력 장치 통합 등을 고려해야 합니다.