Ad Block 한번만 꺼주시면 안될까요..?
게임 개발/게임 개발 이론

유니티 최적화 방법 - 오브젝트 풀링(Object Pooling)

UniCoti(유니코티) 2023. 1. 1.

게임을 만들 때는 최적화가 거의 필수적으로 들어간다.

특히 모바일 게임에 점유율이 훨씬 높은 엔진

유니티의 경우, 퀄리티 높은 모바일 게임이나

 

뱀파이어 서바이벌과 탕탕 특공대 같이 여러 적이 나오는 게임이라면

최적화가 필요하게 된다.

많은 적이 나오는 게임 뱀파이어 서바이벌 플레이 중 화면

대부분 이런 상황을 만들자고 하면,

먼저 Instantiate로 적을 복제하고,

플레이어가 적을 죽인다면

Destory()로 세상에서 없애버리는 로직을 상상한다.


하지만 이 로직은 생각보다 문제가 많다.

실행은 되지만 기본적으로 Destroy()를 사용하면

그 오브젝트의 메모리가 쌓이게 되는데,

 

조금의 메모리는 괜찮겠지만

위의 사진처럼 미친 듯이 많은 적이 나오게 될 경우,

조금의 메모리가 쌓이고, 그걸 청소하기 위해

가비지 컬렉터 즉, GC가 실행되어 렉이 유발된다.


그러한 이유로 이 로직은 사용불가능 하고,

그걸 대체하기 위해서 나온 게 오브젝트 풀링이다.

오브젝트 풀링을 직역하면 알다시피,

오브젝트를 관리하는 일종의 수영장(pool)을 만들어놓는 거다.

 

수영장에 몇백 개의 오브젝트를 던져두고,

필요할 때 수영장에서 건져와서 쓴 후,

일이 끝나면(죽으면) 다시 수영장에 던져놓는 것이다.

그림으로 표현해보자면 이러한 모습이다.

 

이 방법은 메모리를 남기지 않고, 다시 재활용하기에

제한도 없으며 시작부터 수영장(pool)에 오브젝트들을 만들어 둘 것이기에

발생할 수 있는 렉을 로딩 시간으로 커버칠 수 있다.

 

하는 방법은, 먼저 위의 사진의 pool과 같은 역할인 배열을 하나 만들어준다.

필자는 평소에 리스트가 더 쓰기 편하다고 생각해서

리스트로 제작했다.

 

    public GameObject parent; //배열에 들어가는 오브젝트 묶기용 부모오브젝트
    public GameObject bullet; //총알(복제본)
    public List<GameObject> objects = new List<GameObject>(); //리스트 생성
    
    void Start() //시작했을때
    {
        for(int i = 0;i < objects.Count;i++) { //배열의 개수만큼 코드실행
            objects[i] = Instantiate(bullet , parent.transform); //배열의 i번째 = 총알오브젝트, 부모는 parent.
            objects[i].SetActive(false); //모습 숨기기 (모두 숨겨놓고 필요할때 켜서 사용)
        }
    }

이렇게 해서 배열을 만들었다.

이해를 위한 사진 (직접 만듦)

사진을 기준으로 보면 수영장(pool)을 만든 것이라고 볼 수 있다.

이제 저 화살표(로직)을 짜야한다.

필자는 총알을 쏴야 하는 게임을 만들었어서

총알을 쏘는 걸 기준으로 보여주겠다.

int objectint = 0; //배열이 몇번째인지 세는 변수

IEnumerator Shoot() { //쏘기 함수
        yield return new WaitForSeconds(0.1f); //0.1초 기다리기
		if(objectint == opm.objects.Count - 1) { //현재 배열과 배열의 크기가 같다면(마지막 오브젝트라면)
            objectint = 0; //0번째로 초기화(이렇게 해서 반복됨)
        } else { //아니라면
            objectint++; //그냥 1증가 (세기)
        }
        opm.objects[objectint].SetActive(true); //총알 모습 보이게 하기
        opm.objects[objectint].GetComponent<BulletScript>().LetStart(); //총알의 시작함수 실행
        opm.objects[objectint].transform.position = transform.position + new Vector3(0.45f,0,0); //총알 위치 조정
        
        StartCoroutine("Shoot");//쏘기함수 실행(재귀함수) 반복한다는 의미임.
    }

이렇게 로직을 만들 수 있다.

총알의 시작함수도 보여주자면,

 public void LetStart() { //시작함수
        this.gameObject.SetActive(true); //모습 보이게
        this.transform.rotation = Quaternion.Euler(0,0,0); //각도 고정
        rigid.AddForce(Vector2.up * force * Time.fixedDeltaTime); //쏘기
        StartCoroutine("CloneDestroy"); //0,5초 뒤에 모습 숨기기
    }

이렇게 짜서 다시 숨기게 해 놨다.

이렇게 해서 필요할 때 꺼내고,

죽으면(여기서는 총알이 어디에 닿으면) 다시 숨기고,

이렇게 재활용의 구조로 완성할 수 있게 된다.


이상으로 그 유명한 오브젝트 풀링에 대해

알아보았으며, 이론만 알고 코드는 직접 짰는데 작동해서 기쁘다.

 

또한 오브젝트 풀링 말고도 여러 최적화 기법들도 있으니 참고하길 바란다.

참고로 필자의 블로그의 최적화 기법은

아직 이 글과 가비지 컬렉터 밖에 없다.

앞으로 차츰 올려보겠다.

 

https://alpaca-code.tistory.com/89(가비지컬렉터)

 

유니티 가비지 컬렉터(GC)

가비지 컬렉터는 개발자의 면접 질문으로도 많이 나오는 질문이며, 코딩을 오래했다면 어디선가는 한번쯤 들어봤을만한 말이다. 유니티 에서의 가비지 컬렉터는 더이상 필요없어진 오브젝트같

alpaca-code.tistory.com


도움이 되었길 바라며,

 

끝.

댓글

💲 추천 글