最初に宣伝から
現在、開発中のゲーム「Dull Things No Life」ですが、ようやく完成の目処が立ってきたので今回の記事からそのゲーム内容について紹介をしていきたいと思います。
「Dull Things No Life」はいわゆるラン系ゲームというやつで、操作キャラクターが自動で走っているところをタップ操作で左右に移動させ、迫り来る障害物をかわしてゴールを目指して進んでいく、といった内容のゲームとなっています。
このゲームの操作キャラクターは赤色のMotorBikeで、画面の左右をタップすることでMotorBikeも左右に移動します。
またMotorBikeの走る経路はトンネルのように壁で囲まれていますが、上下左右どこの壁にも移動することが出来ます。
こんな感じ、大きな柱が走路を塞いでいる場合は左右の壁に移動してそれを避ける事ができます。
画面の左右をタップするだけで難しい操作はありません。まぁ、類似のアプリは多々あるので「ああ、よくあるやつね」と思って頂いて結構です。
ただ、「Dull Things No Life」ではMotorBikeは左右移動だけでなく特殊な動作で障害物を避けることが出来るのですが、宣伝が長くなるのでこれについてはまた次回紹介したいと思います。
で、本題
UnityではObjectを移動させる処理は様々ありますが、よく使われる手法としてUpdate内で移動速度×前フレームからの時間差分=移動量を計算してObjectのtransformに反映する、という方法があります。
このとき前フレームからの時間差分にはTime.deltaTimeを使用しますが、このdeltaTimeはTime.timeScaleに影響を受けます。
Time.timeScaleはUnity内(ゲーム内)の時間の定義しているので、Time.timeScale = 1であれば現実の時間と同じ時間、この値を小さくすると現実の時間 > ゲーム内の時間となり、例えばtimeScale = 0.5とすると、deltaTimeが1.0(sec)だとしても現実の時間は2.0(sec)経過していることになります。
通常、timeScaleを意図的に変更しない限りは(timeScale = 1のままであれば)、現実の時間=ゲーム内の時間となるのでdeltaTimeを使用しても現実の時間と食い違うことはなく特に問題ない、と思っていました。
だが、しかし・・・
Mobile端末でゲームを動かす場合、PCと比べると演算性能や描画性能は劣るので、PC上のUnityEditorで確認した動作よりももっさりした(処理に時間が掛かる)動作になるのは仕方ないことだと思います。
ただ、どうもUnity内部にて(私の推測だけど)、処理に時間が掛かる分だけゲーム内のtimeScaleを調整しているようで、deltaTimeと現実の時間が一致しない状態になります。
つまり、こちらはtimeScaleを変更していないのに、現実の時間 > ゲーム内の時間、みたいな事象になるということ。
まぁ、現実の時間とリンクしない、ゲーム内の時間だけで完結するようなゲームの場合、それでも問題ないのでしょうが、音楽と同期させてObjectを動かしたい場合はちょっと問題です。
何が問題なのか
AudioClipによるSEやBGMの再生はtimeScaleの影響を受けません。つまり、かならず現実の時間と同じ時間だけ経過して音声を再生していきます。一方、Objectの方はtimeScaleの影響を受けてゲーム内の時間で動作するとなると、例えば、下図のような典型的な音ゲーを作る場合
こんな感じの仕様となるので、各ノーツはそれに対応する音が再生されるより前に画面に表示する必要があります。
しかし、移動処理でdeltaTimeを使用していると現実の時間 > ゲーム内の時間なのでノーツ移動が間に合いません。
これはゲームとして致命的です。しかもUnity内部にて処理速度に応じて勝手に調整されているので(あくまで私の推測)、こちら側(製作者側)で遅くなるのを見越して移動速度を調整するような対処も行う事ができません。
なのでこんなときは・・・
timeScaleに影響ない前フレームからの時間差分を得る場合にはTime.unscaledDeltaTimeを使用します。
deltaTimeとunscaledDeltaTimeの違いはtimeScaleの影響ありなしだけなので、通常時はdeltaTime=unscaledDeltaTime=現実の時間となっていますが、上記のように音楽と同期させるために必ず(勝手にtimeScaleが変わっても)現実の時間とリンクが必要な場合はunscaledDeltaTimeを使用して処理を組み込む必要があります。
ちなみに、Objectを移動させるのにiTweenやDoTweenを使っていても特にオプションを指定してない限りは同様の現象になります。また、Animatorでも同様です。
以下にtimeScaleを考慮しない場合のオプションを記載します。
- iTweenの場合:ignoretimescaleプロパティをtrueに設定する
Hashtable ht = new Hashtable ();
ht.Add ("ignoretimescale", true);
- doTweenの場合:Updateオプションをtrueに設定する
DOTween.To( ~ ).SetUpdate( true );
- Animatorの場合:updateModeプロパティにUnscaledTimeを設定する
animator.updateMode = AnimatorUpdateMode.UnscaledTime;
注意事項
前述のようにunscaledDeltaTimeはtimeScaleを考慮しないので、例えばPause処理などでtimeScale=0としていても関係なく動作します。
また、オブジェクトが一旦非Active状態になると、その間の差分時間が加算されていくのでActive状態に戻ってUpdateでunscaledDeltaTimeを取得すると、最初フレームでunscaledDeltaTime = 非Active状態だった時間、が返ってきて、一気に時間が経過したような動作になるようです。
これについては以下のkan.kikuchiさんのブログに詳しく書かれています。