Class GameDev* SheepAdult

[Unreal Engine] Level Change Crash (Lambda / Delay Crash) 본문

Unreal Engine

[Unreal Engine] Level Change Crash (Lambda / Delay Crash)

SheepAdult 2023. 3. 26. 22:52

 언리얼 엔진 위에서 Delay 기능에 대해 구글링하면 람다 함수를 활용하는 방법이 나온다. 예를 들어 아래와 같다.

FTimerHandle WaitHandle;
float WaitTime;
GetWorld()->GetTimerManager().SetTimer(WaitHandle, FTimerDelegate::CreateLambda([&]()
{

	// 딜레이 후 코드

}), WaitTime /*대기 시간*/, false /*반복*/);

 그런데 "만약 딜레이 후 코드" 부분이 레벨이 바뀐 후 호출된다면 크래쉬가 나버린다. 그래서 처음엔 코드에 작성된 객체의 포인터가 레벨이 바뀌면서 참조 해제되어 null을 가리켜서 그런가 보다 생각해 객체를 null check를 해주었다. 그런데도 크래쉬가 났다... 좀 더 삽질을 한 결과 레벨을 전환하기 전에 FTimerHandle을 Clear 시켜주고 invalidate 시켜주면 될 것 같아 시도했다.

 GameInstance에서 Delegate를 선언한 후 레벨 전환 직전에 delay를 사용하는 스크립트에서 Broadcast 해준다. GameInstance는 모든 스크립트에서 접근이 가능하므로 이와같이 했다.

// GameInstance.cpp
void UMyGameInstance::ResetTimerOnLevelChange()
{
	if (OnLevelChange.IsBound() == true)
	{
		OnLevelChange.Broadcast();
	}
}

이는 ESC 메뉴의 Quit 버튼에 바인딩 해놓은 함수에서 호출한다.

// ESCMenuWidget.cpp
void UESCMenuWidget::QuitGame()
{
	GameInstance->ResetTimerOnLevelChange();
	UGameplayStatics::OpenLevel(GetWorld(), FName("Main_Menu"));
}

그 후 TimerHandle들은 전역 변수로 선언한 후 이를 호출할 함수를 GameInstance의 Delegate와 바인딩해 준다.

// MainCharacter.cpp

void BeginPlay() {
	...
	GameInstance->OnLevelChange.AddUFunction(this, FName("ResetTimerOnLevelChange"));
    ...
}

해당 함수는 아래와 같이 작성했다. 

void AMainCharacter::ResetTimerOnLevelChange() 
{
	GetWorld()->GetTimerManager().ClearTimer(WaitHandle1);
	GetWorld()->GetTimerManager().ClearTimer(WaitHandle2);
	GetWorld()->GetTimerManager().ClearTimer(WaitHandle2);
	WaitHandle1.Invalidate();
	WaitHandle2.Invalidate();
	WaitHandle3.Invalidate();
}

그러면 레벨이 바뀌기 전에 모든 TimerHandle이 Clear 되어 크래쉬를 발생시키지 않는다.

 

다른 방법으로는, 람다 캡처 방식을 변경하면 된다. 해당 방식에서는 모든 정보를 참조하는 방식으로 실행되기 때문에 참조할 정보가 없어져 문제가 발생한다. 이를 WeakPtr로 참조하여 진행하여 문제를 해결해도 된다.