게임 개발 공부 기록

55일차 - Abyss_Slayer 최종 팀 프로젝트 5 (투사체 오브젝트 풀링 1)

00lwt 2025. 4. 10. 21:45

▶▷ 오브젝트 풀링

오브젝트 풀링이란 간단히 설명하자면 공간을 미리 생성해 두고 그 안에서 오브젝트를 생성 및 반환을 하는 기법으로
자주 생성되는 오브젝트를 효율적으로 다루기 위해 사용한다.

지금 내가 구현하는 부분에서는 플레이어 스킬의 화살을 예로 들 수 있다.
화살은 게임 내내 생성되었다 파괴되었다 해야하는데 이걸 매번 Instantiate 하고 Destroy하면 성능에 영향을 미칠 수 있다.
그렇기 때문에 최적화를 위해서라면 정말 중요한 기법이다.

팀원이 먼저 싱글톤으로 제작한 PoolManager, 반환과 초기화를 담당하는 추상 클래스인 BasePoolable,
그리고 이 추상 클래스를 상속 받는 ObjectPool을 통해 기존에 화살을 단순히 생성 및 파괴를 했던 구조에서
오브젝트 풀링을 사용하는 방식으로 변경하기 위한 작업을 하였다.

ArcherSkill_x.cs

[CreateAssetMenu(menuName = "Skill/Archer/Archer_x")]
public class ArcherSkill_x : SkillExecuter
{
    public ArrowProjectile arrowPrefab;        // 발사할 화살 프리팹
    public float arrowSpeed;                   // 화살 속도

    /// <summary>
    /// 아처의 평타 로직을 담당하는 메소드
    /// </summary>
    /// <param name="user">스킬 시전자</param>
    /// <param name="target">타겟팅 정보</param>
    /// <param name="skillData">스킬의 공통 데이터</param>
    public override void Execute(Player user, Player target, SkillData skillData)
    {
        //...

        // 오브젝트 풀에서 화살 가져오기
        var arrow = PoolManager.Instance.Get<ArrowProjectile>();
        arrow.Init(spawnPos, dir, skillData.targetingData.range, arrowSpeed);
    }
}

ArrowProjectile.cs

public class ArrowProjectile : BasePoolable
{
    [SerializeField] private int damage = 50;           // 화살 데미지
    [SerializeField] private float arrowSpeed = 60f;    // 화살 속도
    private float maxRange;
    private Vector3 direction;
    private Vector3 initPos;

    private void Update()
    {
        // 최대 거리 도달 시 풀에 반환
        if (Vector3.Distance(initPos, transform.position) >= maxRange)
        {
            ReturnToPool();
        }

        // 화살 이동
        transform.Translate(direction * arrowSpeed * Time.deltaTime);
    }

    public override void Init()
    {
        // 호출용
    }

    public void Init(Vector3 spawnPos, Vector3 dir, float range, float speed)
    {
        transform.position = spawnPos;
        initPos = spawnPos;
        direction = dir.normalized;
        maxRange = range;
        arrowSpeed = speed;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.TryGetComponent<Boss>(out Boss boss))
        {
            //boss.TakeDamage(damage); // 데미지 전달
        }

        ReturnToPool(); // 투사체 제거
    }
}

BasePoolable.cs

public abstract class BasePoolable : MonoBehaviour
{
    protected ObjectPool<BasePoolable> _pool;

    //풀 설정
    public virtual void SetPool(ObjectPool<BasePoolable> pool)
    {
        _pool = pool;
    }

    //반드시 오버로딩하여 사용
    public abstract void Init();

    public virtual void ReturnToPool()
    {
        gameObject.SetActive(false);
        _pool.ReturnToPool(this);
    }
}

현재 시도 중인 방법은 PoolManager에서 투사체를 가져온 다음 Init()에 생성 위치, 방향, 타겟팅 데이터, 스피드 등의 매개변수를 전달하고 ArrowProjectile에서 초기화하고 생성 및 반환을 하는 방법이다.

 

에디터 상에서는 PoolManager 오브젝트에 플레이어 전용 풀을 만든 다음 PoolManager에서 생성해둔 리스트에 추가하면 된다.

이렇게 해서 사용하려고 해보는 중인데 발사할 때마다 null 오류가 뜨는 중인데 아직 오브젝트 풀링에 대한 지식 부족 이슈로 해결하지 못하는 중이라 내일 글에서 좀 더 자세히 다룰 예정이다.