TIC-80:Adding a replay system

2020年6月14日

fantasy console gamedev Lua TIC-80

t f B! P L

replay system

TIC-80で小さなshoot'em upを作ったのでそのメモ。小さなゲームですが、開発後半でgifアニメを取ったりするのに、動画を取りやすくするために、リプレイのシステムを追加しました。リプレイをサポートすることで、良い面をたくさん受けることができます。リプレイの対応はdeterministicアプローチで行いました。

deterministic system

deterministicモデルは、同じ状態から同じ入力を与えると同じoutputを得ることができます。ゲーム作る場合で注意する点は、ランダム、スレッドなどによる実行の順番、浮動小数点の計算などです。TIC-80上で再生するだけなので、ランダムと、デルタタイムの対応を行うと、問題なく動作させることができました。ゲームエンジンによっては、physicsがdeterministicでない場合などあるので、使用しているシステムに応じて注意が必要です。ランダムはエフェクトなどの表示関連とゲームのロジックの乱数で系列を分けたりすると、リプレイ再生が安定しますが、Luaだとmath.randomしかないので、そのままだと対応することができません。

入力データ

まずキーやマウスなどの入力データを管理する部分を追加しました。個別にbtnなどを記述していたのですが、ゲームループの頭のほうで、ゲーム用の入力データを取得する部分を追加して、ゲーム中はそこから入力を取得するように修正しました。これによって、移動は矢印キーだけだったのが、WASDキーでの移動も簡単に追加できました。カウンタを追加して、同一データはカウントアップするだけにしましたが、あまり使用メモリに関して、効率的にはできていません。
Button_Up=1<<0
Button_Down=1<<1
Button_Left=1<<2
Button_Right=1<<3
Button_Shot=1<<4
Button_Dash=1<<5
function IData:upd(dt)
  local m=0
  if btn(0) or key(KEY.Wthen m=m|Button_Up end
  if btn(1) or key(KEY.Sthen m=m|Button_Down end
  if btn(2) or key(KEY.Athen m=m|Button_Left end
  if btn(3) or key(KEY.Dthen m=m|Button_Right end
  local mx,my,ml,_,mr=mouse()
  if btn(4) or ml then m=m|Button_Shot end
  if btn(5) or mr then m=m|Button_Dash end
  local mpos=Vec2.new(mx,my)
  self.mask=m
  self.mpos=mpos
  self.dt=dt
  self.num=self.num+1
end

ランダム

乱数は、Luaの標準ライブラリを使用していました。これだと、random seedを設定する関数はありますが、現在のrandom seedを取得することはできませんでした。なので、ゲーム開始時に、乱数を生成して、それをrandom seedに設定して、それをリプレイ再生用に保持するようにしました。自分で乱数を生成するものを作成したほうが効率的かもしれません。

メモリ上に保持

データをストレージに保存のやりかた分かってないので、メモリ上のデータを再生するだけなのを作りました。メモリ上に残っている直前のプレイが再生できるだけです。やり方がわかれば、ストレージにsave/loadすれば、もっと便利に利用することができます。

デルタタイム

ゲームは毎フレームデルタタイムを各オブジェクトに渡して更新をおこなっていたので
、デルタタイムも記録、再生するようにしました。

良いこと

不具がある場合、リプレイで再現できるものは、現象の再現などに使えます。また、動画などをとる場合、プレイしながら撮るのが難しいですが、いったんプレイだけ行ってあとから、良いところだけをキャプチャで切り出したりできます。この小さい規模でリプレイを対応しているのあまりないかもしれません。deterministicを維持するためには、初期の段階で対応しておくのがよいと思います。

QooQ