読者です 読者をやめる 読者になる 読者になる

ゆとりーなの日記

日記的な事を書いて行くと思はれる

なんだかんだで進行中

買いました。

Programming in Lua プログラミング言語Lua公式解説書
Programming in Lua プログラミング言語Lua公式解説書Roberto Ierusalimschy 新丈 径

アスキー・メディアワークス 2009-08-28
売り上げランキング : 303247


Amazonで詳しく見る
by G-Tools
最近本買いすぎですね。明らかに部活辞めた反動ですね。
まぁそれはさておきいつの間にか東方のPSP移植の方が地味に進んでいるのでそれについて少し。
今回はPSPSDKのテンプレMakefileのオプションを使うということで、0x禁止、例外、実行時型情報不使用ってことでやってるんですが、実行時型情報はさておき、例外不使用ってのはどうなんですかね。101のルールな本にはデフォルトでオフでもオンにすべきとか書いてあった気がしますし。まぁ0xに関してはg++4.3ベースだとあまり0xの恩恵を受けられる機能がなかった気がするのであれですね。TR1とかはBoostで代用できますし。あ、当然Boostは使いますよ。要ビルドのやつは流石に厳しいでしょうがヘッダのみで使えるやつはPSPでも問題なく使えると信じてます。
で、Luaを全面的に押し出して作っていくことにもしているので、まぁこれはビルドの度にエミュを起動しなおすのがだるいってことですね。Javaベースのエミュなんでちょっくら起動が重いんです。luabindを使いたいところだったのですが、実行時型情報の縛りにより断念、例外不使用のオプションはあるみたいなんですが、実行時型情報に関してはなんか見つけられなかったので。ひょっとすると探せばあるのかもしれませんが、探すのも面倒だったのでグルーコードは手書きです。
C++側はsce系のネィティブAPIをたたいてますね。Win32APIをたたいていた事もあって、ここはネィティブにこだわるわけですね。初期化とか開放処理をクラスでラップしたものや、Luaステートも持ったframeworkクラスを作るわけです。こいつのupdateメソッドをmain関数内のwhile無限ループで延々呼ぶ訳ですね。
というわけでmain関数はこんな感じです。

int main() {
  th06_psp::framework framework;
  while (true) {
    framework.update();
  }
  return 0;
}

main関数の中身は小さいほうがいいのでこんくらいでいいでしょう。
frameworkクラスはコンストラクタでPSPのコールバック、所謂ホームボタンから終了できるようにする初期化や、グラフィック、サウンド、コントローラーとLuaステートの初期化とライブラリオープン、グルーコード登録とかをやってるわけですね。で、th06_psp.luaってファイルのinit関数を呼ぶわけです。これの中でlua内での初期化をちゃっかりやってます。

local co

function init()
  co = coroutine.create(logo)
end

logoってのは最初に出てくる画面の画像ファイルの名前がlogoって付いていたのでとりあえず適当に名づけました。要はあの画面の処理を行うわですが、それをコルーチンとして登録してるだけです。coを宣言した時点で初期化しちゃえばと思ったんですが、どうもそれだと落ちるのでこういう形に落ち着きました。
で、frameworkのupdateメドッドでは、th06_psp.luaのupdate関数を呼び出すことにしました。こいつはこんな感じで、

function update()
  coroutine.resume(co)
end

さくっと初期化しておいたコルーチンを呼んでるだけです。これで直線的にゲームの進行を記述できるようになった気がします。
例えばlogoでは、キーが押されるまで待機して、押されればタイトルに行くってのがこんな風に書けるわけですね。

function logo()
  while true do
    if key_triger(kCircle) == true then
      coroutine.yield()
      title()
    end
    coroutine.yield()
  end
end

C++でこんなことやってるといつかはスタックが破綻しそうですが、Luaとかだと末尾再帰とかなんとかいう最適化が効くとかでなんとかなるみたいです。coroutine.yield()ってのがコルーチンのおいしいやつで、ここを通るとth06_psp.luaのupdate関数にに制御を返して、次のフレームで
updateのcoroutine.resume(co)から呼ばれたときは前回のcoroutine.yield()のところから始ってくれるんですね。とってもおいしいですね。
で、frameworkの方のupdateメソッドは続いて描画リストに登録してあるアクター的なサムシングでアクティブなやつを描画する感じにしています。

// 画面をクリアしておく
BOOST_FOREACH(const actor_element &element, list_.actor_list) {
  if (element.element->is_active()) {
    element.element->draw();
  }
}

list_ってのはこんな感じのえげつない型になってます。

typedef boost::multi_index::multi_index_container<
              actor_element,
              boost::multi_index::indexed_by<
	                boost::multi_index::sequenced<>,                           
	                boost::multi_index::ordered_unique<
                      boost::multi_index::tag<actor_id>,
                      boost::multi_index::member<actor_element, std::string, &actor_element::id> > > > actor_list_type;

Boost先生のmulti_indexですはい。今回はLua側には描画APIは公開せずに、描画リストに描画対象を追加して、それを操作するって形にしたのでこんな感じになりました。こうすると毎フレーム毎に描画APILuaからいちいち呼ぶ必要なくて、最初に一回背景とかを登録しておけばあとはほっといてもC++側が勝手に描画してくれますからね。描画対象を動かしたいときだけ動かせって命令を送ればいいだけになるんで多分スクリプト的にも綺麗になる気がしますね。
追加した順に描画してほしいので基本sequencedで、とはいっても登録した対象を移動するときにどう対象を指定するのかって考えたときに文字列だと割りと楽かなーってことでordered_uniqueで文字列で取ってこれるようにしてみました。あと、自機や弾はフレームより手前に書いて欲しいってのはあるだろうから、キーを基準に挿入するってのも用意しときました。
actor_elementってのは、

struct actor_element {
  actor_element(const std::string &name, const actor_ptr &ptr) : id(name), element(ptr) {}

  std::string id;
  actor_ptr element;
};

な感じで、単純なキーと要素のペアですね。で、actor_ptrってのは単純にactorってクラスのshared_ptrです。で、actorが

class actor : private boost::noncopyable {
 public:
  actor() : is_active_(true) {}

  virtual ~actor() {}
  virtual void draw() = 0;
  virtual void move(float x, float y) {}
  virtual void move_to(float x, float y) {}
  virtual void resize(float width, float height) {}
  virtual void set_uv(float u, float v, float width, float height) {}
  virtual void set_color(boost::uint8_t red, boost::uint8_t green, boost::uint8_t blue) {}
  virtual void set_alpha(boost::uint8_t alpha) {}

  virtual float x() const {
    return 0.f;
  }

  virtual float y() const {
    return 0.f;
  }

  virtual bool exist() const {
    return true;
  }

  bool is_active() const {
    return is_active_;
  }

  void set_active(const bool is_active) {
    is_active_ = is_active;
  }

 private:
  bool is_active_;
};

と随分適当な設計になってしまいました。何しろテクスチャを貼らない対象や、アルファブレンディングを行わない対象もこいつを継承するわけですが、これらに対してset_uvやset_alphaを呼んでも当然何も起きません。public継承的にはこういう挙動はどうなんだろうと思わないこともないですがまぁめんどうなんで今回はこれでいくことにしました。
現状、

  • box・・・アルファ付き四角形
  • back_image・・・アルファなし背景画像
  • image・・・アルファつき画像
  • bright_image・・・ポリゴンの色と合成するアルファつき画像
  • text・・・文字列

が用意してあて、lua側からは追加、或いは挿入APIに加えたいものの種類の定数を渡すって感じにしました。最初は種類ごとにAPIを分けていたんですが、ぶっちゃけLua経由だとあんまり型安全な感じにできないので、もう全部一緒にして実行時に分岐すればいいかってことに落ち着きました。あとは3D背景に向けたのとか、拡大回転に対応したやつを用意すれば終わりって感じですかね。
・・・まぁ今日はこのへんでということで。