오늘은 기존 무기 시스템의 Destroy/SpawnActor 방식을 오브젝트 풀링 방식으로 전면 리팩토링했음
기존 방식은 무기 교체 시마다 객체 생성/삭제로 인한 성능 오버헤드와 데이터 동기화의 복잡성 문제가 있었음
새로운 방식은 미리 생성된 무기 풀을 활용한 활성화/비활성화 전환으로 성능을 개선하고, 무기 상태의 지속적 유지를 통해 데이터 관리를 단순화하는 구조로 개선했음
코드 리뷰 중에 무기 교체 시스템의 성능과 구조적 문제점들을 발견했음
기존 코드의 문제점
// ❌ 문제 1: 매번 객체 생성/삭제
void APlayerCharacter::EquipWeapon(EGunType GunType)
{
if (CurrWeapon)
{
GunManager->UpdateGunData(CurrWeapon); // 데이터 백업
CurrWeapon->Destroy(); // 객체 삭제
}
CurrWeapon = GetWorld()->SpawnActor<AGun>(GunClass); // 새 객체 생성
GunManager->SetCurrentGun(CurrWeapon); // 데이터 복원
}
// ❌ 문제 2: 이중 데이터 관리
TMap<EGunType, int32> OwnedGuns; // 별도 데이터 저장
void UpdateGunData(AGun* CurrGun); // 무기 → 데이터 동기화
void SetCurrentGun(AGun* NextGun); // 데이터 → 무기 동기화
성능 영향 분석
핵심 아이디어 "미리 생성 + 활성화 전환 + 단일 데이터 소스"
설계 원칙 설정
성능: 객체 생성/삭제 → 활성화/비활성화
관리: 이중 데이터 → 무기 객체 단일 소스
구조: UObject → UActorComponent
예상 개선사항:
1단계: 헤더 파일 개선
// 기존: 데이터만 관리
class UGunManager : public UActorComponent
{
TMap<EGunType, int32> OwnedGuns; // 데이터만 저장
};
// 개선: 객체 풀 관리
class UGunManager : public UActorComponent
{
TMap<EGunType, AGun*> WeaponPool; // 실제 무기 객체 풀
TSet<EGunType> OwnedWeapons; // 소유 무기 관리
};
2단계: 무기 풀 초기화 시스템
void UGunManager::BeginPlay()
{
// 모든 무기 타입 미리 생성
for (auto& WeaponPair : WeaponClasses)
{
AGun* WeaponInstance = World->SpawnActor<AGun>(GunClass);
WeaponInstance->SetActorHiddenInGame(true); // 비활성화
WeaponPool.Add(GunType, WeaponInstance);
}
}
기존 방식: 생성/삭제
// ❌ 비효율적: 매번 새 객체 생성
CurrWeapon->Destroy();
CurrWeapon = GetWorld()->SpawnActor<AGun>(GunClass);
개선된 방식: 활성화/비활성화
// ✅ 효율적: 풀에서 가져와서 활성화
if (CurrWeapon)
{
CurrWeapon->SetActorHiddenInGame(true); // 현재 무기 숨김
CurrWeapon->DetachFromActor(...);
}
CurrWeapon = GunManager->GetWeapon(GunType); // 풀에서 가져오기
CurrWeapon->SetActorHiddenInGame(false); // 새 무기 표시
CurrWeapon->AttachToComponent(...);
이중 관리 시스템 제거
// 기존: 무기 객체 + 별도 데이터 저장
TMap<EGunType, int32> OwnedGuns; // 데이터 저장소
void UpdateGunData(AGun* Gun); // 객체 → 데이터
void SetCurrentGun(AGun* Gun); // 데이터 → 객체
// 개선: 무기 객체가 모든 상태 보유
TMap<EGunType, AGun*> WeaponPool; // 객체 풀만 관리
AGun* GetWeapon(EGunType type); // 직접 객체 반환
상태 유지 개선
BeginPlay 단순화
void APlayerCharacter::BeginPlay()
{
Super::BeginPlay();
// 컴포넌트가 자동으로 BeginPlay에서 풀 초기화 수행
EquipWeapon(EGunType::PISTOL); // 바로 장착 가능
}
AddItem 함수 최적화
void APlayerCharacter::AddItem(AItem* Item)
{
AGun* Gun = Cast<AGun>(Item);
if (Gun)
{
EGunType GunType = Gun->GetGunType();
// 중복 확인 후 획득
if (!GunManager->HasWeapon(GunType))
{
GunManager->AcquireWeapon(GunType);
// 픽업한 무기 상태를 풀 무기에 복사
AGun* PoolWeapon = GunManager->GetWeapon(GunType);
PoolWeapon->SetCurrentAmmo(Gun->GetCurrentAmmo());
}
}
}
달성한 개선사항:
성능 벤치마크:
핵심 원칙:
1. 게임 시작 시 필요한 객체들을 미리 생성
2. 사용하지 않는 객체는 비활성화 상태로 대기
3. 필요할 때 활성화/비활성화로 재사용
4. 객체 자체에 모든 상태 정보 보존
UActorComponent 사용 시기:
- 특정 액터에 종속된 기능
- 라이프사이클 관리가 중요한 경우
- 블루프린트 접근성이 필요한 경우
UObject 사용 시기:
- 독립적인 데이터 구조체
- 여러 객체에서 공유되는 유틸리티
- 순수한 로직 처리 클래스