自己紹介
2008年度、数値班に所属していたMachiaWorxと申します。
なんか面白そうなアドベントカレンダーがあったのでせっかくだから参加しました。
(コミケとかで隣のサークルさんとSTGの実装について話するとやっぱ同じことするよね・・・って話題になるんだけど、そこらへん共有されてないので書き記しとくのも目的です)
概要
Unity上でクラシックなゲームを実装するための方法について連携します。
クラシックなゲーム=更新するデルタタイムの長さを固定に近づけるというのが大前提になります。(更新状況により正確には固定にならんケースもあるので)
それに付随するように各機能を実装するという方針で話を進めます。
余談ですが、上記状況はどちらかというとVSyncを前提としてゲームを作るという形を取ってたからだとは思うんですけどねえ・・・。
簡単に言うとUnityだとモダンな作りになっていて、クラシックなゲームっぽく調整しようとすると無理が出てきます。
よって、Unity上で実装を工夫していくことでクラシックなゲームっぽい動きを再現する形ですね。
どちらかというとシングルスレッドっぽく動かすため、マルチスレッドで動くUnity本来の動きと比較して性能面の劣化は免れないことに注意してください。
(実際のところ2000年代のDirectXを直接叩いてたゲームとかは割とシングルスレッドでの制御になってたんじゃないかと。VSYNCすると勝手に120FPSで動作する時どうしたらいいかと悩んでた世代とか・・・)
得られる知見
- Unityにおける動作とゲームエンジン使わないケースでの処理の違い
- 各処理の自前実装方法
割とUnityゴリゴリ使ってる人向けになっちゃうかもしれませんが、いざとなったら自分で実装できちゃうよ!みたいなのが分かるので、エンジン使う前に色々調べてる人向けともいえるかも。
Unityのバージョンは、Unity 2018.2.18f1 を利用してますが、
コミケが終わったら更新予定です。
基本的なアプローチ
フレーム制御を固定化する
どういう事かと言うと、「Update処理に対して各関数を動かさない」。
- 神GameObjectを作成(各Objectに動作命令を出すためのGameObject)
- 神Objectでは経過フレーム管理を行い、0.01666666秒ごとに処理を行う形にする
- 各オブジェクトは神Objectに関連付けて動作を行うようにする。このためUpdate処理では処理を実行しない。(別の関数で代替する)
影響は以下の通り。
- Update関数を利用することを前提とした処理全般が実質利用不可能となる。(利用してもいいけど実際の時間軸と神Objectの時間管理にズレがある場合、違和感が出たり後述の当たり判定にズレが出る)
ただこの方法の利点は、「神Objectから各Objectに更新処理を行うように制御をかける」ため、「リプレイを実装可能」ということ。
通常だとUpdate処理がマルチスレッドで走ることになるため、リプレイの実装などということがそもそも不可能ですが、今回の方法だと神Objectをトリガーとして各処理を動かすため、処理タイミングの制御がしやすくなります。
よって、アクションゲーム・シューティングゲームにおけるリプレイの実装もやりやすくなります。(ランダムシードの固定化、デモシーンのフレーム制御実装等は必要かと思いますが)
ちなみに、FixedUpdate関数で処理するのも問題ないとは思いますが、FixedUpdate自体の動作が信用ならんときがあるので、頼り切りにするのもどうかと思います。(FixedUpdate自体が遅延するケースとか。ゲームの内容としては問題ないとおもうんですが、特定アセットでUpdateでの処理を前提にした場合動作にズレが出ます)
Update内のフレーム制御・FixedUpdate・VSync対応みたいな形で場合分けできればベストかと。
(自分の場合はVSyncを切るの前提でUpdate関数でフレーム管理するのをデフォルトにしてます。Unityのアセットを使った動画撮影時に影響が出にくいため)
Collider同士の衝突を当たり判定の検出として使いたくない
表題の通りです。
具体的には、判定の検出にCollider同士の衝突を使わないようにしたいです。
理由は、神Objectのフレーム管理との同期が取れなくて、意図しないタイミングで当たり判定の検出が行われるのを避けるため。
とは言ってもいきなり自前当たり判定検出も難しいと思うので、UnityのCollider機能を利用した判定検出・完全に自前判定検出という形でアプローチを書いていきます。
初期の実装(Unityの機能を使った判定検出)
- OnCollisionEnterなどで検出した際は、神Objectが処理を行うまで待つ形にし、判定フラグだけを立てておく。神Objectで更新関数を処理したタイミングでフラグ検出を行い、立っていたらダメージ判定処理を行う
この方法の問題点は、神Objectと非同期で処理を行うため、あるタイミングでは判定をすり抜ける現象が発生したり、処理タイミングが毎回異なる形になります。
それでも違和感はだいぶ減ると思うので、判定検出の実装が難しそうならこの方法をおすすめします。
(あと基本的には1/60秒より早いタイミングで衝突判定が発生すると思うのでこのやり方で問題でるケースは割と少ないはず)
後期の実装(完全に自前実装)
- 神Object経由で更新処理を行う際、当たり判定を検出する
すなわち、当たり判定の検出処理を自分で作成する
ここまでする場合、もはやUnityで処理をする意義は薄いといえます。
レンダリングだけUnityでやってるような状況とも言えてしまうので。
一番単純なのは、座標を軸にして矩形で判定取ってしまう方法です。
当たり判定でググればいっぱい出てきます。
3Dの判定の場合直方体の判定検出等ステップアップで考えれば問題はないはず。
自分の場合矩形+奥行きの判定を複数入力できるようにしてます。
計算を省力化できるなら矩形だけでいいかもしれません。
ただ、Colliderを使った処理のうちRayCast関数は任意のタイミングで呼び出すことができるので、判定検出の一環でRayCastを使って敵弾等の判定には自前での処理、という形で役割分担が可能です。
(勿論上記の場合、Collider+自前当たり判定の設定が必要になりますが)
あと、判定すり抜けは構造上起きてしまうので、自前でやるなら前フレームの判定と比較する等も仕込むことが可能です(その分重くなるけど。あとそこまでの精度が必要かという部分は要検討)
UnityのAnimation関数をなんとか使いたい
上記の通り、Update関数を前提として動く処理全般は利用不可能になるため、UnityにおけるAnimationファイルの処理が利用不可能になるのが現状。
ただ、それでもなんとかしたい場合、フレーム制御に紐づけての処理は可能。
上記URLの方法が一番手っ取り早いんですが、UnityのAnimationをいじらなきいけないのでちょい面倒です。
で、内部でPlayableAPIを利用した、SimpleAnimationという公式アセットが存在するのでそれ使うという手があります。
SimpleAnimationの内部をいじくってフレーム制御が可能になります。
keepStoppedPlayablesConnected→false
にして対応、のはず。実際色々いじって動くようになりました。
(ただパフォーマンスに影響でるかもしれないので注意)
他に確か未定義のパラメータがあるから正常に動作しないって話だとおもうので、これを定義すればとりあえず動く形にはなります。
(あとパラメータがあったけど忘れたな・・・)
それでも動かない場合、CommandBufferで描画する・Animationを垂れ流し(LoopをOFFにする)・IKで制御、のいずれかになるかと思います。(特に3DのAnimationを利用する場合、フレーム制御と当たり判定のズレが発生する可能性があるので、IK制御にせざるを得ないんじゃないかと)
もうちょっと突っ込んだTips
Tween関数を自作
自作は可能。
GameObject使っていいのであれば位置の制御は可能なので、簡単にTweenオブジェクトを作ることが可能。
ただし動作はフレーム制御に依存することになるので注意。
シェイク処理
ダメージ時にシェイクする処理ね。
これはランダムの座標変更+一定の倍率で収束する、という処理を書けばOK。
上記と同じでフレーム制御になるのに注意。
動き全般でIKを導入する
IK=InverseKinematic
これならGameObjectに追従する形でキャラを制御でき、更に多関節キャラの制御も可能になるので、Animationを使わなくてもキャラの動きを制御できる。
FastIKやFinalIKが存在。(自分はFastIKを利用してる
GameObjectを除外する
ここまではまだやってないけど、Unityの機能を使わないのであれば、GameObject自体を削除することも可能。
元々GameObjectには自動で色々なことを行ってくれるように機能が仕込まれてるんだけど、これらを全部利用しないのであればGameObjectの利用を減らしてキャラクターの情報を生成・破棄だけを担当させる等は可能。
描画もCommandBuffersを利用すれば3D空間での描画も可能になるため、それほど困ることはなくなる。
ただ、削除するかはゲームの構造に依存する。(Raycastを使わない、Sceneで直接調整を行わない等であれば有用)
終わりに
すっげー駆け足で書いてみました。
意外と共有されない内容なので、知識を得るきっかけになれば幸いです。
参考資料
https://madewithunity.jp/stories/aka-to-blue/
アカとブルー(クラシックなルールで実装している例)
https://github.com/Unity-Technologies/SimpleAnimation
SimpleAnimation(アセット)