2.1. タスクシステム

プレイヤーを表現するとき、その状態は、かわるがわるに移りかわり、例えば、ユーザーからの入力待ち、攻撃、モンスターの行動待ち、などとなります。それらの状態に応じて、アニメーションや、HPの変化などをプログラムします。このような、状態の移りかわりによって表現できるプログラムを、有限状態機械といいます。英語ではFinite State Machineです。タスクシステムは、この有限状態機械を書くための方法の1つです。

状態の移りかわりによって表現できるものの例

switch文でも、有限状態機械を書けます。例えば、Player構造体のなかにstateという変数を作っておいて、switch文でそれに合わせた処理をします。コードは以下のようになります。


enum {
  STATE_TURN_START,
  STATE_ATTACK,
  STATE_TURN_END,
  STATE_DAMAGE,
  STATE_DEAD
};

for (;;) {
  switch(Player.state) {
  case STATE_TURN_START:
    if (keys[KEY_SPACE]) Player.state = STATE_ATTACK;
    break;
  case STATE_ATTACK:
    monster = is_there_monster(front_of_player());
    if (monster) {
      monster->hp -= calc_damage(monster, &Player);
      monster->state = STATE_DAMAGE;
      Player.state = STATE_TURN_END;
    } else {
      Player.state = STATE_TURN_END;
    };
    break;
  case STATE_TURN_END:
    break;
  case STATE_DAMAGE:
    if (Player.hp <= 0) Player.state = STATE_DEAD;
    break;
  case STATE_DEAD:
    Game.state = GAME_OVER;
    break;
  };
};

どんな有限状態機械でもswitch文で書けます。しかし、caseの数が10を超えたり、2重switch文が必要になるころには、このswitch文は長くなって1ページにおさまらなくなり、状態の変化を目で追えなくなり、デバッグが難しくなります。タスクシステムを使うと、有限状態機械をよりエレガントに書くことができます。

タスクシステムとは、

図 2-1. タスクのイメージ。

タスクシステムでは、状態に応じた処理内容のそれぞれを、関数で書きます。イメージとしては、コードは以下のようになります。


struct _task {
  state (*fp)(struct _task *task);
  void *pointer[3];
  struct _task *next;
};

void task_schedular()
{
  struct _task *task;
  for (task = task_list->next; task != task_list; task = task->next) {
    (*task->fp)(task);
  };
}

state player_turn_start(struct _task *task)
{
  if (keys[KEY_SPACE]]) task->fp = player_attack;
}

state player_attack(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  monster = is_there_monster(front_of(player));
  if (monster) {
    monster->hp -= calc_damage(monster, player);
    monster->task->fp = monster_damage;
    task->fp = player_turn_end;
  } else {
    task->fp = player_turn_end;
  };
}

state player_turn_end(struct _task *task)
{
  struct _player *player;
  player = (struct _player *)task->pointer[0];
  if (player->hp <= 0) task->fp = player_dead;
}

state player_dead(struct _task *task)
{
  Game.task->fp = game_over;
}