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

ごちゃログぴこっ

はなまるデジタル創作紀行(DTM、TAS、いろいろな技術)

メモリ内容等を動画(の外側)に表示させる方法

ゲーム ゲーム-TAS 技術 技術-プログラミング 技術-プログラミング-Lua

先日発表されたロックマン2 TASの動画は、普段目にすることのないメモリの内容を画面右下に表示していました。本稿ではこの方法について説明します。

作業工程にLuaを用いるため、少なからずLuaの知識が要求されます。さほど難しくもありませんし、TAS制作にも役立ちますので、興味のある方はKaretta|Luaプログラミング入門等を見ながら習得してみるのもよいでしょう。
内部の情報がいっしょに表示されていると、目の前で起こっていることを理解するのに役立つことがあります。そこでわたしもこれにのっかって、蒼月の十字架のTASでは制作時に見ていたあれこれを映像にのせちゃいました。

キー入力、フレームカウンタ、メモリ内容、タッチ表示と幅広く追加しています。現状DeSmuMEにはスクリーン上の表示をAVIに録画する方法が用意されていないこともあり、こういったものは映像編集で加えるほかありません。

必要なもの

実質的に今回必要なものは、たったひとつのAviUtlプラグインのみです。

本記事では以後、上記の Lua プラグインを用いていますが、2016年現在では AviUtl 本体が Lua スクリプト機能を有しているため、そちらを使用する方が流儀としては自然そうです。

Lua for AviUtl プラグインはその名が示すとおり、AviUtlからLuaスクリプトを実行して映像編集をおこなうためのプラグインです。文字や単純図形を描写したり、色を取得・変換したりする簡単な機能群を内蔵しています。エミュレータからの映像出力の際にメモリの内容を並行してファイルに出力し、それらをLua for AviUtlから解釈して適当な形で表示させることで、あのような映像ができあがるのです。

その他、使い慣れた映像編集ツールやプラグインがあった方が便利なのは言うまでもありません。AviUtlですべて済ませるのであれば、配布サイトにある拡張編集プラグインを入れておけばだいたいのことはできます。ちなみに、わたしはAviSynthを使いました(AviUtlとの関連: AviSynth Wiki - AviUtlでAVSファイルを開くAviSynth Wiki - AviUtlでAviSynthフィルタの設定を行う)。

作業工程

作業工程例をおおまかに示すと以下のようになります。

  1. メモリの値(やコントローラ操作)を各フレームの終わりに記録するエミュレータ用のLuaスクリプトを記述する(サンプル後述)。
  2. エミュレータのAVIを出力と並行してLuaスクリプトを実行、加工前映像と動画に表示させたい情報をファイルに記録する。
  3. 前の工程で記録したファイルを読み込み、その内容を適当な位置に表示するLuaスクリプトを記述する(サンプル後述)。
  4. 出力したAVIをAviUtlで読み込み、プラグインからLuaスクリプトを呼び出し、表示を映像上に追加する。

注意:AviUtlでエミュレータで出力したAVIを開くと、映像が正しくデコードされないことがあります。「ファイル → 環境設定 → 入力プラグイン優先度の設定」から読み込み方法を変えると解決することがあります。

AviUtlでのLuaスクリプトの起動は、メニュー「設定 → luaの設定」から出てくるダイアログからおこないます。「右上のチェックボックスはフィルタが有効かどうかを意味している」「メータとチェックは気にしなくてよい」の2点がわかれば、あとは「Browse」と「Reload」を使って作業するだけなので、動作させることは難しくありません。

映像のサイズを拡げたり、背景画像を貼り付けたりといった編集は、Luaによる処理の前に別の方法でおこなっておくと楽かと思います。先述の拡張編集プラグインを用いておこなうこともできます。その際はプラグインの優先順位に気をつけましょう。

さて、以降にサンプルを示しますが、これから紹介するスクリプトはファイル読み込みの際に数秒以上を要する可能性があります。スクリプトの動作はあらかじめ短い動画でテストしておくことをおすすめします。

エミュレータからの書き出し

以下にサンプルを用意しました。TODOで示された箇所を適切に変更するだけで利用できます*1

あまりEmuLuaの機能に関しては深く説明しませんが、必要になりそうなポイントは次のとおりです。

  • emu.registerafter を使って、フレーム処理後のタイミングで関数を実行できる。
    • エミュレータによっては emu ではなく snes9x などの固有名詞を書かなければならない(今後はそうでもなくはるはず)。
    • じつはsnes9xのemu.registerafterでは現状タイミングにやや問題がある。必要なら代わりにemu.registerbeforeのタイミングで値を書き出して調整する方が賢明かもしれない(2009年11月追記)
  • memory.readbyte などの memory.read* 系関数で、メモリから値を読むことができる。
  • joypad.get が正しくサポートされている場合、これを通じてコントローラ操作内容を取得できる。

エミュレータごとにLuaの実装状況は異なるので、実装状況を把握するにはエミュレータのドキュメントやソースコードを読みつつ、動作を確かめてみるほかありません。現状では、Gensが仕様・ドキュメントともに理想的に整備されています。以下、若干の資料にリンクしておきます。

joypad.get はエミュレータによって実装状況がまちまちで、ムービー再生中でもユーザ入力が返ってきてしまったり、タイミングがずれていたりする問題を抱えていることがあります。蒼月の十字架エンコードをした時点では、DeSmuMEでは joypad.get が利用可能ではなく、またタッチペンの情報も得たかったために、dsmファイルから入力内容を読み込むスクリプトを書きました。整理したものを用意しましたので、参考までにおいておきます。

smvなどのバイナリファイルを読む場合は、file:read で文字列として読み込んだあと、string.byte で数値化することになると思います。Luaの仕様上、32bit数値に対してビット演算相当の演算をおこなう場合はLua Bit Operations Moduleを利用する必要があります(2009年10月末時点で、各エミュレータの最新のLuaエンジンにはLuaBitOpが内包されています)。

AviUtlからの読み込み

こちらもサンプルを用意しました。TODOで示された箇所を適切に変更するだけで利用できます。

おまけで縁ありの文字出力をおこなう簡易関数もついています。プラグインが提供する関数は同梱されているドキュメントやサンプルを見ればわかるので、とくに説明しません。

蒼月編集当時、なぜか aviutl.draw_text が空白文字に対して . を描画してしまう現象がありました。プラグインはAviUtl側の機能に処理を任せているだけなのでおそらく本体の不具合なのですが、解消方法はよくわかりません。

ところで、エミュレータによっては最後に以下の行を足した方がよいかもしれません。

while true do emu.frameadvance() end

まとめ

スクリプトを用いるので一見複雑そうですが、読み解いてしまえば処理の内容は非常に簡単なものです。この手法を用いたおもしろい動画が出てくる可能性に期待しています。ぜひ、利用してみてください。

最後に、この方法を一番最初に試みた、ロックマン2のTAS動画を編集したピロ彦(@pirohiko)先生に感謝します*2

追伸

  • VisualBoyAdvance ではエミュレータの実装が不十分ゆえ、AVI出力を行った際に同じフレームが重複して出力される場面があります。このため、Luaでフレーム毎のデータを書き出して処理しようとすると、AVIファイルとタイミングが合わない問題がありました。AVI出力の仕様は変わっていませんが、スクリプト側でタイミング差を埋めるコードを記述したので、現在はVBAでも特に複雑な改変をせず利用いただけるはずです。 (2011-08-28)(参考:VBAでのLua利用の実例)。

*1:2種類ありますが、違いは出力されるファイルの形式のみです。dofile版は意外にも読み込み時の速度が早いですが、出力容量が大きくなってしまいますので、空白区切り版を用いる方が無難かとは思います。ファイルサイズなどの効率を気にするのであれば、バイナリでの出力を行うべきかもしれませんが、32bit数値の扱いなどが面倒なのでやめておきます。

*2:厳密には、RAMの値の出力はFinalFighter先生が改造したFCEUからおこなったらしいので、多少アプローチが異なります。