설모의 기록

[Cocos creator] Object pool 이란? 본문

게임엔진/Cocos creator

[Cocos creator] Object pool 이란?

HA_Kwon 2018. 1. 27. 01:53

Object pool? 객체 풀이 뭐야?

  대뜸 배틀그라운드 사진을 첨부해서 놀라셨죠? 이번 포스팅은 게임을 개발할 때 성능을 좌지우지하는 object pool 을 설명하기 위해 배틀그라운드 사진을 첨부했습니다. 위와 같은 게임에서는 총수들이 총을 쏘며 캐릭터들을 죽이는 장면을 쉽게 볼 수 있습니다. 그런데 만약, 제가 총 쏘기를 1000번을 수행했다면 총알을 1000을 만들어야 할까요? '에이~ 요즘 컴퓨터들이 얼마나 빠른데 1000개 만드는거까지 성능 최적화를 해야하나요?' 라고 말씀하실 수 있습니다. 그러나 유저들에게 제공되어야하는 게임의 속도는 어색하지 않고 끊김이 없으며 부드러워야 합니다. 총알 객체를 new 하고 이미지를 입히고 애니메이션 이미지들을 로드해 애니메이션을 적용해 화면을 붙이는 작업을 1000번 한다면, 그만큼 다른 작업을 하지 못하고 점점 느려지게 되는 것이죠. 어차피 총알같은 객체들은 화면에 1초도 되지 않은 시간동안 보여진 뒤 삭제됩니다. 그렇기 때문에 new 하고 remove 하는 작업들을 반복하지 말고 화면에서 삭제할 총알들을 다시 재 사용해 객체를 생성하는 작업을 최소화하는 것입니다. 이러한 방법이 바로 object pool 입니다.

  다시 말해, 만약 총을 1000번 발사한다면, 총알은 3개 (가정입니다. 속도에 따라 더 만드셔도 돼요!) 정도 만든 후 1번 총알이 화면에서 사라질 때쯤 4번 총알로 다시 재 사용해 단 3개의 총알로 1000번 발사하는 장면을 구현하는 것이 object pool 을 사용하는 방법입니다. 이해가 되시나요?



Cocos Creator 에서의 Object Pool 사용법

  각 게임 엔진들마다 Object pool 을 사용하는 방법은 조금씩 다르지만 개념은 모두 같으니 아래의 예시를 보고 이해를 한 후 각 게임엔진에 맡는 코드로 구현해보시면 좋을 것 같습니다!

  Cocos creator 에서는 pool 기능을 제공합니다. 먼저 총알 풀을 만듭니다.

var bulletPool = new cc.NodePool("Bullet");

  위의 코드를 실행하면 풀이 생성됩니다. 풀 이름은  원하시는대로 설정하시면 됩니다. 이제 총알을 생성할 때 이 풀이 비어있으면 new 를 통해 새 객체를 생성하고, 비어있지 않으면 풀에 넣어둔 객체를 꺼내와 다시 재 사용합니다. 

   아래의 코드는 if-else 분기를 통해 총알 객체를 생성하거나 재 사용합니다.

var bullet;
if (bulletPool.size() > 0) { bullet = bulletPool.get({x: 10, y: 40}); }
else { bullet = cc.instantiate(this.bulletPrefab); }

this.node.addChild(bullet);

  위의 코드와 같이 풀의 사이즈가 0보다 크면 객체가 존재한다는 것이기 때문에 풀에서 객체를 가져옵니다. 하지만 풀이 비어있다면 총알 프리펩을 생성하는 것이죠. 그 후 bullet 을 스크립트 컴포넌트를 가진 노드의 자식으로 붙여줍니다. this.bulletPrefab 은 미리 로드해놓은 총알 프리펩을 말합니다.

  오브젝트 풀에서 객체를 가져올 때는 오브젝트_풀.get() 함수를 이용합니다. 오브젝트 풀에서 관리할 객체에 스크립트 컴포넌트를 추가해 reuse() 함수를 구현해 놓으면 get() 함수를 이용해 풀에서 객체를 가져올 때마다 콜백함수로 실행됩니다. 따라서 객체를 풀에서 꺼내와 화면에 붙이기 전 해야할 일들을 reuse() 함수에 구현해 놓으면 됩니다. 또한 get(parameter) 함수로 parameter 를 인자로 넣으면 reuse(param) 에서 param 으로 parameter 가 들어오게 됩니다. 그렇기 때문에 객체를 풀에서 꺼내올 때 동적인 데이터들을 객체에게 넘겨야 한다면 인자를 사용하시면 됩니다. 아래의 예제에서는 풀에서 꺼내와 위치와 각도를 설정합니다.

reuse: function (bulletInfo) {
this.node.rotation = 0;
this.node.x = bulletInfo.x;
this.node.y = bulletInfo.y;
}


  이제는 다 사용한 객체를 풀에 넣어볼까요?

bulletPool.put(bullet.node);

  풀에 넣는 방법은 꺼내올 때의 방법과 비슷합니다. put(객체의_노드) 를 통해 풀에 다시 객체를 넣을 수 있습니다. put() 은 get() 과 다르게 넣으려는 객체의 노드를 파라미터로 넘겨줘야 합니다. 또한 put() 을 실행하면 자동으로 부모에서 풀에 넣으려는 노드를 지워주기 때문에 removeChild() 를 따로 실행하지 않아도 됩니다.

  get() 을 실행하면 reuse() 가 실행되듯이 put() 을 실행하면 unuse() 가 실행됩니다. 따라서 풀에 객체를 넣기 직전 실행해야 할 일들은 unuse() 에 구현하시면 됩니다. 아래의 예시에서는 스케줄을 모두 멈추었습니다.

unuse: function () {
this.unscheduleAllCallbacks();
}


객체를 오브젝트 풀을 통해 관리하는 것은 굉장히 중요합니다. 오브젝트 풀을 사용하느냐 마느냐에 따라 frame rate 는 현저히 차이가 나기 때문에 게임의 성능에도 많은 영향을 끼칩니다. 오브젝트 풀을 올바르게 사용해 최적화된 게임 구현을 해보시길 바랍니다 :)

Comments