ユニティちゃんの2Dアクション:改 その3-2 左右の移動解説② 更新処理をちょっと学ぼう

ども、ゴコーです。

前回は主にコード書くことについてでしたが、今回は Unity の内部について見ていきましょう。

なんと、今回も予想外に長文になったので今回は

Update と FixedUpdate の違いについて

だけやっていきます。

スポンサーリンク

Update について

Update やフレームという概念については、高橋さんが書かれたの以下のページが最強なので、僕に書くことはありません(えー)

驚きの丸投げっぷりですが、本当のことなのでどうしようもありません。この後の文章はこの記事を読んでいる前提で書きますのでお願いします。

さすがに丸投げしておわり、というのは忍びないので、代わりに Unity における更新には欠かせないデルタタイム(Time.deltaTime)という概念について見ていきたいと思います。

デルタタイムについて

上記のページを見れば分かる様に、ゲーム(Unity)の画面が動くのは 時間進行+計算(Update)、描画(Rendering) を繰り返しているからです。
本来、計算や描画の量が多かったり、内容が複雑だったりして起こるのは「ゲーム進行の全てがスローモーションになる」ことのはずです。そして、ゲームのジャンルによってはそうした遅延によってゲームプレイの難易度が変動してしまうこともありえます。

例えば、シューティングゲームの大量に弾幕が出てくるシーンで、マシンの処理能力が貧弱だと計算や描画に時間がかかり、プレーヤーの動きをはじめとして、敵や敵の弾の移動もゆっくりになってしまう、ということが起こります。
しかし、本来難しいはずのシーンが、全てがゆっくりになった結果逆に簡単になった、なんてことが起こるのは残念すぎます。(最近やったアプリにそんなのがあったので分かりますがw)

…そんな事態を防ぐために Unity が用意した手段の一つがデルタタイムという訳です。

デルタタイムは

直前のフレームを完了するのに要した、秒単位での時間
The time in seconds it took to complete the last frame

Unity スクリプトリファレンス – http://docs.unity3d.com/ScriptReference/Time-deltaTime.html

とあります。言い換えると、前の更新処理を始めた時からの経過時間とも言えます。

では、デルタタイムを使わない2つのケースについて考えてみましょう。

ケースAでは、毎フレーム足し算を1000回しています。計算の所要時間は仮で0.1秒としましょう。
ケースBでは、毎フレームキャラクターを1000体呼び出し、1000体消しています。計算の所要時間は仮で0.25秒としましょう。

今回は計算の時間のみを考慮し、描画などその他の時間は一切かかっていないということにします。できるだけシンプルに考えましょう。
つまり、計算の所要時間 = 次のフレームまでの時間ということで、

ケースAの場合、0.1秒間隔で処理を繰り返す
ケースBの場合、0.25秒間隔で処理を繰り返す

ということになります。

まあ、この計算くらいでこんなに時間かかるわけありませんけど、そこはスルーしてください。

では、例として以下のコードを元に移動させてみましょう。
更新の度に1ずつ右に移動していくというコードです。

ここまでは良いですかね?

では実際に一秒間走る処理を図にしてみましょう。

delta2

こんな感じです。

移動速度は等しく1ですが、
ケースAの場合は1秒間に 1 ÷ 0.1 = 10 で 10回実行されるので移動距離は 1 × 10 で10になります。
ケースBの場合、1秒間に 1 ÷ 0.25 = 4 で 4回実行されるので移動距離は 1 × 4 で4になります。

かなり違いますね。
このように、処理にかかる時間によって起こってくる移動距離などの結果が異なるので、何らかの対策を施さないと「スローモーション」になるんですね。

さて、では次にデルタタイムを処理に挟んだ場合です。

今回使うコードはこんな感じです。

掛けている数字が変わっていますが、これはケースAで起こる結果を同じにするためです。
ではこの場合のケースA、Bも図に表してみましょう。

delta4

今回の場合、実行回数は変わっていませんが、移動速度に変化があります。

の実際の移動量である 10 * Time.deltaTime に注目してみましょう。

ケースAの場合、

実行にかかっている時間は毎フレーム 0.1 秒 ですので 10 * 0.1 で 移動量は1、
1秒間に10回繰り返すので 1 × 10 で移動距離 10 になります。

ケースBの場合、
実行にかかっている時間は毎フレーム 0.25 秒ですので、10 * 0.25 で移動量は 2.5 、
1秒間に4 回繰り返すので 2.5 × 4 で移動距離 10 になります。

結果として、実行回数は違いますが、同じ時間に同じだけの距離を移動できています。

このように、 Time.deltaTime は実行速度の差を吸収して良い感じの数字を返してくれるので、デルタタイムを掛け合わせることによって、ある程度はプラットフォームや機種の実行速度に左右されずに思った通りの速度を表現できるんです。便利ですね!

ちなみに、今回は計算量とそれに付随する実行速度を勝手に設定していますが、普通はそんなことできません。

Application.targetFrameRate から大体のフレームレートを設定することはできますが、そのフレームレートを絶対に維持できる訳では無く、あくまで「そのくらいを目標にしてね」というのを Unity のシステムに指示する感じになります。素直に Time.deltaTime を使用しましょう。

参考:http://docs.unity3d.com/ScriptReference/Application-targetFrameRate.html

他にも注意点はありますが、今回はこれくらいにしておきましょう。

FixedUpdate について

上記リンクの他にもいくつか補足しておきます。

①処理の間隔

まず、FixedUpdate が一定の間隔で呼ばれる一方で、Update 更新の間隔は先ほどから書いているように一定では無く、しかも Update の方が更新回数は多くなります。

以下のような感じです。

MonoCycle3

FixedUpdate は等間隔で比較的少なく、 Update は不等間隔で比較的多いです。
これは正確な図ではないですが、後でこの図を使うので今回のところは処理はこんな感じなんだと思っておいてください。

②FixedUpdate に書くべき内容について

Unity では、FixedUpdate が呼ばれた直後に物理演算の更新が行われます。

Unity の実際の処理の流れを示した図が以下のもので、

MonoCycle

参考元: http://docs.unity3d.com/Manual/ExecutionOrder.html

MonoCycle2

このように、同じループ内のかなり直前に呼ばれています。

これは言い換えれば、FixedUpdate に書いた内容は必ず物理演算の結果に反映されるということです。
そんなわけで、 Rigidbody(2D)を使う場合は FixedUpdate の中に書くべきです。
これについてはおまけでもうちょっとだけ触れます。

③おまけ:Update に書くべきでは無い内容について

Rigidbody(2D) を使っていて、動きがガクガクするという現象を見たことはありませんか?

①で提示した図のように処理が行われたとして、さらに Update の中で Rigidbody(2D)を使ってしまったとしましょう。

こんな感じです。

そうすると、加わる力はどうなるでしょうか?その力の単位を仮に F(orce) として図に表してみます。

MonoCycle4

図の中の表現で良いのが思いつきませんでしたorz
とにかく、 FixedUpdate の中で計算していれば、加えた分の力がそのまま物理演算に反映されるので毎回同じ速度で移動できますが、Update の中で計算する場合は反映されるまでに複数回速度が適用されることもあるので、反映時の速度が安定しません。

その結果ガクガクしたような動きになってしまうんですね。

だから、Update に物理挙動の操作に関するものは入れると意図しない結果になるかもしれません。注意しましょう。

まとめ

物理挙動に関する処理はFixedUpdate に、そのほかは Update に書きましょう!
移動速度などを安定させたいなら Time.deltaTime で決まり!

そんなルールに則った結果が、入力は Update 内で取り、 FixedUpdate 内でその入力を元に移動するというコードだったということです。

さて、それなりに長くなってしまいましたが、今回はここまでにしておきます。

次回は AddForce などの使い分けを考えましょう!

では、また。

Comments

comments

スポンサーリンク
336*280px