mystic-agit 개발 블로그

[RemoteAccess] 모바일에서 Windows Application 원격 제어하는 시스템 구현 - Unreal 에서 키맵핑(Enhanced Input) 활용 (5) 본문

Platform/RemoteAccess

[RemoteAccess] 모바일에서 Windows Application 원격 제어하는 시스템 구현 - Unreal 에서 키맵핑(Enhanced Input) 활용 (5)

mystic-agit 2024. 8. 22. 15:09

 

연관글 목록
[RemoteAccess] 모바일에서 Windows Application 원격 제어하는 시스템 구현 - 시스템 구상 (1)
[RemoteAccess] 모바일에서 Windows Application 원격 제어하는 시스템 구현 - Mobile Application 구현 (2)
[RemoteAccess] 모바일에서 Windows Application 원격 제어하는 시스템 구현 - Server 구현 (3)
[RemoteAccess] 모바일에서 Windows Application 원격 제어하는 시스템 구현 - Windows Unreal Application 구현 (4)
[RemoteAccess] 모바일에서 Windows Application 원격 제어하는 시스템 구현 - Unreal 에서 키맵핑(Enhanced Input) 활용 (5)

 

목  차
1. Enhanced Input 기능 추가하기
2. Enhaned Input 활성화
3. Content Brower 창 열기
4. InputAction Asset Type으로 action을 구성
5. InputMappingContext Asset Type으로 매핑 정보를 구성
6. Enhanced Input으로 InputAction 등록
7. 키보드 입력 이벤트 발생
8. 동작
9. Github

 

 

이전 글에서 Server로 부터 데이터를 받은 경우 동작하고자 하는 함수를 호출하여 처리하고 있다.

다만, 어느 키가 어느 동작으로 이어지는지를 함수 단위로 제어하면 유지관리 복잡성이 올려갈 것으로 예상된다.

이를 조금이라도 도울 수 있게 특정 함수를 키맵핑으로 연결한다면

Server로 부터 전달받은 데이터를 확인하여 그 키가 눌렸다고 호출하면 코드 관리에 효율이 올라갈 것으로 예상된다.

 

이를 감안하여

  • Unreal 키맵핑 기능 (Enhanced Input)
  • Unreal 키보드 입력 구현

두 가지를 확인해보았다.

 

 

1. Enhanced Input 기능 추가하기

  • Edit > Plugins 에서 Enhanced Input 을 검색하면 플러그인을 찾을 수 있음

 

2. Enhanced Input 활성화

  • Edit > Project Settings 에서 Input > Default Classes 에서 ‘Default Player Input Class’ 와 ‘Default Input Component Class’를 각각 ‘EnhancedPlayerInput’ 과 ‘EnhancedInputComponent’ 선택

 

3. Content Browser 창 열기

  • Windows > Content Browser 수행



4. InputAction Asset Type으로 action을 구성

 

5. InputMappingContext Asset Type으로 매핑 정보를 구성

  • Mappings 항목에서 InputAction Asset 을 찾아 선택
  • 해당 Action 동작과 연결되는 키보드, 마우스, 패드 등의 이벤트를 등록
  • Setting Behavior 에서 Action에 대한 추가 이벤트도 설정 가능

 

6. Enhanced Input 으로 InputAction 등록

// InputAction 과 연결된 구체화된 코드 구현

void ARemoteAccessTest003Character::Move(const FInputActionValue& Value)
{
   // input is a Vector2D
   FVector2D MovementVector = Value.Get<FVector2D>();


   if (Controller != nullptr)
   {
       // add movement
       AddMovementInput(GetActorForwardVector(), MovementVector.Y);
       AddMovementInput(GetActorRightVector(), MovementVector.X);
   }
}




void ARemoteAccessTest003Character::Look(const FInputActionValue& Value)
{
   // input is a Vector2D
   FVector2D LookAxisVector = Value.Get<FVector2D>();


   if (Controller != nullptr)
   {
       // add yaw and pitch input to controller
       AddControllerYawInput(LookAxisVector.X);
       AddControllerPitchInput(LookAxisVector.Y);
   }
}

// .. 생략..
// InputAction 을 SetupPlayerInputComponent 함수에 등록
// - SetupPlayerInputComponent 함수는 Unreal Engine에서 제공하는 함수이며 ACharacter, AActor 등 게임 오브젝트에 사용하는 클래스가 상속하고 있음, override 하여 추가 행동을 입력 가능

// EnhancedInputComponent 에 BindAction 처리

void ARemoteAccessTest003Character::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{  
   // Set up action bindings
   if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
   {
       // Jumping
       EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
       EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);


       // Moving
       EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ARemoteAccessTest003Character::Move);


       // Looking
       EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ARemoteAccessTest003Character::Look);
   }
   else
   {
       UE_LOG(LogTemplateCharacter, Error, TEXT("'%s' Failed to find an Enhanced Input Component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));
   }
}

// .. 생략..

 

7. 키보드 입력 이벤트 발생

void ARemoteAccessTest003Character::BeginPlay()
{
	//..(생략)..

	// 키 처리를 할 수 있는 PlayerController 구성
	// Get the Player Controller
    APlayerController* PlayerController = Cast<APlayerController>(GetController());
    if (PlayerController && InputMappingContext)
    {
		UE_LOG(LogTemp, Log, TEXT("BeginPlay AddMappingContext 001"));
        // Add the Input Mapping Context
        if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
        {
            Subsystem->AddMappingContext(InputMappingContext, 0);
			UE_LOG(LogTemp, Log, TEXT("BeginPlay AddMappingContext 002"));
        }
    }
}

// Unreal의 PossessedBy 함수에도 PlayerController 구성
void ARemoteAccessTest003Character::PossessedBy(AController* NewController)
{
    Super::PossessedBy(NewController);

    APlayerController* PlayerController = Cast<APlayerController>(NewController);
    if (PlayerController && InputMappingContext)
    {
        // Add the Input Mapping Context
        if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
        {
            Subsystem->AddMappingContext(InputMappingContext, 0);
        }
    }
}

// 매 프레임마다 호출되는 Tick 함수에서 Server 응답 처리
void ARemoteAccessTest003Character::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (Socket && Socket->GetConnectionState() == ESocketConnectionState::SCS_Connected)
    {
        ReceiveData();
    }
}

// 키보드 pressed 동작 이후에 released 하는 동작 구현
void ARemoteAccessTest003Character::ReleaseKey()
{
    APlayerController* PlayerController = Cast<APlayerController>(GetController());
    if (PlayerController)
    {
        // Simulate the key release
        PlayerController->InputKey(KeyToRelease, EInputEvent::IE_Released, 1.0f, false);
		UE_LOG(LogTemp, Log, TEXT("ReleaseKey done"));
    }
}

// 키보드 pressed 동작 구현
void ARemoteAccessTest003Character::SimulateKeyMappingPress(FKey Key)
{
    APlayerController* PlayerController = Cast<APlayerController>(GetController());
    if (PlayerController)
    {
        // Simulate the param 'Key' press
        PlayerController->InputKey(Key, EInputEvent::IE_Pressed, 1.0f, false);

		// Store the key to be released
		KeyToRelease = Key;

		// 타이머 시간 이후 key release 처리
		GetWorld()->GetTimerManager().SetTimer(
			KeyReleaseTimerHandle,
            this,
            &ARemoteAccessTest003Character::ReleaseKey,
            0.5f,
            false
        );
    }
}

// Server에서 전달받은 데이터 확인하여 키보드 입력 이벤트 수행
void ARemoteAccessTest003Character::ReceiveData()
{
    uint32 Size;
    while (Socket->HasPendingData(Size))
    {
        ReceivedData.SetNumUninitialized(FMath::Min(Size, 65507u));

        int32 Read = 0;
        Socket->Recv(ReceivedData.GetData(), ReceivedData.Num(), Read);

        const std::string cstr(reinterpret_cast<const char*>(ReceivedData.GetData()), Read);
        FString ReceivedString = FString(cstr.c_str());

		//..(생략)..
		
		// Server 데이터 to 키보드 입력 전환
		if(ReceivedString == TEXT("w")) {
			SimulateKeyMappingPress(EKeys::W);
		}
		else if(ReceivedString == TEXT("a")) {
			SimulateKeyMappingPress(EKeys::A);
		}
		else if(ReceivedString == TEXT("d")) {
			SimulateKeyMappingPress(EKeys::D);
		}
		else if(ReceivedString == TEXT("s")) {
			SimulateKeyMappingPress(EKeys::S);
		}

        UE_LOG(LogTemp, Log, TEXT("Received: %s"), *ReceivedString);
    }
}

 

8. 결과

  • 키보드 눌림과 동일한 이벤트가 발생했고 이와 연결된 조건의 action이 동작하는 것을 확인하였다.
  • Windows 잠금 화면에서도 동작함을 확인하였다.
    • 잠근 화면이라는 조건이 없다면 '데이터-키' 를 연결하는 코드 없이 Windows API로 키 발생만 일으킨다면 맵핑된 동작을 할 것으로 예상된다.

9. Github

Comments