見出し画像

「時空運送」の時間移動プログラミング

先日「時空運送」というパズルゲームをリリースしました。時空運送は倉庫番に時間移動要素を盛り込んだパズルゲームで、プレイヤーや荷物を過去や未来へ移動させることができる時間移動ポータルを使って解く、新しいゲームシステムを実装しています。大変光栄なことに、Google Play Indie Games Festival 2021でトップ3という賞もいただきました

この記事では「時空運送」でどのようにして時間移動のギミックを実装したのかを書こうと思うのですが、この話をするには時間移動のギミックがどんなものかを知っておいてもらう必要があるので、ひとまずGoogle Playで公開しているゲームを遊んでいただくか、PC向けにunityroomで公開している旧バージョンを遊んでいただくか、もしくはIGFで行ったプレゼン動画をご覧いただければと思います。

あと前提として「時空運送」はUnity 2020.3で開発しています。

アイデア

肝は、盤面の状態自体ではなく、プレイヤーの行動に注目することです。過去改変が起きうるこのゲームでは盤面の状態は非常に流動的に変化します。しかし一度プレイヤーが起こした行動は確定的な事実となり、過去改変によって消えることはありません。盤面の状態が不安定であるという前提に立って、プレイヤーの行動だけを丁寧に記録し、それによる盤面の変化を追いかけるというのが基本的なアイデアです。

プレイヤーが盤面に対して起こすあらゆる変化を記録すれば、時間移動が絡んでも時間軸に整合性を持たせることができます。例えばプレイヤーが過去に移動したとき、移動元の時刻に「プレイヤーの消滅」というアクションを、移動先の時刻に「プレイヤーの出現」というアクションを記録すればいいわけです。あとは新たに記録されたアクションに基づき盤面の状態を更新していきます。

設計

まずは登場人物紹介です。

ある瞬間の盤面の状態を保持するBoardStateクラスを定義します。表現方法はなんでもかまいません。

画像1

プレイヤーが行動すると、盤面の状態は変わります。このとき、盤面にどんな変更が加わったかをBoardActionというオブジェクトで表現します。BoardActionは関数のように機能し、以前の状態のBoardStateを入力すると、アクションが適用されたBoardStateを返す機能を持ちます。BoardAction以外の方法では盤面の状態を変化させないようにします。

時空運送で言うと「プレイヤー移動」アクションなどがあり、プレイヤーの移動元・移動先の座標を保持しています。

画像2

そして、時間軸上のどのステップでどんなアクションが起きたかを記録しておきます。あるステップのBoardStateと、そのステップに起こされたBoardActionのリストをひとまとめにしたBoardFrameクラスを定義します。

画像3

さらにBoardFrameのリストを保持することで、ひとつの時間軸上の盤面の遷移を表現します。こうすると、盤面の初期状態のBoardStateとそれぞれの時刻に記録されたBoardActionによって任意の時刻のBoardStateを一意に決定することができます。こうすれば、過去に対して変化が起きた場合それは当然BoardActionとして記録されるため、改変後の盤面の状態を簡単に更新することができます。

画像4


これで登場人物がそろいました。

具体例として、過去改変が起きたときの処理について考えてみましょう。時空運送の例で、荷物を過去に送った場合を考えます。このとき送付元のBoardFrameに「箱の消滅」アクションを、送付先のBoardFrameに「箱の出現」アクションを記録します。

画像5

そうすると、送付先の時刻(過去)よりも未来の状態は変化しうるので、BoardStateは更新される必要があります。

画像6

仕上げに、プレイヤーの観測している現在時刻までのBoardStateを更新して終了です。

画像7

このとき、場合によっては矛盾が生じる(新しく記録されたアクションによって生じた変化でもとからあったアクションが実行できなくなる)場合があるため、そのときはタイムパラドックスが起きたとみなし盤面の状態をロールバックする必要があります。また、実際にはアンドゥ機能、BoardActionの適用順なども絡んでくるのでもう少し複雑です。

実装上の工夫

Unity世界と盤面のロジックが分離できるようにしています。盤面のロジック自体はUnityに依存する必要がないため、Assembly Definition Filesでアセンブリごとロジックを分離しているうえ、UnityEngine.dllに対する参照すら無効化しています(なので非Unityの.NET環境でも動かせます)。Unity側は盤面状態の変化を観測し、それに合ったGameObjectを作成して表示しているだけになります。盤面の状態がどんなにハチャメチャに変化しようが、Unity側は慎ましくそれを追いかければよいので、いろいろと問題がシンプルになります。

おわり

時空運送では倉庫番という題材でゲームを作りましたが、時間移動システム自体にはそれなりに汎用性がありそうなので、何か別の題材でも作れるんじゃないかな~と思っています。ついでに、最近はこういう時間軸歪曲系(と僕が勝手に呼んでいる)ゲームが増えてきているような気がしているので、こういうジャンルがもっと流行ってくれたらうれしいな、という気持ちでこんな記事を書いてみました。なにかの役にたてば幸いです。

この記事が気に入ったらサポートをしてみませんか?