Unity/Portfolio Daily Log

캐주얼 슈팅 액션 대전 게임 (모바일) - 10 오브젝트 풀 개선

Korokke 2022. 7. 26. 13:31

이전에 만들었던 오브젝트 풀은 리스트 한 개를 사용하여 bullet과 hitEffect 모두를 넣어놨다.

만약 나중에 추가적으로 관리해야 할 오브젝트들이 늘어난다면 리스트 안에서 특정 오브젝트를 찾는데 많은 연산을 해야할 지도 모른다.

 

예를 들어 리스트 안에 Bullet이 10개 HitEffect가 10개 StunEffect가 10개 있다고 해보자 필자는 리스트에 이 오브젝트들을 차곡차곡 넣어놨기 때문에 StunEffect를 가져다가 쓰려면 최소 21번은 리스트를 뒤져야 한다.

 

그래서 이 점을 개선하기 위해 오브젝트별로 리스트를 나눠놔야할 필요성을 느꼈다. 물론 지금은 관리해야 될 오브젝트들이 몇개 되지 않아서 이렇게 써도 퍼포먼스 적인 측면에서 큰 타격이 없을 것이다. 하지만 회사에 들어간다거나 혼자 게임을 개발할 경우 이러한 부분까지 신경을 써줘야 하기 때문에 미리 경험을 쌓는 느낌으로 만들어 봤다.

 

첫 번째로 시도한 방법은 List안에 List를 둔 것이다(2차원 리스트). 오브젝트 별로 관리하는 리스트가 있고 모든 리스트를 가지고 있는 리스트가 있는 형식이다. 이렇게 할 경우 이중 for문을 돌려야 하지만 코드가 보기 쉽고 간략화 된다는 장점이 있었다(전체적인 코드 길이가 짧아짐). 확장성 부분에서 두 번째 방법보다 괜찮은 것 같다.

 

 

1. 2차원 리스트

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class MemoryPool : MonoBehaviour
{
    
    public MemoryPool instance { get; private set; }
    public GameObject bullet;
    public GameObject hitEffect;
    public enum ObjectType { Bullet, HitEffect }
    private void Awake()
    {
        InitEnv(bullet, ObjectType.Bullet);
        InitEnv(hitEffect, ObjectType.HitEffect);
    }
    [Serializable]
    public class PoolItem
    {
        public bool isActive;
        public GameObject gameObect;
    }

    private int increaseCount = 5;          //오브젝트가 부족할 때 Instantiate()로 추가 생성되는 오브젝트 개수
    private int maxCount;                   //현재 리스트에 등록되어 있는 오브젝트 개수
    private int activeCount;                //현재 게임에 사용되고 있는 활성화 오브젝트 개수

    private GameObject bulletObject;          //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
    private GameObject hitEffectObject;          //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
    public List<List<PoolItem>> wholePoolItemList;    //관리되는 모든 오브젝트를 저장하는 리스트
    public List<PoolItem> bulletList;
    public List<PoolItem> hitEffectList;
    public MemoryPool() { }
    
    public void InitEnv(GameObject poolObject, ObjectType objectType)
    {
        maxCount = 0;
        activeCount = 0;
        switch (objectType)
        {
            case ObjectType.Bullet:     this.bulletObject = poolObject;         break;
            case ObjectType.HitEffect:  this.hitEffectObject = poolObject;      break;
            default: break;
        }

        if(wholePoolItemList == null)
        {
            wholePoolItemList = new List<List<PoolItem>>();
            wholePoolItemList.Add(bulletList);
            wholePoolItemList.Add(hitEffectList);
        }
            

        InstantiateObjects(objectType);
    }
    // increraseCount 단위로 오브젝트를 생성
    public void InstantiateObjects(ObjectType objectType)
    {
        maxCount += increaseCount;

        for( int i =0; i < increaseCount; i++ )
        {
            PoolItem poolItem = new PoolItem();

            poolItem.isActive = false;
            switch (objectType)
            {
                case ObjectType.Bullet:     poolItem.gameObect = GameObject.Instantiate(bulletObject);      break;
                case ObjectType.HitEffect:  poolItem.gameObect = GameObject.Instantiate(hitEffectObject);   break;
                default: break;
            }
            
            poolItem.gameObect.transform.parent = this.transform;
            poolItem.gameObect.SetActive(false);

            switch (objectType)
            {
                case ObjectType.Bullet:     wholePoolItemList[0].Add(poolItem);  break;
                case ObjectType.HitEffect:  wholePoolItemList[1].Add(poolItem);  break;
                default: break;
            }
        }
    }

    // 현재 관리중인 (활성 / 비활성) 모든 오브젝트를 삭제
    public void DestroyObjects()
    {
        if (wholePoolItemList == null) 
            return;

        int wCount = wholePoolItemList.Count;
        int bCount = bulletList.Count;
        int hCount = hitEffectList.Count;
        for(int i = 0; i < wCount; i++)
        {
            for (int j = 0; j < bCount; j++)
            {
                GameObject.Destroy(wholePoolItemList[i][j].gameObect);
            }
            for (int k = 0; k < hCount; k++)
            {
                GameObject.Destroy(wholePoolItemList[i][k].gameObect);
            }
        }
        wholePoolItemList.Clear();
    }

    // poolItemList에 저장되어 있는 오브젝트를 활성화 해서 사용
    // 현재 모든 오브젝트가 사용중이면 InstantiateObjects()로 추가 생성
    public GameObject ActivePoolItem(ObjectType objectType)
    {
        if (wholePoolItemList == null) 
            return null;

        if (activeCount == maxCount)
            InstantiateObjects(objectType);

        int wCount = wholePoolItemList.Count;
        int whichListCount = 0;

        if (objectType == ObjectType.Bullet) { wCount = 0; whichListCount = bulletList.Count; }

        else if (objectType == ObjectType.HitEffect) { wCount = 1; whichListCount = hitEffectList.Count; }
        
        for (int i = 0; i < whichListCount; i++)
        {
            PoolItem poolItem = wholePoolItemList[wCount][i];
            if(poolItem.isActive == false)
            {
                activeCount++;

                poolItem.isActive = true;
                poolItem.gameObect.SetActive(true);

                return poolItem.gameObect;
            } 
        }
        return null;
    }

    // 현재 사용이 완료된 오브젝트를 비활성화 상태로 설정
    public void DeactivatePoolItem(GameObject usedObject)
    {
        if (wholePoolItemList == null || usedObject == null)
            return;

        int wCount = wholePoolItemList.Count;
        int whichListCount = 0;

        if (usedObject.CompareTag("Bullet")) { wCount = 0; whichListCount = bulletList.Count; }

        else if (usedObject.CompareTag("HitEffect")) { wCount = 1; whichListCount = hitEffectList.Count; }
            

        for (int i = 0; i < whichListCount; i++)
        {
            PoolItem poolItem = wholePoolItemList[wCount][i];

            if(poolItem.gameObect == usedObject)
            {
                activeCount--;

                poolItem.isActive = false;
                poolItem.gameObect.SetActive(false);

                return;
            }
        }
    }

    // 게임에서 사용중인 모든 오브젝트를 비활성화 상태로 설정
    public void DeactiveAllPoolItems()
    {
        if (wholePoolItemList == null)
            return;

        int wCount = wholePoolItemList.Count;
        int bCount = bulletList.Count;
        int hCount = hitEffectList.Count;
        for (int i = 0; i < wCount; i++)
        {
            for (int j = 0; j < bCount; j++)
            {
                PoolItem poolItem = wholePoolItemList[i][j];
                if (poolItem.gameObect != null && poolItem.isActive == true)
                {
                    poolItem.isActive = false;
                    poolItem.gameObect.SetActive(false);
                }
            }
            for (int k = 0; k < hCount; k++)
            {
                PoolItem poolItem = wholePoolItemList[i][k];
                if (poolItem.gameObect != null && poolItem.isActive == true)
                {
                    poolItem.isActive = false;
                    poolItem.gameObect.SetActive(false);
                }
            }   
        }
        activeCount = 0;
    }
}

 

 

 

 

두 번째로 시도한 방법은 전체적으로 관리하는 리스트를 빼고 각 오브젝트의 리스트만 남겨 두는 것이었다.

난이도는 이쪽이 쉽긴 한데 확장성을 봤을 때는 조금 떨어지는 것 같다. 코드의 중복성이 있다.

 

2. 1차원 리스트

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class MemoryPool : MonoBehaviour
{
    
    public MemoryPool instance { get; private set; }
    public GameObject bullet;
    public GameObject hitEffect;
    public enum ObjectType { Bullet, HitEffect }
    private void Awake()
    {
        InitEnv(bullet, ObjectType.Bullet);
        InitEnv(hitEffect, ObjectType.HitEffect);
    }
    [Serializable]
    public class PoolItem
    {
        public bool isActive;
        public GameObject gameObect;
    }

    private int increaseCount = 5;          //오브젝트가 부족할 때 Instantiate()로 추가 생성되는 오브젝트 개수
    private int maxCount;                   //현재 리스트에 등록되어 있는 오브젝트 개수
    private int activeCount;                //현재 게임에 사용되고 있는 활성화 오브젝트 개수

    private GameObject bulletObject;          //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
    private GameObject hitEffectObject;          //오브젝트 풀링에서 관리하는 게임 오브젝트 프리팹 // 불릿
    
    public List<PoolItem> bulletList;
    public List<PoolItem> hitEffectList;
    public MemoryPool() { }
    public MemoryPool(GameObject poolObject)
    {
        maxCount = 0;
        activeCount = 0;
        this.bulletObject = poolObject;

        //wholePoolItemList = new List<List<PoolItem>>();

        //InstantiateObjects(); 
    }
    public void InitEnv(GameObject poolObject, ObjectType objectType)
    {
        maxCount = 0;
        activeCount = 0;
        switch (objectType)
        {
            case ObjectType.Bullet:     this.bulletObject = poolObject;         break;
            case ObjectType.HitEffect:  this.hitEffectObject = poolObject;      break;
            default: break;
        }

        if(bulletList == null)
            bulletList = new List<PoolItem>();

        if (hitEffectList == null)
            hitEffectList = new List<PoolItem>();



        InstantiateObjects(objectType);
    }
    // increraseCount 단위로 오브젝트를 생성
    public void InstantiateObjects(ObjectType objectType)
    {
        maxCount += increaseCount;

        for( int i =0; i < increaseCount; i++ )
        {
            PoolItem poolItem = new PoolItem();

            poolItem.isActive = false;
            switch (objectType)
            {
                case ObjectType.Bullet:     poolItem.gameObect = GameObject.Instantiate(bulletObject);      break;
                case ObjectType.HitEffect:  poolItem.gameObect = GameObject.Instantiate(hitEffectObject);   break;
                default: break;
            }
            
            poolItem.gameObect.transform.parent = this.transform;
            poolItem.gameObect.SetActive(false);

            switch (objectType)
            {
                case ObjectType.Bullet:         bulletList.Add(poolItem);  break;
                case ObjectType.HitEffect:      hitEffectList.Add(poolItem);  break;
                default: break;
            }
        }
    }

    // 현재 관리중인 (활성 / 비활성) 모든 오브젝트를 삭제
    public void DestroyObjects()
    {
        if (bulletList == null && hitEffectList == null) 
            return;

        
        int bCount = bulletList.Count;
        int hCount = hitEffectList.Count;

        for (int i = 0; i < bCount; i++)
        {
            GameObject.Destroy(bulletList[i].gameObect);
        }
        for (int i = 0; i < hCount; i++)
        {
            GameObject.Destroy(hitEffectList[i].gameObect);
        }

        bulletList.Clear();
        hitEffectList.Clear();
    }

    // poolItemList에 저장되어 있는 오브젝트를 활성화 해서 사용
    // 현재 모든 오브젝트가 사용중이면 InstantiateObjects()로 추가 생성
    public GameObject ActivePoolItem(ObjectType objectType)
    {
        if (bulletList == null && hitEffectList == null) 
            return null;

        if (activeCount == maxCount)
            InstantiateObjects(objectType);
        
        int whichListCount = 0;

        if (objectType == ObjectType.Bullet)
        {
            whichListCount = bulletList.Count;
            for (int i = 0; i < whichListCount; i++)
            {
                PoolItem poolItem = bulletList[i];
                if (poolItem.isActive == false)
                {
                    activeCount++;

                    poolItem.isActive = true;
                    poolItem.gameObect.SetActive(true);

                    return poolItem.gameObect;
                }
            }
        }

        else if (objectType == ObjectType.HitEffect) 
        { 
            whichListCount = hitEffectList.Count;
            for (int i = 0; i < whichListCount; i++)
            {
                PoolItem poolItem = hitEffectList[i];
                if (poolItem.isActive == false)
                {
                    activeCount++;

                    poolItem.isActive = true;
                    poolItem.gameObect.SetActive(true);

                    return poolItem.gameObect;
                }
            }
        }
        
        
        return null;
    }

    // 현재 사용이 완료된 오브젝트를 비활성화 상태로 설정
    public void DeactivatePoolItem(GameObject usedObject)
    {
        if ((bulletList == null && hitEffectList == null) || usedObject == null)
            return;

        int whichListCount = 0;
        if (usedObject.CompareTag("Bullet")) 
        {
            whichListCount = bulletList.Count;
            for (int i = 0; i < whichListCount; i++)
            {
                PoolItem poolItem = bulletList[i];

                if (poolItem.gameObect == usedObject)
                {
                    activeCount--;

                    poolItem.isActive = false;
                    poolItem.gameObect.SetActive(false);

                    return;
                }
            }
        }

        else if (usedObject.CompareTag("HitEffect")) 
        { 
            whichListCount = hitEffectList.Count;
            for (int i = 0; i < whichListCount; i++)
            {
                PoolItem poolItem = hitEffectList[i];

                if (poolItem.gameObect == usedObject)
                {
                    activeCount--;

                    poolItem.isActive = false;
                    poolItem.gameObect.SetActive(false);

                    return;
                }
            }
        }
            

       
    }

    // 게임에서 사용중인 모든 오브젝트를 비활성화 상태로 설정
    public void DeactiveAllPoolItems()
    {
        if (bulletList == null && hitEffectList == null)
            return;

        int bCount = bulletList.Count;
        int hCount = hitEffectList.Count;

        for (int i = 0; i < bCount; i++)
        {
            PoolItem poolItem = bulletList[bCount];
            if (poolItem.gameObect != null && poolItem.isActive == true)
            {
                poolItem.isActive = false;
                poolItem.gameObect.SetActive(false);
            }
        }
        for (int k = 0; k < hCount; k++)
        {
            PoolItem poolItem = hitEffectList[hCount];
            if (poolItem.gameObect != null && poolItem.isActive == true)
            {
                poolItem.isActive = false;
                poolItem.gameObect.SetActive(false);
            }
        }

        activeCount = 0;
    }
}

느낀점: 우선 재미있었다. 빵을 먹으려다가 배달음식을 시킬 때 피자와 치킨 사이에서 고민하는 느낌이었다.

인터넷 검색으로 알게 된 사실인데 시간 복잡도는 첫 번째가 n2으로 더 안 좋다는 걸 알 수 있었다(이중 for문). 하지만 확장성을 봤을 때는 첫 번째가 더 좋기 때문에 아직 추가하지 못한 이펙트들이 있으니 첫 번째 방법으로 채택하겠다.