본문 바로가기

스틱맨 발차기, 이펙트, 트윈, 효과음

스틱맨 발차기

FSM 기반으로
스틱맨 발차기 애니효과, 트윈효과, 발차기 효과음이 적용되었습니다.

카보스 우측화살표 키(->) 와 마우스 클릭시 동작합니다.

발차기 애니메이션 효과

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);        
    }

    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.image('sky', 'img/sky.png'  );
            this.load.multiatlas('stickman', 'img/kick/kick.json', 'assets');          
        },

        create() {
            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});

            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';

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

            //col size
            player.setSize(player.width, player.height, 0);
        }        
    }
}

기본 FSM 패턴에 키보드 우측 화살표키 와 마우스 클릭시 동작 기능을 추가하였습니다.(FightState Class)

config physics의 debug를 true 설정하여 캐릭터 주위의 충돌경계가 표시되도록 했습니다.
player.setSize 함수로 발차기전과 발차기 후 의 플레이어 충돌 경계를 조정하였습니다.

발차기 tween 효과

preload() {
    ...
    ...
    this.load.image('kickEffect','img/atk_eff01.png');          
},
create() {
           ...
           ...
           ...
            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.kickText = this.add.text(player.x, player.y,"Kick",{fontFamily: "Arial",fontSize: player.height*0.6,color: "#ffffff"})
            this.kickText.visible = false;
    ...
    ...
        },
    ...
    ...
};

class FightState extends State {
    execute(scene, player) {    
        ...
    ...
    ...
        }else if( ( right.isDown || ( scene.pointer.isDown && player.x < scene.pointer.x  )  ) && player.direction != 'right'  ){
            player.direction = 'right';

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

            //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
                        }]
            });
            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); 

        }        
    }

this.kickEff : 이펙트 이미지 위치(x,y), key, alpha, 크기 등등을 설정
this.kickText : 이펙트 텍스트 설정
this.kickText.visible : 표시여부

tweens.timeline으로 이미지 투명도를 조절하여 나타났다가 사라지는 2개의 효과를 줍니다.
kick 텍스트는 나타나서 위로 올라간 후 사라지는 효과를 주었습니다.

발차기 효과음

preload() {
    ...
    ...
        this.load.audio("hit_02", "audio/Hit_02.wav");
},
create() {
           ...
           ...
           ...
            this.audio_hit_kick = this.sound.add('hit_02');
    ...
    ...
        },
    ...
    ...
};

class FightState extends State {
    execute(scene, player) {    
        ...
    ...
    ...
        }else if( ( right.isDown || ( scene.pointer.isDown && player.x < scene.pointer.x  )  ) && player.direction != 'right'  ){
        scene.audio_hit_kick.play({volume:0.5});            
        ...
        ...
        ...
        }        
    }

전체 소스

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);        
    }

    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.image('sky', 'img/sky.png'  );
            this.load.multiatlas('stickman', 'img/kick/kick.json', 'assets');  
            this.load.image('kickEffect','img/atk_eff01.png');          
            this.load.audio("hit_02", "audio/Hit_02.wav");
        },

        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});

            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.kickText = this.add.text(player.x, player.y,"Kick",{fontFamily: "Arial",fontSize: player.height*0.6,color: "#ffffff"})
            this.kickText.visible = false;


            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.play({volume:0.5});

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

            //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
                        }]
            });
            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); 
            // this.kickText.visible 
        }        
    }
}
현재글 : 스틱맨 발차기, 이펙트, 트윈, 효과음
Comments
Login:

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