기존의 DataAsset을 사용한 IMC 맵핑 후 UI통한 키리맵핑에 대해서 추가 작성하도록 하겠습니다.

IMC와 매핑한 데이터에셋 UI 연동과정

 

키리맵핑 API 구현시 사용된 함수 및 관련 함수

UI 입력 구조 - Unreal Engine

 

UI 관련

 

NativeOnInitialized

  • 위젯 생성 시 호출되는 UUserWidget내 함수
  • Initialized()와 같이 위젯 생성시 초기화, AddDynamic과같은 바인딩 작업 할 시 사용
  • Initialized는 리턴타입 bool, 내부적으로 초기화시 많은 내부적인 작업을 처리
  • NativeOnInitialized 리턴타입 void, 언리얼 블루프린트와 관련된 OnInitialized 호출
  • 기존의 Native가 아닌 함수들은 BlueprintImplementableEvent로 인한 C++클래스 내 재정의 불가
    • Ex) PreConstruct, OnInitialized 등

bIsFocusable

  • UI위젯이 키보드 입력을 받을 수 있도록 설정하는 변수
  • 기본값 false
  • Set Keyboard, OnKeyDown, OnKeyUp등의 키보드 관련 이벤트 함수 처리 가능

FReply NativeOnKeyDown(FGeometry,FKeyEvent)

  • UI 위젯 내 키보드 키가 눌렸을 때 실행되는 함수
  • ETriggerEvent::Started와 같은 개념
  • bIsFocusable가 활성화 된 경우 UI를 통한 키 입력 처리 가능
  • FGeometry
    • UI 위젯의 위치, 크기, 화면 변환 정보를 포함하는 구조체
    • 배치 정보 및 좌표 변환 시 사용
    • UI 내부에서 마우스나 키보드 입력의 좌표 변환 가능
  • FKeyEvent
    • 키보드 이벤트 정보를 담고있는 구조체
    • 어떤 키가 눌렸는지, 조합키(Shift,Ctrl,Alt)가 눌렸는지 확인 가능
    • GetKey() : 눌린 키 (FKey 타입) 반환
    • Is[Shitft/Control/Alt/Reapeat]()
      • 각각 [Shitft/Ctrl/Alt] 키가 눌렸는지에 대한 여부
    • IsRepeat
      • 키가 반복 입력 중인지 확인
      • 길게 누를시 True
      • ETriggerEvent::Triggered같은 개념
  • FReply 
    • 입력을 어떻게 처리할지에 대한 여부를 결정하는 구조체
    • Handled() : UI에서 입력을 처리했음을 엔진에 알린다. (게임에 전달되지 않는다.)
    • UnHandled() : UI에서 입력을 무시, (게임 혹은 다른위젯에 전달)
      • 해당 위젯이 활성화 되어있음에도, 게임 내 캐릭터가 특정 FKey의 입력을 받을 때 등
    • CaptureMouse(Widget) : 특정 위젯에서 마우스 입력 캡처
    • ReleaseMouseCapure() : 마우스 입력 캡처 해제
    • SetUserFocus(Widget) : 특정 위젯에 UI 포커스 설정
    • SetKeyboardFocus(Widget) : 특정 위젯에서 키보드 입력 유지
    • SetMousePos(Position) : 마우스 커서 위치 설정

 

OnClicked, Pressed,Hovered...

  • UMG UButton버튼 관련 이벤트 처리 함수
  • AddDynamic을 통해 C++에서 이벤트 바인딩
  • OnClicked : 버튼이 클릭될 때 실행
  • OnPressed : 버튼을 눌렀을 때 실행
  • OnReleased :  버튼을 땟을 때 실행
  • OnHovered : 버튼에 마우스가 올라갔을 때 실행
  • OnUnhovered : 버튼에서 마우스가 벗어났을 때 실행

키리맵핑 관련

UInputMappintContext (IMC)

  • 여러개의 키 바인딩을 저장하는 컨테이너
  • 한번의 컨텍스트에 여러 입력 액션을 포함 가능
  • 해당 컨텍스트를 UEnhancedInputLocalPlayerSubsystem*을 통한 입력 제어 변경 가능

 

FEnhancedActionKeyMapping

  • UInputAction과 키를 연결하는 구조체
  • IMC에 포함할 키들을 개별적으로 매핑을 담당
  • 여러개의 키를 하나의 액션에 바인딩 가능
  • FKey, Modifiers,Triggers등 추가 가능

UInputModifier

  • 입력 값을 수정하는 역할
  • IMC의 Modifiers
  • 외 입력값 Value 반대로 변경하는 UInputModifierNegate와 같은 다양한 입력 값 관련 조절 가능
  • IMC에 매핑시 배열형식의 TArray<UInputModifier*> modifiers를 사용한 모디파이어 추가 가능

 

키리맵핑 구현

 

발로란트 - UI통한 키리맵핑 시 참조한 UI

 

  1. ActionName / Binding Action FKey 형식의 구조
  2. 버튼 클릭(OnClicked) 시 SetText()내 입력된 FKey값 " - " 변경
  3. 적용 버튼 클릭 시 변경된 값 적용, 되돌리기를 통해 기본값으로 복원

OptionMenuWidget.cpp

// 초기화
void UUserWidget::NativeOnInitialized()
{
	Super::NativeOnInitialized();
	// ... 유효성 검사
 
	// UButton 이벤트 바인딩 과정
	// IsBound : 이미 바인딩이 되어있는가?
	if (ApplyButton && !ApplyButton->OnClicked.IsBound())
	{
		ApplyButton->OnClicked.AddDynamic(this, &UUserWidget::OnApplyButtonClicked);
	}
	if (ResetButton && !ResetButton->OnClicked.IsBound())
	{
		ResetButton->OnClicked.AddDynamic(this, &UUserWidget::OnResetButtonClicked);
	}
}

// Apply Button OnClicked 
void UUserWidget::OnApplyButtonClicked()
{
	for (auto& KeyMapping : CurrentKeyDataAsset->KeyMappings)
	{
		// Enum to String
		FString ActionName = StaticEnum<EPlayableInputAction>()->GetNameStringByValue(static_cast<int64>(KeyMapping.InputActionEnum));
		// 적용을 누르기 전까지 DataAssets에 반영되지 않는다. TMap<FString,FKey> PendingKeyChanges를 통한 변경 값 임시 저장
		// Apply시 임시 저장한 PendingKeyChanges를 연동한 DataAssets에 반영한다.
		if (PendingKeyChanges.Contains(ActionName))
		{
			KeyMapping.CurrentKey = PendingKeyChanges[ActionName];
		}
	}
	// 적용 후 초기화
	PendingKeyChanges.Empty();

	// PlayController에게 변경된 DataAsset을 통한 키 리맵핑 요청
	if (APlayerController* PC = Cast<APlayerController>(GetOwningPlayer()))
	{
		PC->UpdateCurrentIMC(CurrentKeyDataAsset);
	}
}

// Reset 로직... (생략)
// Apply와 같은 로직, CurrentKey를 DefaultKey로 변경 후 요청
// 임시저장 PendingKeyChanges 초기화

// 델리게이트 Dynamic_Multicast
void UUserWidget::HandleKeyBindingUpdated(UOptionKeyBindWidget* Widget, FKey NewKey)
{
	if (!Widget) return;

	PendingKeyChanges.Add(Widget->CurrentData.ActionName, NewKey);
}

PlayerController

void PlayerController::UpdateCurrentIMC(DataAsset* CurrentDataAsset)
{
    // 동적 환경에서 빙의된 폰이 있는지에 대한 검사.
    // 빙의가 된 경우, Subsystem 최신화, 아닌경우 변경된 DataAsset과 매핑한 IMC만 변경하기위해
    bool bHasPawn = (GetPawn() != nullptr);

    if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
    {
        if (UEnhancedInputLocalPlayerSubsystem* SubSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
        {
            // TMap ControlModeMap : 3인칭,1인칭,그외(자동차, 드론 등)을 구분하는 Key, IMC와DataAssets을 매핑한 Value
            for (auto& ControlMap : ControlModeMap)
            {
                // UI에서 받아온 DataAsset과 일치한 경우를 모두 변경하기위한 조건문 추가
                if (ControlMap.Value.DataAsset == CurrentDataAsset)
                {
                    // IMC 인스턴스 생성
                    UInputMappingContext* NewIMC = NewObject<UInputMappingContext>(this);
                    // IMC 매핑할 Key들을 각각 하나씩 설정한다.
                    for (const FPlayerDefaultInputKeyMapping& Mapping : CurrentDataAsset->KeyMappings)
                    {
                        if (Mapping.InputAction && Mapping.CurrentKey.IsValid())
                        {
                            // 개별 키 FEnhancedActionKeyMapping를 통한 DataAsset내 IA, FKey 설정
                            FEnhancedActionKeyMapping& NewMapping = NewIMC->MapKey(Mapping.InputAction, Mapping.CurrentKey);
                            // 개발자가 미리 설정한 Modifier을 다시 적용한다.
                            // 해당 과정 누락 시 모디파이어를 통한 Ex) FKey A Value -1(Native) -> 1로 변경, 원하는 입력이 안된다.
                            for (UInputModifier* Modifier : Mapping.Modifiers)
                            {
                                if (Modifier)
                                {
                                    NewMapping.Modifiers.Add(Modifier);
                                }
                            }
                        }
                    }
                    
                    // DataAsset과 매핑한 IMC내 IA들을 변경된 NewIMC를 통해 변경
                    ControlMap.Value.IMC = NewIMC;
                    // 빙의된 Pawn/Character이 있을 경우, 실시간 반영을 위한 Subsystem을 통한 IMC 변경
                    // Ex) 인게임 내 조작키 변경 시 실시간 반영
                    if (bHasPawn && ControlMap.Key == CurrentMode)
                    {
                        SubSystem->ClearAllMappings();
                        SubSystem->AddMappingContext(NewIMC, 0);
                    }
                }
            }
        }
    }
}

 

 

구현 결과

https://www.youtube.com/watch?v=atH2eThw2NI

 

 

 

 

 


 

출처 및 참고내역

 

https://community.gamedev.tv/t/nativeoninitialized-may-replace-initialize-and-setup/152157

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/input-fundamentals-for-commonui-in-unreal-engine

https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/UMG/Components/UButton

 

 

+ Recent posts