위 게임은 뱀파이어 서바이벌이라는 게임인데,
오브젝트 풀링에 분명히 신경을 많이 썼을 것으로 예상돼서 가져왔다.
오브젝트 풀링은 대표적이고 전통적인 최적화 기술 중 하나고 다양한 방면에서 활용할 수 있다.
꽤나 중요하고 자주 쓰는 기술이다 보니 검색하면 관련 내용들이 무수히 나온다.
하지만, 그렇다고 유니티가 따로 오브젝트 풀링 관련된 API를 제공하지는 않는다.
반면 코코스 크리에이터에서는 오브젝트 풀링 API를 제공하고 있다.
그리고 코코스는 오브젝트를 노드로 부르기 때문에 그 이름이 노드 풀링(Node Pooling)이다.
오브젝트 풀링은 여러가지 예외처리, 최적화 처리를 해줘야 해서 built-in 된 API를 그대로 쓰는 것보다는
직접 구현하는 것이 적절하다고 생각하지만, 그래도 한 번 정리해본다.
1. 노드 풀 생성
this.cardPool = new cc.NodePool();
간단하게 노드 풀을 만들어준다.
생성자에는 poolHandlerComp라는 매개변수를 넣을 수 있는데 밑에서 다시 설명한다.
2. 노드 풀에 새 노드 추가 (put)
const poolSize = 30; // 초기 노드 풀에 들어갈 노드 개수
for (let i = 0; i < poolSize; ++i) {
let card = cc.instantiate(this.cardPrefab);
this.cardPool.put(card); // 노드 풀에 새 노드 추가
}
put 함수는 노드 풀에 새 노드를 추가하는 함수다.
put 함수는 몇 가지 특이점이 있다.
(1) 부모 노드와 연결을 해제한다.
const poolSize = 30;
for (let i = 0; i < poolSize; ++i) {
let card = cc.instantiate(this.cardPrefab);
card.setParent(this.hand); // card의 부모 노드를 설정해도
this.cardPool.put(card); // put이 실행되면서 card.parent = null이 된다
}
put 함수는 노드를 풀에 집어넣으면서 해당 노드의 parent를 null로 초기화한다.
(2) 다른 타입의 노드를 넣을 수 있다
const poolSize = 30;
for (let i = 0; i < poolSize; ++i) {
let card = cc.instantiate(this.cardPrefab);
let gold = cc.instantiate(this.goldPrefab);
this.cardPool.put(card);
this.cardPool.put(gold);
}
노드 풀에 들어갈 수 있는 제한은 노드의 컴포넌트나 클래스가 아닌, cc.Node 타입이다.
위의 코드대로라면 나중에 노드 풀에서 노드를 꺼낼 때도 card와 gold가 번갈아가면서 나올 것이다.
(3) 같은 uuid의 노드가 풀에 존재하면 넣지 않는다
const poolSize = 30;
let bar = this.node.getChildByName("fixedBar");
for (let i = 0; i < poolSize; ++i) {
let card = cc.instantiate(this.cardPrefab);
this.cardPool.put(card);
this.cardPool.put(bar);
}
put 함수는 노드 풀에 새로 추가하려는 노드와 동일한 uuid가 있는지 검사해서 있으면 추가하지 않는다.
card는 매번 새로 생성했기 때문에 새로운 uuid가 부여된다.
그리고 bar는 반복문을 계속 돌아도 동일한 노드를 참조하는 것이기 때문에 항상 uuid가 같다.
for문에서 최초에 한 번만 풀에 추가되고 이후에는 들어가지 않는다.
3. 노드 풀에서 노드 가져오기 (get)
let card = this.cardPool.get();
card.setParent(this.startDeck);
get 함수는 노드 풀에서 노드를 꺼내서 리턴하는 함수다.
put 함수와 마찬가지로 몇 가지 특이점이 있다.
(1) put으로 풀에 넣었을 때 parent를 null로 초기화했기 때문에
노드의 parent를 특정 노드로 초기화하려면 get 이후에 해야 한다.
(2) 풀에서 노드를 꺼낼 때는 뒤에서부터 꺼낸다.
즉 put/get은 Stack 구조에서의 push/pop과 비슷하다고 볼 수 있다.
4. put, get의 콜백 함수
put이 실행될 때, get이 실행될 때 같이 실행되는 콜백 함수들이 있다. reuse와 unuse다.
이 reuse와 unuse를 재정의할 수 있다.
this.cardPool = new cc.NodePool(Card); // reuse, unuse가 정의된 Card 클래스 전달
const poolSize = 30;
for (let i = 0; i < poolSize; ++i) {
let card = cc.instantiate(this.cardPrefab);
card.addComponent(Card); // card 노드에 Card 클래스 컴포넌트 추가
this.cardPool.put(card);
}
먼저 cc.NodePool을 생성할 때, reuse와 unuse가 정의된 클래스를 매개변수로 넣어준다.
이 매개변수 pooolHandlerComp는
'The constructor or the class name of the component to control the unuse/reuse logic.' 이라고 한다.
그리고 풀에 넣는 card 노드에는 위에 매개변수로 전달했던 클래스가 컴포넌트로 붙어있어야 한다.
class Card extends cc.Component {
onLoad() {
console.log("onLoad card");
}
reuse() {
console.log("reuse card");
}
unuse() {
console.log("unuse card");
}
}
그리고 Card 클래스에 reuse와 unuse를 정의해준다.
실제로 노드 풀에서 노드를 꺼내면서 get이 실행될 때마다 reuse,
노드 풀에 노드를 넣으면서 put이 실행될 때마다 unuse가 실행된다.
reuse에는 노드의 시작과 관련된 내용들, 매 번 초기화가 필요한 작업들을 넣어주면 되고
unuse는 노드의 종료와 관련된 내용들, 해제가 필요한 작업들을 넣어주면 된다.
이 reuse와 unuse에는 주의해야 할 특징이 있다.
(1) reuse는 cc.Node의 라이프 사이클에서 onLoad 보다 먼저 실행된다.
onLoad는 노드가 생성됐을 때 한번 실행되지만, reuse는 노드 풀에서 꺼내질 때마다 실행된다.
(2) get과 reuse는 동일한 매개변수를 사용할 수 있다.
let card = this.cardPool.get(CardRank.RARE, 15);
reuse(cardRank: CardRank, cardIndex: number) {
this.cardRank = cardRank;
this.cardIndex = cardIndex;
console.log("reuse card");
}
위와 같이 활용할 수 있다.
'Cocos Creator' 카테고리의 다른 글
Label Update (0) | 2022.07.28 |
---|---|
프리팹 안에 프리팹 (0) | 2022.04.13 |
Layout Update (0) | 2022.04.06 |
좌표계 (월드 스페이스와 로컬 스페이스) (0) | 2022.04.06 |
[2.4.5 버그] cc.lerp (0) | 2022.04.01 |