본문 바로가기

오브젝트 풀링 - Object Pooling

Object Pooling

오브젝트 풀링은 게임에서 자주 쓰이는 기법입니다.

오브젝트가 많아 질수록 cpu와 memory 사용율이 올라가면 오브젝트를 파괴해도 찌꺼기가 남아 cpu와memory를 괴롭힙니다.

수많은 오브젝트 생성, 파괴를 반복하는 것이 아닌 일정갯수를 오브젝트를 미리 생성해놓고 재활용하는 방식으로 쓰는 것입니다.

여기는 kick 텍스트를 tween처리한 부분은 pooling 기법으로 변경하겠습니다.

kick text 10개 생성 후 큐 삽입

            //kick text
            //this.kickText = this.add.text(player.x, player.y,"Kick",{fontFamily: "Arial",fontSize: player.height*0.6,color: "#ffffff"})
            //this.kickText.visible = false;

            //kick text pool          
            let kickPool = new KickPool()
            this.poolManager = {'kick':kickPool};

            for(var i=0;i<10;i++){
                var kickItem = this.add.text(player.x, player.y,"Kick",{fontFamily: "Arial",fontSize: player.height*0.6,color: "#ffffff"})            
                this.poolManager.kick.push(kickItem);
            }

this.poolManager 를 생성하고 kick(key) 에 해당하는 kickpool을 넣어줍니다.
- kickPool은 queue로 만든 클래스입니다.
- 기본적으로 꺼내고, 쓰고, 반환 하는 방식에는 큐(Queue) 자료구조를 많이 씁니다.

유니티에서도 많이 쓰이죠.

KickPool class Queue 생성

class KickPool {      
    constructor() {
        this.items = [];
        this.add_x = 30;
    }

    push(item){
        item.visible = false;
        item.setOrigin(0.5);
        item.x = item.x+this.add_x;
        this.items.push(item);
    }

    enqueue(x, y, item) {        
        item.x = x+this.add_x;        
        item.y = y;
        item.visible = false;
        this.items.push(item);
    }

    dequeue() {        
        var item = this.items.shift();
        item.visible = true;
        return item;
    }

    peek() {return this.items[0];}    
    getSize() {return this.items.length;}    
    isEmpty() {return this.getSize() === 0;}     
}  

기본적인 큐 자료구조로 먼저 들어간것이 먼저 나오는 FIFO 방식
constructor : 생성자, this.add_x는 캐릭터 오른쪽에 텍스트를 표시하기 위해서 30을 고정
push: 텍스트 생성 삽입 및 초기 설정
dequeue : this.items 값 꺼내기
enqueue : this.items에 삽입

kick 텍스트 꺼내 사용하기

//scene.kickText.visible = true;
//            scene.tweens.add({targets:scene.kickText ,duration: 100, y:player.y-20, completeDelay: 0,
//                onComplete: function () {scene.kickText.visible =false;},
//                 }, this); 

let kickItem = scene.poolManager.kick.dequeue()            
            scene.tweens.add({targets:kickItem ,duration: 100, y:player.y-20, completeDelay: 0,
                            onComplete: function () {scene.poolManager.kick.enqueue(player.x, player.y, kickItem); },
                             }, this); 

scene.poolManager 에서는 꺼내서 쓰고, onComplete에서 enqueue로 반환하면 됩니다.

이 방식으로 초기에 10개를 미리 만들고
필요할때 꺼내 쓰고 반환 하는 방식으로 사용하면 되겠습니다.

전체 소스

class StateMachine {
    constructor(initialState, possibleStates, stateArgs=[]) {
        this.initialState = initialState;
        this.possibleStates = possibleStates;
        this.stateArgs = stateArgs;
        this.state = null;

        for (const state of Object.values(this.possibleStates)) {
        state.stateMachine = this;
        }
    }

    step() {    
        if (this.state === null) {
            this.state = this.initialState;
            this.possibleStates[this.state].enter(...this.stateArgs);
        }    
        this.possibleStates[this.state].execute(...this.stateArgs);
        //console.log("this.state :"+this.state)
    }

    transition(newState, ...enterArgs) {
        this.state = newState;
        this.possibleStates[this.state].enter(...this.stateArgs, ...enterArgs);
    }
}

class State {
    enter() {}
    execute() {}
}

const config = {
    type: Phaser.AUTO,
    scale: {                
        mode: Phaser.Scale.FIT,
        //mode:Phaser.Scale.RESIZE,
        autoCenter: Phaser.Scale.CENTER_BOTH,
        parent: "theGame",
        width: 400,
        height: 300
    },    
    pixelArt: true,
    zoom: 2,
    physics: {default: 'arcade',
              arcade: {gravity: { y: 0 },
              debug: true}
              },
    scene: {
        preload() {
            this.load.path = 'https://actioncall.github.io/actioncall/assets/';            
            this.load.audio("hit_02", "audio/Hit_02.wav");
            this.load.image('sky', 'img/sky.png'  );
            //this.load.path = '/static/img/phaser/assets/';
            this.load.multiatlas('stickman', 'img/kick/kick.json', 'assets');          
            this.load.image('kickEffect','img/atk_eff01.png');  
        },

        create() {
            this.audio_hit_kick = this.sound.add('hit_02');

            this.keys = this.input.keyboard.createCursorKeys();
            this.add.image(200, 150, 'sky');  
            let player = this.physics.add.sprite(100, 150, 'stickman',0);          
            this.add.existing(player,0)
            player.direction = 'down'; 


            // input anims
            let frameNames = this.anims.generateFrameNames('stickman', {
                start: 1, end: 19, zeroPad: 4,
                prefix: 'kick_', suffix: '.png'
            });

            this.pointer = this.input.activePointer;
            this.anims.create({key: 'right',frames: frameNames,frameRate: 30,repeat: 0});            

            //kick text pool
            let kickPool = new KickPool()
            this.poolManager = {'kick':kickPool};

            for(var i=0;i<10;i++){
                var kickItem = this.add.text(player.x, player.y,"Kick",{fontFamily: "Arial",fontSize: player.height*0.6,color: "#ffffff"})            
                this.poolManager.kick.push(kickItem);
            }

            this.kickEff = this.make.image({
                x: player.x+20,
                y: player.y-10,
                key: 'kickEffect',            
                alpha: 0,
                flipX: true,            
                scale : {x: 0.3,y: 0.3},
                origin: {x: 0.5, y: 0.5},
            });

            this.stateMachine = new StateMachine('idle', {
                idle: new IdleState(),
                fight: new FightState(),               
            }, [this, player]);
        },

        update() {
            this.stateMachine.step();
        },
    }
};
window.game = new Phaser.Game(config);

class IdleState extends State {
    enter(scene, player) { 
        player.direction = 'down';
        player.setSize(player.width-5, player.height, 0);
    }

    execute(scene, player) {
        const {left, right, up, down, space, shift} = scene.keys;

        if (left.isDown || right.isDown || up.isDown || down.isDown || scene.pointer.isDown) {
            this.stateMachine.transition('fight');
            return;
        }
    }
}

class FightState extends State {
    execute(scene, player) {    
        const {left, right, up, down, space, shift} = scene.keys; 

        if (!(left.isDown || right.isDown || up.isDown || down.isDown || scene.pointer.isDown)) {
            this.stateMachine.transition('idle');
            return;
        }        
        if(up.isDown){
            player.direction = 'up'
        }
        if(down.isDown){
            player.direction = 'down';
            player.setVelocityX(0);
        } 
        if(left.isDown){
            player.direction = 'left';
        }else if( ( right.isDown || ( scene.pointer.isDown && player.x < scene.pointer.x  )  ) && player.direction != 'right'  ){
            player.direction = 'right';                

            scene.audio_hit_kick.pause();
            scene.audio_hit_kick.play({volume:0.5});

            player.anims.play(player.direction, false);
            player.anims.play(player.direction, true);

            let kickItem = scene.poolManager.kick.dequeue()            
            scene.tweens.add({targets:kickItem ,duration: 100, y:player.y-20, completeDelay: 0,
                            onComplete: function () {scene.poolManager.kick.enqueue(player.x, player.y, kickItem); },
                             }, this); 
            //col size
            player.setSize(player.width, player.height, 0);

            //attack
            scene.tweens.timeline({
                tweens: [{ targets: scene.kickEff,
                            y:player.y-20,
                            duration: 50,
                            alpha: { from: 0, to: 0.5 },
                            ease: 'Cubic.easeInOut'
                        },{targets: scene.kickEff,
                           y:player.y-20,
                           duration: 10,
                           alpha: { from: 0.5, to: 0 },
                           delay: 100
                        }]
            });
        }        
    }
}
class KickPool {      
    constructor() {
        this.items = [];
        this.add_x = 30;
    }

    push(item){
        item.visible = false;
        item.setOrigin(0.5);
        item.x = item.x+this.add_x;
        this.items.push(item);
    }

    enqueue(x, y, item) {        
        item.x = x+this.add_x;        
        item.y = y;
        item.visible = false;
        this.items.push(item);
    }

    dequeue() {        
        var item = this.items.shift();
        item.visible = true;
        return item;
    }

    peek() {return this.items[0];}    
    getSize() {return this.items.length;}    
    isEmpty() {return this.getSize() === 0;}     
}  

            // scene.tweens.timeline({
            //     tweens: [{ targets: player,
            //                 x:player.x+15,
            //                 duration: 10,
            //                 // alpha: { from: 0, to: 0.5 },
            //                 ease: 'Cubic.easeInOut'
            //             },{targets: player,
            //                x:player.x,
            //                duration: 10,
            //             //    alpha: { from: 0.5, to: 0 },
            //                delay: 100
            //             }]
            // });


// this.time.delayedCall(500, () => {                
// });
현재글 : 오브젝트 풀링 - Object Pooling
Comments
Login:

Copyright © PythonBlog 2021 - 2022 All rights reserved
Mail : PYTHONBLOG