728x90

우선 이슈와 관련해서 간략한 구조 환경은
WidgetManager : 요청받은 팝업 UI를 생성 및 배치해주고 이후 관리하는 역할.
UserWidget : 팝업 UI를 구성하는 부모 클래스, 팝업이 show될때 인풋모드를 FInputModeUIOnly(UI 모드), hide될때 FInputModeGameOnly(게임 모드)로 변경.

팝업이 show, hide여부에 따라 인풋모드를 변경하다 보니 중간중간 팝업 발생 여부에 따라 플레이어 캐릭터의 이동과 관련된 입력이 자연스럽지 않은 상황이였다.
(ex. 이동중에 팝업이 발생하여 이동을 게속 이어서 하기위해 키를 누르고 있지만 팝업이 종료되면 인풋이 초기화되서 플레이어 캐릭터가 가만히 있는 상황)

이 상황을 해결하기 위해서 WidgetManager에서 최초 팝업이 오픈될때
현재 플레이어 컨트롤러로 부터 입력되고 있는 키를 체크해서 저장을 하도록 했다. 이유는 팝업이 종료될때 해당키를 계속 누르고 있다면 해당키를 누르고 있다라는 상황을 복구 시켜 주어서 캐릭터가 바로 이동 되도록 위함이다.

최초 팝업때만 저장을 하는 이유는 팝업 1개가 show되는 순간 인풋모드가 FInputModeUIOnly로 변경되면서 이후 플레이어 컨트롤러의 입력은 무시되기 때문에 최초1회에 한해서만 저장하고 이후 키입력에 대한 체크는 최상단에 배치되는 팝업의 NativeOnKeyDown과, NativeOnKeyUp에서 입력되는 키를 체크하여 수시로 어떤 키들이 입력되고 있는지 변경사항을 저장하도록 하였다.

테스트 했을때는 UI에 SetFocus와 같은 포커싱을 제때 주면 키를 계속 입력하고 있으면 NativeOnKey함수에서 바로 체크가 되었다. 

코드의 일부는 이렇다

void UWidgetManager::CheckMovementKeys()
{
	if (WidgetStack.IsEmpty())
	{
	 	MovementKeyStates.Reset();
	}

	if (APlayerController* playerController = GetPlayerController())
	{
		TArray<FKey> movementKeys = { EKeys::W, EKeys::A, EKeys::S, EKeys::D };
		for (const FKey& Key : movementKeys)
		{
			if (playerController->IsInputKeyDown(Key))
			{
				UpdateMovementKeys(Key, true);
			}
		}

		playerController->FlushPressedKeys();
	}
}

void UWidgetManager::UpdateMovementKeys(FKey key, bool bAdd)
{
	if (bAdd)
	{
		MovementKeyStates.AddUnique(key);
	}
	else
	{
		MovementKeyStates.Remove(key);
	}
}

/* WASD 키에 한해서 입력 상황을 체크하며, 최초 1회 팝업 발생시 CheckMovementKeys를 호출하여
플레이어 컨트롤러의 키가 입력중이면 UpdateMovementKeys를 호출하여 배열에 저장시킨다.

이후 UI에서 발생하는 키입력 또한 NativeOnKeyDown과, NativeOnKeyUp에서 체크하여
UpdateMoventKeys를 호출하여 배열에 저장시킨다. */


모든 팝업이 종료될때 저장되어 있는 키를 입력 복구 시켜준다.
코드의 일부는 이렇다

void UWidgetManager::RestoreMovementKeys()
{
	if (WidgetStack.IsEmpty() == false)
	{
		return;
	}

	if (APlayerController* playerController = GetPlayerController())
	{
		for (const auto& KeyState : MovementKeyStates)
		{
			FInputKeyParams Params;
			Params.Key = KeyState;
			Params.Event = EInputEvent::IE_Pressed;
			Params.Delta.X = 1.0f;
			Params.bIsGamepadOverride = false;

			playerController->InputKey(Params);
		}
	}

	MovementKeyStates.Reset();
}

/* 모든 팝업이 종료되어서 WidgetStack이 0일때만 최종 복구를 시켜준다.
배열에 저장해두었던 키를 순회하면서 플레이어 컨트롤러에 해당 키를 입력중인 상황인것으로
변경시켜준다. */


위처럼 처리 하였을때 일반적인 팝업이 한번 생성되고 종료될때
이동 -> 플레이어 컨트롤러 키 저장 -> 팝업 생성 -> UI 키 저장 -> 팝업 종료 -> 플레이어 컨트롤러 키 복구 -> 이동
과정은 잘되었었다.

하지만 문제가 있었다, 특정 팝업이 종료 된후 조건을 체크하는 델리게이트를 호출하여 충족한다면 해당 팝업을 바로 다시 오픈 시켜주는 경우가 있었는데. 이 상황에서 키 입력 복구가 제대로 되지 않는 현상이 발생했다.

여기서부터는 테스트를 하면서 납득한 추측인데
키를 특정 입력 상황(Pressed)로 변경할때는 이 입력 상황이 적용이 될 충분한 시간(Consume)을 줘야 플레이어 컨트롤러에 적용이 올바르게 되는거 같은데 이 시간이 충분하지 않고 바로 새로운 팝업이 다시 발생하다 보니, 키를 누르고 있지 않는데도 다시 생성된 팝업이 종료되면 그제서야 키 입력상황이 뒤늦게 적용되어 마치 자동 이동이 되버리는 문제가 있었다.

고민 끝에 팝업이 종료되고 델리게이트를 통해 조건 체크 후 다시 팝업를 오픈해주는 이 호출 부분을 한프레임 이후에 호출하도록 처리 했더니 해결이 됬다.

위 이미지는 CheckMovementKeys시점이 팝업 생성, RestoreMovementKeys시점이 팝업 종료 기준으로 보면 된다.
팝업 생성시에 누르고 있는 컨트롤러의 키에 대한 입력중인 초를 체크하였고,
종료후 복구한 키를 체크하였다.

컨트롤러 키를 복구한 이후 새로운 팝업이 생성되더라도 키를 누르고 있다면 해당 입력중인 초가 제대로 찍히고 있는것을 볼수 있었다.

한프레임 이후 처리를 하지 않았을때는 누르고 있더라도 계속해서 입력중인 초가 0초로 찍히면서 제대로 적용이 안되는 상황이였다.

아무래도 추측한 부분이 얼추? 맞는 이유 이지 않을까 싶다.

728x90
Posted by 정망스
,


맨 위로
홈으로 ▲위로 ▼아래로 ♥댓글쓰기 새로고침