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 정망스
,
728x90

-TArray

Reset() : 배열의 모든 요소를 제거, 할당된 메모리는 유지, 즉 배열을 다시 채울 계획이 있어서 메모리가 재할당을 피하고 싶을때 사용

Empty() : 배열의 모든 요소를 제거, 할당된 메모리 까지 해제, 즉 배열을 더이상 사용하지 않아서 메모리 사용을 줄이고 싶을때 사용

 

-Delegate

UFUCTION() 매크로 : 델리게이트에 등록하여 사용할 함수는 UFUNCTION 매크로를 선언해주어야 한다. 이유는 언리얼에서 이 매크로를 통해 리플렉션 시스템이 해당 함수를 인식하고 등록하여 사용될수 있도록 작동하기 때문

IsBound() or IsAlreadyBound() : 언리얼에서는 델리게이트에 함수를 등록하려할때 이미 해당함수가 등록되어 있으면 에러를 발생시킨다.
언리얼에서는 기본적으로 중복 등록을 허용하지 않으며, 중복 등록으로 인한 의도하지 않은 동작, 메모리 누수와 같은 성능문제, 기타 예상치 못하는 예외 발생등을 고려하여 허용하지 않는다.

그래서 함수를 등록전에 이 함수들을 사용하여 이미 등록되어있는지 체크를 하여 등록 해줄수 있다.

RemoveAll(const void* InUserObject) or Clear() : 위 함수 델리게이트 등록전 등록 여부를 체크하는것이 아닌, 이 함수들을 사용하면 현재 델리게이트에 등록되어있는 함수들을 제거해준다.
차이점은 RemoveAll은 해당하는 객체에 등록된 모든 함수를 제거 해주는것으로 보통은  this인자로 해당 객체를 넘겨준다, Clear는 델리게이트에 등록된 모든 함수를 그냥 제거해 주는것이다.

그래서 델리게이트에 중복 등록 여부 체크를 하고 싶지 않다 하면 종료시점에 델리게이트에 등록된 함수를 제거해주는것도 하나의 방법이다.

728x90
Posted by 정망스
,
728x90

작업을 하던중 브러시 모드에서 펜툴을 이용해 내가 원하는 모양의 지오메트리를 생성했고
브러시 세팅 탭에서 브러시 셰이프를 내가 직접 만든 클래스 (ATriggerVolume 상속)로 변환해주었다.
의도는 내가 원하는 모양의 충돌 영역을 만들고 싶었던것이다.

그런데 작업을 하고 충돌 체크를 확인해보는데 아예 충돌 자체가 안되는 것이다.

찾다가 브러시 버텍스 정렬이라는 것을 보았고 이걸 한번 거친후에 다시 확인해보니 충돌이 제대로 되었다.

정확한 정보인지는 알수 없지만 내가 알아본 바로는
언리얼 엔진에서 브러시를 사용하여 TriggerVolume을 만들 때, 브러시의 버텍스(Vertex)가 제대로 정렬되지 않으면 충돌 체킹이 제대로 작동하지 않을 수 있다라는 것이다. 

브러시 버텍스 정렬을 통해  기대할수 있는 상황은 아래 3가지 정도였다

1. 정확한 지오메트리 생성: 브러시의 버텍스가 정렬되지 않으면, 브러시의 지오메트리가 왜곡되거나 비정상적으로 생성될 수 있다. 이로 인해 충돌 체킹이 제대로 작동하지 않을 수 있다.
버텍스 정렬을 통해 브러시의 지오메트리가 정확하게 생성되면, 충돌 체킹도 정상적으로 작동하게 된다.

2. 충돌 데이터 갱신: 브러시의 버텍스를 정렬하면, 언리얼 엔진은 브러시의 충돌 데이터를 갱신한다.
이 과정에서 충돌체의 경계와 폴리곤이 다시 계산되므로, 충돌 체킹이 정상적으로 작동하게 된다.

3. 브러시의 일관성 유지: 버텍스 정렬은 브러시의 일관성을 유지하는 데 도움이 된다.
일관된 브러시는 충돌 체킹과 같은 물리 연산에서 더 예측 가능하고 안정적으로 작동한다.

비슷한 상황으로 안된다면 버텍스 정렬을 해보자.

728x90
Posted by 정망스
,
728x90

언리얼에서 TFieldIterator을 사용하여 리플렉션으로 속성과 함수들의 검색이 가능하다.

멤버 변수에는 UPROPERTY, 멤버 함수에는 UFUNCTION 매크로를 지정해주어야 가능하다.

 

TFieldIterator<UProperty>타입을 사용하면 속성을 가져올수 있다.

for (TFieldIterator<UProperty> It(ClassInfo1); It; ++It)
{
    AB_LOG(Warning, TEXT("Field : %s, Type : %s"), *It->GetName(), *It->GetClass()->GetName());
    UStrProperty* StrProp = FindField<UStrProperty>(ClassInfo1, *It->GetName());
    if (StrProp)
    {
       AB_LOG(Warning, TEXT("Value = %s"), *StrProp->GetPropertyValue_InContainer(WebConnection));
    }
 }

 

TFieldIterator< UFunction >타입을 사용하면 함수을 가져올수 있다.

for ( TFieldIterator<UFunction> FunctionIterator( MyObjectsClass ); FunctionIterator; ++FunctionIterator )
{
    UFunction* Func = *FunctionIterator;
    
    UE_LOG( LogTemp, Log, TEXT( "%s" ), *FunctionIterator->GetName() ); 
}

 

함수의 경우 NativeFunctionLookupTable 배열을 사용해 현재 클래스에 어떤 함수가 있는지 알수 있다.

함수의 이름을 사용하여 FindFunctionByName함수를 사용해 가져올수 있다.

for (const auto& Entry : ClassInfo1->NativeFunctionLookupTable)
{
    AB_LOG(Warning, TEXT("Function=%s"), *Entry.Name.ToString());
 
    UFunction* Func1 = ClassInfo1->FindFunctionByName(Entry.Name);
    if (Func1->ParmsSize == 0)
    {
        WebConnection->ProcessEvent(Func1, NULL);
    }
}

 

728x90
Posted by 정망스
,
728x90

-게임 인스턴스 (컨텐츠에 관여하는 용도로 사용하기보다, 어플리케이션의 데이터를 관리하는 용도로 많이 사용)

-에셋 매니저

-게임 플레이 관련 액터 (게임모드, 게임 스테이트)

-프로젝트에 싱글톤으로 등록한 언리얼 오브젝트

728x90
Posted by 정망스
,


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