BulletAnt 개발일지 (19) - 트러블슈팅(Egg, TribeManager)

김펭귄·5일 전

Today What I Learned (TIL)

목록 보기
134/139

Egg 자폭/사망 충돌 문제

또 가끔 자폭병이 GA_Die 실행 중 크래시가 발생했다.
문제는 GA_Die에서 몽타주 Task 생성하여 ReadyForActivation() 호출 직후, 드물게 GA Task가 이미 사라져버리는 상황이었다.

자폭병은 자폭 공격, Death 하는 동작이 거의 비슷했고, 둘 다 마지막에 자기 자신을 Destroy() 하고 있었다.
즉, 공격 종료 처리와 Death 처리 타이밍이 겹치며 이미 제거 중인 객체에 접근하는 상황이 생긴 것으로 판단했다.

해결 방법

최대한 일반 적들과의 로직을 동일하게 하고 싶어 StateTree 구조를 바꾸지 않았었다.
이런 상황이 발생할 때마다 조건 분기로 막으려 했지만, 결국 상태 전환 타이밍 사이에 틈이 생겼다.
그래서 해결 방식 자체를 바꿨다.

기존 StateTree수정 후 StateTree

기존엔 Alive 상태에서 Die 태그를 받으면 Death로 전환되는 방식이었다.
하지만 이 구조에서는 Attack 중 Death로 전환이 가능해 문제가 되는 것이었다.

결국 Egg 전용 StateTree를 따로 만들고 Attack과 Death를 완전히 분리하여 충돌을 완전히 막아냈다.
두 State 다 시작하면 끝날 때까지 상태 전환이 없기 때문이다.

결과적으로:

Attack과 Death가 동시에 얽히는 상황 자체를 제거

할 수 있었고, 이후 동일 크래시는 발생하지 않았다.


TribeMaterialManager GC 문제

3웨이브부터 갑자기 적 Material 적용 부분에서 에러가 발생했다.
TribeManagerSubsystem은 기본 Material과 종족 색상을 Key로 사용하고, 둘을 조합한 Material Instance Dynamic를 Value로 가지는 TMap으로 머티리얼을 관리한다.
따라서, 동일한 몬스터/종족 조합이면 기존 MID를 재사용하는 구조였다.

문제는 GC였다.
1~2웨이브 동안 생성된 적들이 모두 제거되자,
TMap의 Value로 저장된 MaterialInstance 역시 GC 대상이 되었다.

하지만 Key 자체는 Map에 그대로 남아 있었다.
그래서 3웨이브에서 같은 조합 등장 시

  • Key 존재
  • 기존 Value 반환

은 정상적으로 수행되는데,

실제로 반환된 Value는 이미 GC에 의해 처리된 쓰레기 포인터상태였다.
결국 제거된 MID를 재사용하려다 크래시가 발생했다.

해결 방법

Material 관련 포인터를 모두 TWeakObjectPtr로 변경했다.
애초에 Manager가 Material의 소유권을 가질 필요는 없었기 때문이다.

그리고 재사용 시 IsValid() 검사를 통해

  • 살아있으면 재사용
  • 제거되었으면 새로 생성

하도록 수정했다.

이후 웨이브가 반복되어도 동일 문제는 발생하지 않았다.

// Manager.h
UCLASS()
class BULLETANT_API UTribeMaterialManagerSubsystem : public UGameInstanceSubsystem
{
private:
    TMap<FTribeMaterialKey, TWeakObjectPtr<UMaterialInstanceDynamic>> TribeMaterialCache;
};

// Manager.cpp
UMaterialInstanceDynamic* UTribeMaterialManagerSubsystem::GetTribeMaterial(UMaterialInterface* InBaseMat, const FLinearColor& InColor)
{
	if (TWeakObjectPtr<UMaterialInstanceDynamic>* FoundPtr = TribeMaterialCache.Find(key))
	{
		if (FoundPtr->IsValid())
		{
			return FoundPtr->Get();
		}
	}

	UMaterialInstanceDynamic* MID = UMaterialInstanceDynamic::Create(InBaseMat, this);
	MID->SetScalarParameterValue(TEXT("Alpha"), Alpha);
	TribeMaterialCache.Add(key, MID);
	return MID;
}
profile
반갑습니다

0개의 댓글