스틱맨 발차기, 이펙트, 트윈, 효과음
스틱맨 발차기
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: