Unreal/TroubleShooting

[TroubleShooting]GetWorldTimeManager() - UnrealEngine

workbench34 2025. 2. 14. 20:33

 바로 전날 GetWorldTimeManager 에 대해 알아보았다. 정확히는 내 언리얼 프로젝트에서 이와 관련된 오류가 발견되어 더 깊게 알아보고자 정리한 것이었다. 오늘은 내 프로젝트에 어떤 오류가 있었고 어떻게 해결했는지 적어보겠다.

 

문제 상황

 MineItem 이라는 클래스에서 ActivateItem 함수에 GerWorldTImeManger 을 이용해 1.7 초 뒤 이 엑터가 Destroy 되며 데미지를 주는 로직을 짰고 이는 잘 작동했다. 그러나 내 프로젝트에서는 웨이브가 끝나면 다음 레벨로 바로 넘어가지기에 웨이브가 끝나기 직전 MineItem 이 Activate 되었으면 갑자기 바뀐 World 에 의해 MineItem 이 존재하지 않게 되는 거였다!

그러나 MineItem 은 GetWorldTimerManger 을 이용해 1.7초 뒤 데미지를 주면서 파티클을 만들어야했다. 데미지를 주는 부분은 상관없으나 파티클을 생성하는 과정에서 없는 MineItem 과 바뀐 WorldTimeManger 에 의해 오류가 난 것 이었다.

void ABaseItem::ActivateItem(AActor* Activator) 
{
	UParticleSystemComponent* Particle = nullptr;

	if (PickupParticle)
	{
		Particle = UGameplayStatics::SpawnEmitterAtLocation(
			GetWorld(),
			PickupParticle,
			GetActorLocation(),
			GetActorRotation(),
			true
		);
	}
	if (PickupSound)
	{
		UGameplayStatics::PlaySoundAtLocation(
			GetWorld(),
			PickupSound,
			GetActorLocation()
		);
	}

	if (Particle)
	{
		FTimerHandle DestroyParticleTimerHandle;

		GetWorld()->GetTimerManager().SetTimer(
			DestroyParticleTimerHandle,
			[Particle]()
			{
				Particle->DestroyComponent(); // 오류가 난 부분
			},
			0.5f,
			false
		);

	}
}

 

 즉 정리하자면

 

1. 레벨이 넘어가기 직전 MineItem 이 Activate 되는 상황이 나왔다.

2. 레벨이 넘어가자 MineItem 안의 함수에 GetWordlTimeManger 가 HandeTime 을 불러오지 못했다. 또한 MineItem Actor 도 찾을 수 없어 이에 대한 함수를 실행시킬 수 없게 되었다.

3. 그렇기에 파티클을 보여주고 DestroyComponent 해주는 과정에서 컴파일 에러가 났다..

 

문제 해결

 처음에는 레벨이 넘어갈 때 ClearTime 을 통해 TimerHandle 을 해제하려고 하였다. 그러나 EndLevel 에 대한 로직은 GamseState 에 존재하고 이를 확인하려면 GameState 에서 EndLevel 함수를 사용할 때 MineItem 이 이를 참조하여 TimerHandle 을 해제하는 명령어를 실행해야 하는 복잡한 상황이다. 그렇기에 이것은 기각이되었다.

 

 두 번째로는 라운드가 끝나고 레벨이 넘어갈때 3초 간의 유예시간을 주어 이펙트들이 모두 실행 된 뒤 라운드가 넘어가게 만들기로 계획하였다. 약간의 편법같지만 EndLevel 함수를 호출해야할 상황에 GetWorldTimeHandle 을 이용해 3.0f 초 뒤에 EndLevel 함수가 실행되도록 만들었다. 실제로 이 방법은 유효했다! 그러나 이 3초 동안 플레이어가 MineItem 에 오버랩 되면 다시 오류가 나기 시작했다...

 

 결국은 두 번째 방법을 보완하기로 결정했다. 원래 게임에서 다음 레벨로 넘어가는 조건은 2개였다. 

 

1. 맵에 스폰된 코인을 모두 획득하기

2. 시간초 동안 생존하기

 

 1 번과 2 번의 조건이 다르기에 서로 다른 코드로 EndLevel 을 호출하게 만들었는데 둘다 오류가 발생하기에 한 함수로 묶어 EndLevel 을 호출하게 만들었다. 아래가 그 코드의 원본이다.

void ASpartaGameState::OnLevelTimeUp()
{
	EndLevel();
}

 원래는 2번 조건을 만족할 때 EndLevel 을 호출하게 만들었으나 이제는 3초 뒤에 EndLevel을 호출하게 만들었다. 그리고 1번 조건에도 이 OnLevelTimeUp() 을 호출하게 만들어 주었다. 아래는 수정한 코드이다.

void ASpartaGameState::OnLevelTimeUp()
{
	FTimerHandle EndLevelTimerHandle;
	GetWorldTimerManager().SetTimer(EndLevelTimerHandle, this, &ASpartaGameState::EndLevel, 3.0f, false);

}

 FtimerHandle을 선언해주고 3.0f 초 뒤에 한번 EndLevel 을 호출하게 만들어 주었다. 그러나 아직 문제가 남았다. 이 3초 동안 플레이어가 마인 아이템을 건드리면 다시 오류가 발생한다! 그렇다면 플레이어가 아이템을 먹을 수 없게 만들면 되지 않을까? 그렇기에 PlayerController 에서 Pawn 을 불러와 그 폰의 콜리전을 DisableCollision() 을 통해 오버랩이 불가능 하게 만들었다. 아래가 최종 코드이다.

void ASpartaGameState::OnLevelTimeUp()
{
	if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
	{
		if (ASpartaCharacter* PlayerCharacter = Cast<ASpartaCharacter>(PlayerController->GetPawn()))
		{
			PlayerCharacter->DisableCollision();
		}
	
	FTimerHandle EndLevelTimerHandle;
	GetWorldTimerManager().SetTimer(EndLevelTimerHandle, this, &ASpartaGameState::EndLevel, 3.0f, false);

}

 

 

문제 해결이 끝나고

 이번에 문제 해결을 하며 뿌듯하였는데 처음 오류가 생기자마자 어떤 곳이 문제인지 바로 알았기 떄문이었다. 그러나 이 문제를 해결하는데는 좀 오래 걸렸었다. 처음에는 깔끔하게 ClearTimer 을 사용하면 될 줄 알았는데 먹히지 않아서 결국은 편법으로 문제를 해결하였다. 이게 최선의 수였을까? 라고 물으면 난 단호하게 아니라고 말할 수 있다. 그러나 이런 방식으로 문제를 해결했기에 게임의 완성도는 조금 높아진 느낌이다.

 

 완성도가 높아졌다 느낀 이유는 라운드가 끝나고 3초 동안의 시간이 주어지니 약간의 숨을 돌릴 시간이 되기도 하고 이 시간에 UI 를 추가하여 다음 라운드로 넘어감을 예고 할 수 있게 되었다. 처음에는 라운드가 끝나자마자 바로 다음 라운드로 넘어갔는데 다시 돌이켜보면 게임으로서는 조금 어색했던 것 같다.

 

 오늘 경험으로 느낀바는 트러블 슈팅은 문제를 최적으로 해결하는 것 보다는 이를 통해 전체적으로 더 완성도 있게 만드는 방법인 것 같다. 앞으로 많은 오류가 생길 것이고 많은 해결방법을 찾아내야 할 것이다. 그 중 어려웠거나 흥미로웠던 부분들은 트러블 슈팅을 작성해 보도록 하겠다.