phaser fsm HTML5 - 유한상태머신 FSM
Finite State Machines, FSM
게임 개발에 관심이 있다면 FSM을 많이 보았을것입니다.
유한상태머신(FSM) 이라고 어려워 보이지만
주어지는 각 상태에 따라 코딩을 하면 되기 때문에 간단한 게임을 만들때 사용하며 좋습니다.
FSM에 대한 기본 골격만 만들어 보겠습니다.
StateMachine 클래스 만들기
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() {}
}
FSM을 적용하기 위해 StateMachine 과 State Class를 추가합니다.
게임 실행시 update()에서 step()을 계속 호출하여 상태를 체크하여 각 상태에 맞는 처리를 한다.
기본 설정
const config = {
type: Phaser.AUTO,
scale: {
mode: Phaser.Scale.FIT,
//mode:Phaser.Scale.RESIZE,
autoCenter: Phaser.Scale.CENTER_BOTH,
parent: "theGame",
width: 590,
height: 490
},
pixelArt: true,
zoom: 2,
physics: {
default: 'arcade'
},
scene: {
preload() {
this.load.spritesheet('dude','https://actioncall.github.io/actioncall/assets/img/dude.png',{ frameWidth: 32, frameHeight: 48 });
this.load.image('sky', 'https://actioncall.github.io/actioncall/assets/img/sky.png' );
},
create() {
this.keys = this.input.keyboard.createCursorKeys();
this.add.image(400, 300, 'sky');
this.player = this.physics.add.sprite(100, 350, 'dude',0);
this.player.direction = 'down';
// The state machine managing the player
this.stateMachine = new StateMachine('idle', {
idle: new IdleState(),
move: new MoveState(),
dash: new DashState(),
}, [this, this.player]);
this.anims.create({key: 'left',frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),frameRate: 10,repeat: -1});
this.anims.create({key: 'down',frames: [ { key: 'dude', frame: 4 } ],frameRate: 20});
this.anims.create({key: 'right',frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),frameRate: 10,repeat: -1});
},
update() {
this.stateMachine.step();
},
}
};
window.game = new Phaser.Game(config);
class IdleState extends State {
enter(scene, player) {
player.setVelocity(0);
}
execute(scene, player) {
const {left, right, up, down, space, shift} = scene.keys;
if (shift.isDown) {
this.stateMachine.transition('dash');
return;
}
if (left.isDown || right.isDown || up.isDown || down.isDown) {
this.stateMachine.transition('move');
return;
}
}
}
class MoveState extends State {
execute(scene, player) {
const {left, right, up, down, space, shift} = scene.keys;
if (!(left.isDown || right.isDown || up.isDown || down.isDown)) {
this.stateMachine.transition('idle');
return;
}
if(up.isDown){
player.direction = 'up'
player.setVelocityY(-120 );
}
if(down.isDown){
player.direction = 'down';
player.setVelocityX(0);
player.setVelocityY(100);
}
if(left.isDown){
player.direction = 'left';
player.setVelocityX(-120);
}else if(right.isDown){
player.direction = 'right';
player.setVelocityX(120);
}
player.anims.play(player.direction, true);
//player.anims.play(`${player.direction}`, true);
}
}
class DashState extends State {
enter(scene, player) {
player.anims.play(player.direction);
switch (player.direction) {
case 'up':
player.setVelocityY(-300);
break;
case 'down':
player.setVelocityY(300);
break;
case 'left':
player.setVelocityX(-300);
break;
case 'right':
player.setVelocityX(300);
break;
}
scene.time.delayedCall(300, () => {
this.stateMachine.transition('idle');
});
}
}
config에서 preload(), create(), update()에 대해 정의한다.
create에서 idle, move, dash 상태에 따른 stateMachine을 생성한다.
update에서 step을 지속적으로 호출하여 상태 변화(idle, move, dash )에 따른 각 클래스를 호출하게 된다.
Comments
Login: