본문 바로가기

UNITY

UNITY_20231018[팀 과제, Object Pool]

팀 프로젝트 진행 중 일정한 수의 오브젝트를 지속적으로 생성, 소멸해야 하는 기능이 필요했다.

이럴 때 사용하는 로직이 Object Pooling이다. 

 

총에서 발사하는 총알 오브젝트를 예로 든다.

대부분의 상황에서는 총에서 총알이 무한정으로 생성되야 한다. 그럼 어떻게 해야할까?

단순하다. 총구 위치에서 총알을 "생성"하고, 생성된 총알이 자신의 코드에 맞게 직진하다가 특정 물체와 충돌하거나 일정 진행 거리를 초과하면 파괴된다.

볼트액션 저격총, 단발 소총이라면,, 그냥 위 순서대로 만들거도 상관 없을 것이다.

하지만, 분당 3000발의 미니건이라면..? 하다못해 3시간 동안 저격총으로 2000발 소비하는 게임이라면..?
총알-오브젝트의 "생성"과 "파괴"는 유니티 런타임에서는 그냥 생겼다 사라지는 것 처럼 보이지만, 이 과정을 거친 모든 오브젝트는 반드시 메모리에 그 흔적-데이터가 남는다. 수만수천발의 총알 데이터가 메모리에 남는다면? 게임 성능 저하는 뻔한 결과다.

오브젝트의 생성-소멸 대신 다른 방안을 찾아야 한다.

 

그렇게 나온 로직이 Object Pooling, 총알을 한가득 생성해 담아놓은 Pool을 미리 준비하는 것이다.

다시 총알 발사를 예로 들겠다.

우선 총알오브젝트를 300개 "생성"한다. 다음 이 총알들을 전부 "비활성화"하고 어디 한 오브젝트에 다 담아 놓는다. 방아쇠를 당길 때 마다(필요할 때 마다) 총알은 한개씩 총구 위치에서 "활성화"한다.

활성화된 총알은 직진하다가 특정 물체와 충돌하거나 일정 진행 거리를 초과하면 다시 "비활성화"한다.

 

생성-소멸의 반복이 아닌 기존의 오브젝트집단을 활성화-비활성화 반복하는 로직이다. 생성 오브젝트는 제한해야겠지만, 당연히 메모리 부하는 획기적으로 감소한다.

 

아래는 프로젝트에 적용한 오브젝트 풀 스크립트다. 일부 하자가 있다.

더보기
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

public class TargetSpawnManager : Singleton<TargetSpawnManager>
{
    [SerializeField]
    private GameObject targetSpawnPositions;

    public List<Transform> targetPositionGroup;

    [SerializeField]
    private List<GameObject> targetPool = new List<GameObject>();
    [SerializeField]
    private GameObject target;

    private int maxTargets;

    void Start()
    {
        Transform _targetPos = targetSpawnPositions.GetComponentInChildren<Transform>();

        foreach (Transform position in _targetPos)
        {
            targetPositionGroup.Add(position);
            maxTargets++;
        }

        CreateTargetPool();

        FirstGenerateTarget();

        InvokeRepeating(nameof(CreateTarget), 3.0f, 3.0f);
    }

    private void FirstGenerateTarget() // MainScene 시작 시 초기 타겟 표시 활성화 메서드. 모든 타겟 위치에 타겟 활성화
    {
        for (int i = 0; i < maxTargets; i++)
        {
            CreateTarget(i);
        }
    }

    private void CreateTarget(int index)
    {
        var _target = GetTargetPosition();

        _target?.transform.SetPositionAndRotation(targetPositionGroup[index].position, targetPositionGroup[index].rotation);
        _target?.SetActive(true);
    }

    private void CreateTarget() // 타겟 비활성화 이후 재활성화 메서드, 현재 비활성화된 타겟이 재활성화될 때 다른 위치에서 다른 타겟과 겹친 상태에서 활성화되는 버그 발생
    {
        int index = Random.Range(0, targetPositionGroup.Count); // 얘때문

        var _target = GetTargetPosition();

        _target?.transform.SetPositionAndRotation(targetPositionGroup[index].position, targetPositionGroup[index].rotation);
        _target?.SetActive(true);
    }

    private GameObject GetTargetPosition() // 타겟 활성화 여부 체크, 비활성화 시 해당 타겟 반환
    {
        foreach (var _target in targetPool)
        {
            if (_target.activeSelf == false)
            {
                return _target;
            }
        }
        return null;
    }

    private void CreateTargetPool() // 타겟 풀 생성 메서드
    {
        for (int i = 0; i < maxTargets; i++)
        {
            GameObject _target = Instantiate(target);
            _target.name = $"target : {i:00}";
            _target.SetActive(false);
            targetPool.Add(_target);
        }
    }
}