icon안동민 개발노트

입력 시스템 (Input) 처리


 언리얼 엔진의 입력 시스템은 다양한 입력 장치로부터의 사용자 입력을 효과적으로 처리할 수 있게 해줍니다.

 이 절에서는 C++에서 입력 시스템을 구현하고 활용하는 방법을 살펴보겠습니다.

입력 처리 과정 및 InputComponent

 언리얼 엔진의 입력 처리는 다음과 같은 과정을 거칩니다.

  1. 입력 이벤트 발생
  2. PlayerController가 입력 이벤트 수신
  3. 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;
}

다양한 입력 유형 처리

  1. 키보드 입력
PlayerInputComponent->BindAction("Action", IE_Pressed, this, &AMyCharacter::PerformAction);
  1. 마우스 입력
PlayerInputComponent->BindAxis("LookUp", this, &AMyCharacter::AddControllerPitchInput);
PlayerInputComponent->BindAxis("Turn", this, &AMyCharacter::AddControllerYawInput);
  1. 게임패드 입력
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

  1. 입력 매핑 문서화
// 프로젝트 설정의 입력 매핑을 코드에서 명시적으로 설명
/** 전후 이동 입력 축. W/S 키 또는 게임패드 왼쪽 스틱 Y축에 매핑됨 */
void AMyCharacter::MoveForward(float Value);
  1. 입력 처리의 모듈화
// 별도의 컴포넌트로 입력 처리 로직 분리
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);
};
  1. 디버그 모드에서의 입력 로깅
void AMyCharacter::MoveForward(float Value)
{
    ##if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
    UE_LOG(LogTemp, Verbose, TEXT("MoveForward: %f"), Value);
    ##endif
 
    // 이동 로직
}
  1. 입력 설정의 데이터 주도적 접근
// 데이터 테이블을 사용하여 입력 설정 관리
USTRUCT(BlueprintType)
struct FInputMappingData : public FTableRowBase
{
    GENERATED_BODY()
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FName ActionName;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    FKey DefaultKey;
};

 언리얼 엔진의 입력 시스템은 다양한 입력 장치와 게임 플레이 요구사항을 효과적으로 지원합니다.

 C++에서 입력 시스템을 구현할 때는 InputComponent를 통한 바인딩, 다양한 입력 유형 처리, 터치 입력 지원, 사용자 정의 입력 장치 통합 등을 고려해야 합니다.