超前衛的ゲームプログラミング方法論 その1

  • 予定

1:コア編
2:描画システム編
3:(おまけ)サウンドシステム編

  • その0 概要
  • はじめに

今年の未踏ユースにて、目的じゃなくて手段が先に来てるとか、
習作ぽいとか、テーマに何か引っかかるものはあるけどPM的にはピンと来ないとか、
散々な理由で採択されなかった私のテーマであったが、
(というか、習作の感がぬぐえないとかそういうのが落とす理由になるとは
思いもよらんかったですよ。あれだけチャレンジしろチャレンジしろって書いておいて)
この世界がいつまでたっても退屈な言語に支配されている現状を
黙然としているのにはやはり耐えられず、
Haskellがそのような言語に対していかなるアドバンテージを持っているかを
実例とともに示す必要があると思いこれを書くことにした。

  • とはいえ

別にこれ、応募時から暖めていたものではない。
(というか、アイデアはとくになかった…。
考えりゃ何とかなるだろと思っていた…)
コア部分についてはHSDL作りながら考えたし、
描画システムはつい最近思いついた。
まぁ、そんなものだから別に大したものではないのかもしれないが、
今更ファイバでコンテキストを隠蔽しようとしているのに比べると
そのはるか先を行く手法であることは間違いなく?、
その点で超前衛的と題させてもらった。
イデア自体は全く持って練られていないのだが、
いずれも定義的な手法で、
もしちゃんとしたライブラリに仕立て上げることが叶ったならば
ゲームプログラミングに対して一石を投じることになるであろう。
(こんな閲覧者の少ないページで何言ってんだ、って感じだけど…)

  • その1、コア編

コアとか書いているが、単に実行制御のことである。
一般的な実装方法を疑似コードで書くと、

while(true){
  process_events(); // OSのイベント処理
  on_move(); // 一フレーム分のゲーム状態の更新
  on_draw(); // 描画処理
  wait();    // FPSあわせ
}

このような感じになる。
この方法の問題点は言うまでも無くon_move()の処理が切れ切れになってしまうことである。
一フレーム分の処理をして返って来る必要があるので、
前回のフレームの状態を保持しておく必要がある。
その保持しておくべき情報はキャラクタの位置とか、弾のベクトルとか、点数とか、
そういう明示的な情報のみならず、たとえば100フレームウェイトするだとかいった場合に
現在xxフレーム目であるとか、そもそも現在がウェイト中であるとか、
そういう局所的な情報も含まれる。
そのような状態を明示的に保存して毎回復帰しなければならないということが良くない、
いたずらにプログラムの複雑さを高める結果となっていると思うのである。


ここでコルーチン(あるいはファイバ、マイクロスレッド?)を使う手法は
よく知られていると思うのだが、OSのサービスのような何かを使った実装を
行う場合はシューティングの弾丸一個とかそういうレベルでのファイバの
割付もコストがどうかわからないし、それにそもそもCのような言語にとって
継続まがいな動作をするこれらは正直どうなのかわからないし(例外の整合性とか?)
継続を自力で扱える言語であれば幾分自然であるが、
どちらにせよ結局コードとしては同じような感じになると思う。
(一フレームの処理が終わるたびに他のファイバに実行を移す)


ここで先の説明の例を挙げておく。
問題として、

  • 100フレームかけてフェードイン
  • ボタンが押されるとゲーム開始

この程度のものを考える。


非常にナイーブな実装だと、on_moveは

void on_move()
{
  if (state<100){
    draw_bg(state/100.0);
    state++;
  }
  else if (state==100){
    if (button_pushed)
      state==101;
  }
  else if (state==101){
    ...
    (ゲーム処理)
    ...
  }
}

このようなステートマシン的な実装にならざるを得ない。
この実装の問題は処理が分断されることにより
プログラムの流れがわかりにくくなっていることである。
ここでコルーチンが使えるのであれば、

void on_move()
{
  for (int i==0;i<100;i++){
    draw_bg(t/100.0);
    yield();
  }
  while(!button_pushed)
    yield();

  ...
  ゲーム処理
  ...
}

このように書くことが出来る。
これはこれで悪くはないと思うのだが、
これでもやはりフレームを意識したコーディングを要求される。
要するに、yield()が一個でも抜けたら正しく動かないのである。


このことを問題とするのはどうなのか、という気もするが、
皆様ご承知の通り(?)、遅延評価処理系では継続の使用自体が必要にならない場合が多い。
http://www.sampou.org/haskell/a-a-monads/html/contmonad.html
そこで、ここでは遅延評価を積極的に利用して
フレームごとの処理を全く意識せずに記述できる方法を考えることにする。


(明日に続く…。今回、時間を消費しすぎた…)