[DevLog] Game Engine 개발일지 #8
1. 날짜 (Date)
2025/07/16
2. 작업 목표 (Daily Goals)
- TeleportAbility 기능 구현(TeleportIndicator 파기)
- Enemy Ability 구조 생각하기
3. 진행 사항 (Progress)
- TeleportIndicatorDecal이 업데이트(새로 생성 및 Enemy 죽었을 때 소멸)가 안되는 버그 해결 중
4. 문제점 및 해결 방법 (Challenges & Solutions)
template<typename T>
T* GiveEnemyAbility()
{
FGameplayAbilitySpecHandle Handle = mASC->GiveAbility(FGameplayAbilitySpec(T::StaticClass(), 1, 0));
bool IsInstanced = mASC->TryActivateAbility(Handle);
if (!IsInstanced)
{
UE_LOG(LogTemp, Warning, TEXT("[0716] TryActivateAbility() in GiveEnemyAbility() returned nullptr"));
return nullptr;
}
if (Spec && Spec->Ability && Spec->Ability->IsInstantiated())
{
T* AbilityInstance = Cast<T>(Spec->GetPrimaryInstance());
if (!AbilityInstance)
{
UE_LOG(LogTemp, Warning, TEXT("[0716] GetPrimaryInstance() in GiveEnemyAbility() returned nullptr"));
return nullptr;
}
AbilityInstance->PostActivateInit(mCooldowns);
mAbilities.Add(AbilityInstance);
UE_LOG(LogTemp, Warning, TEXT("[0716] GiveEnemyAbility Succeeded"));
return AbilityInstance;
}
UE_LOG(LogTemp, Warning, TEXT("[0716] GiveEnemyAbility() returned nullptr"));
return nullptr;
}
위의 코드를 실행했을 때, Spec->Ability->IsInstantiated()
에서 false가 되어 인스턴스를 받아오는 데 실패했다. 혹시 생성은 다음 프레임에 되거나, 너무 이른가 싶어 타이머를 통해 잠시 후에 조건을 확인해봤더니 이번에는 인스턴스를 잘 받아올 수 있었다.
FTimerHandle TempHandle;
GetWorld()->GetTimerManager().SetTimer(TempHandle, [this, Handle]()
{
FGameplayAbilitySpec* Spec = mASC->FindAbilitySpecFromHandle(Handle);
if (Spec && Spec->Ability && Spec->Ability->IsInstantiated())
{
T* AbilityInstance = Cast<T>(Spec->GetPrimaryInstance());
if (AbilityInstance)
{
UE_LOG(LogTemp, Warning, TEXT("[0716] Instance Get~!"));
AbilityInstance->PostActivateInit(mCooldowns);
mAbilities.Add(AbilityInstance);
}
}
}, 0.01f, false);
- if-else 구문 {}로 안묶으면 컴파일 에러 발생
5. 다음 단계 (Next Steps)
- TeleportIndicatorDecal이 업데이트(새로 생성 및 Enemy 죽었을 때 소멸)가 안되는 버그 해결
6. 회고 (Reflection)
- 현재 처한 문제 상황
- Enemy는 살아 있는 동안 반복해서 스킬을 사용하므로, Enemy와 Ability의 생명 주기를 맞춰줘야 한다.
- 스킬을 반복적으로 사용하기 위해 Enemy 또는 Ability에서 Timer를 등록해야 한다.
Ability::EndAbility()
를 Enemy의 소멸에 맞춰서 호출해야 한다.- InstancingPolicy =
InstancedPerActor
- 방안
- Enemy에서 Timer를 가지고 있고, 일정 시간마다 Ability 활성화 및 해제하기
- 장점
- Ability 객체를 알 필요 없이 Timer만 다루면 됨
- 단점
- 빈번한 Ability 생성 및 소멸(GC 비용 증가)
- 빈번한 Timer 등록 및 해제
- Ability의 단일 책임 위배 및 확장성 저해
- TeleportAbility의 경우 텔레포트 위치 표시 Timer외에, 실제 텔레포트 용 Timer가 내부에 추가로 필요함. 이 경우 Enemy와 TeleportAbility 2개의 Timer를 따로 관리해야 함.
- Enemy에서 Ability를 생성, 해제하는 함수를 wrapping해서 TArray<Ability*>에 인스턴스 저장 및 EnemyAbility::Init(), Release() 호출하기
- 장점
- Ability의 단일 책임 및 확장성 보장
- Enemy 객체 하나 당 한번의 Ability Instancing
- 단점
- Ability Instance를 찾는 오버헤드 발생
- 장점
-
만약 Enemy 객체 생성 -> TryActivateAbility() 호출했지만 Ability Instance 생성되지 않은 상황 -> Enemy가 한방에 죽어서 Release가 호출된다면?
- TeleportIndicatorDecal이 표시된 상황에서, Ability 소유한 객체가 죽었을 때 여전히 TeleportIndicatorDecal가 표시되고 있는 문제 발생 -> Timer 2개를 사용해서 ShowTeleportIndicator() 함수와 ExecuteTeleport() 함수를 각각의 타이머에 등록했다. ShowTeleportIndicator() 함수를 등록하고 5초 후에 ExecuteTeleport() 함수를 등록했는데, 각 타이머의 interval도 5초였기 때문에 이런 문제가 발생했다. ExecuteTeleport() 함수가 호출되는 타이밍을 약간 더 앞으로 당겨도, 렉이 걸려서 Timer의 업데이트가 밀리게 되면 비슷한 상황이 발생할 수 있다. Timer에 함수 하나만 등록하고, 그 안에서 분기 처리하자.
7. 메모 (Notes)
- 캐시 정리 로컬 디스크 -> 사용자 -> (내 계정) -> AppData -> Local -> Temp 이 곳에 있는 내용 정리했더니 200GB 공간 확보함
전달 받은 기획
- 메인 UI에서 플레이어 테스트 레벨로 가는 기능 구현
-
보스 등장 타이머, ex. 30초 -> 0초 점점 줄어들게, 초 줄어들 때마다 심장박동처럼 커졌다 줄어들었다, 시간 점점 다가올 때마다 폰트 크게,
Leave a comment