TIL [25.08.30] - Lost In Eden 무기 스왑 오브젝트 풀링

chooha·2025년 8월 30일

TIL

목록 보기
59/60

📝 개발일지 - 무기 시스템 오브젝트 풀링 전환

오늘은 기존 무기 시스템의 Destroy/SpawnActor 방식을 오브젝트 풀링 방식으로 전면 리팩토링했음

기존 방식은 무기 교체 시마다 객체 생성/삭제로 인한 성능 오버헤드와 데이터 동기화의 복잡성 문제가 있었음

새로운 방식은 미리 생성된 무기 풀을 활용한 활성화/비활성화 전환으로 성능을 개선하고, 무기 상태의 지속적 유지를 통해 데이터 관리를 단순화하는 구조로 개선했음


💡 오늘의 5분 기록

1. 기존 무기 시스템의 문제점 분석 🔍

코드 리뷰 중에 무기 교체 시스템의 성능과 구조적 문제점들을 발견했음

기존 코드의 문제점

// ❌ 문제 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); // 데이터 → 무기 동기화

성능 영향 분석

  • 무기 교체 시마다 메모리 할당/해제로 인한 프레임 드롭 가능성
  • 가비지 컬렉션 압박 증가
  • 데이터 동기화를 위한 추가 오버헤드

2. 오브젝트 풀링 전환 전략 수립 💡

핵심 아이디어 "미리 생성 + 활성화 전환 + 단일 데이터 소스"

설계 원칙 설정

성능: 객체 생성/삭제 → 활성화/비활성화
관리: 이중 데이터 → 무기 객체 단일 소스
구조: UObject → UActorComponent

예상 개선사항:

  • 무기 교체 성능: 부드러운 전환
  • 메모리 관리: 안정적인 사용량
  • 코드 복잡도: 데이터 동기화 로직 제거

3. GunManager 구조 재설계 🛠️

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);
    }
}

4. 무기 교체 로직 개선 ⚡

기존 방식: 생성/삭제

// ❌ 비효율적: 매번 새 객체 생성
CurrWeapon->Destroy();
CurrWeapon = GetWorld()->SpawnActor<AGun>(GunClass);

개선된 방식: 활성화/비활성화

// ✅ 효율적: 풀에서 가져와서 활성화
if (CurrWeapon)
{
    CurrWeapon->SetActorHiddenInGame(true);     // 현재 무기 숨김
    CurrWeapon->DetachFromActor(...);
}

CurrWeapon = GunManager->GetWeapon(GunType);   // 풀에서 가져오기
CurrWeapon->SetActorHiddenInGame(false);       // 새 무기 표시
CurrWeapon->AttachToComponent(...);

5. 데이터 관리 단순화 📊

이중 관리 시스템 제거

// 기존: 무기 객체 + 별도 데이터 저장
TMap<EGunType, int32> OwnedGuns;              // 데이터 저장소
void UpdateGunData(AGun* Gun);                // 객체 → 데이터
void SetCurrentGun(AGun* Gun);                // 데이터 → 객체

// 개선: 무기 객체가 모든 상태 보유
TMap<EGunType, AGun*> WeaponPool;             // 객체 풀만 관리
AGun* GetWeapon(EGunType type);               // 직접 객체 반환

상태 유지 개선

  • 탄약 수, 업그레이드, 커스터마이징 등 모든 무기 상태가 객체에 지속적으로 보존
  • 데이터 동기화 과정에서 발생할 수 있는 상태 손실 위험 제거

6. PlayerCharacter 연동 최적화 🎯

BeginPlay 단순화

void APlayerCharacter::BeginPlay()
{
    Super::BeginPlay();
    // 컴포넌트가 자동으로 BeginPlay에서 풀 초기화 수행
    
    EquipWeapon(EGunType::PISTOL);  // 바로 장착 가능
}

7. 무기 획득 시스템 개선

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());
        }
    }
}

8. 최종 성능 개선 결과 📈

달성한 개선사항:

  • 무기 교체 성능: Destroy/SpawnActor 제거로 부드러운 전환
  • 메모리 안정성: 객체 생성/삭제 사이클 제거
  • 상태 관리: 무기별 개별 상태 지속적 유지
  • 코드 단순화: 데이터 동기화 로직 완전 제거

성능 벤치마크:

  • 무기 교체 지연: 프레임 드롭 위험 → 즉시 전환
  • 메모리 사용: 변동적 → 일정한 사용량
  • 코드 복잡도: 높음 (동기화 로직) → 낮음 (직접 접근)

📚 개발 참고

언리얼 엔진 오브젝트 풀링 패턴

핵심 원칙:
1. 게임 시작 시 필요한 객체들을 미리 생성
2. 사용하지 않는 객체는 비활성화 상태로 대기
3. 필요할 때 활성화/비활성화로 재사용
4. 객체 자체에 모든 상태 정보 보존

UActorComponent vs UObject 선택 기준

UActorComponent 사용 시기:
- 특정 액터에 종속된 기능
- 라이프사이클 관리가 중요한 경우
- 블루프린트 접근성이 필요한 경우

UObject 사용 시기:
- 독립적인 데이터 구조체
- 여러 객체에서 공유되는 유틸리티
- 순수한 로직 처리 클래스

0개의 댓글