본문 바로가기

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 )에 따른 각 클래스를 호출하게 된다.

현재글 : phaser fsm HTML5 - 유한상태머신 FSM
Comments
Login:

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