🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2210閲覧

Lerpの戻り値を次の開始位置として更新するLerpの処理

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2019/09/14 17:40

前提・実現したいこと

下記はUnityの書籍のコードの一部を抜粋したものですが
(Unity標準アセットのSmoothFollow.csを改造したものらしく、ほぼSmoothFollow.csのコードでした)、
このLerpにおける処理に関して質問です。
Lerpの戻り値を開始位置として更新するLerpの処理とはどういうものなのか、
また、Time.deltaTimeを掛けている理由やコード中のheightDampingについて
ご教示お願いします。

//高さに変化を加えるときにどのくらい遅延させるか。 [SerializeField] private float heightDamping; void Update() { var currentHeight = transform.position.y; var wantedHeight = target.position.y + height; currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime); transform.position = new Vector3(transform.position.x ,currentHeight , transform.position.z); }

試したこと

簡単な数に置き換えて試してみました。

float i = 0; // Update is called once per frame void LateUpdate() { float j = 24; i = Mathf.Lerp(i, j, 0.5f); Debug.Log(i); }

出力結果。

12 18 21 22.5 23.25 …

出力結果から、これはイージング処理になるのではないかと思いました。
続いて、Time.deltaTimeで試してみました。

float i = 0; float t = 0; // Update is called once per frame void LateUpdate() { float j = 24; i = Mathf.Lerp(i, j, Time.deltaTime * 5); t += Time.deltaTime; Debug.Log(i); Debug.Log(t); if(t>5){ Debug.Break(); } }

5秒後に終点に収束になるのかと予想したのですが、出力結果はそうではなさそうでした。
t==2.972162の時点で、i==23.99999の値を取っていました。
Time.deltaTimeを使っている理由がやはりわかりません。

以上を踏まえて質問です。

・質問1。
「Lerpの戻り値を開始位置として更新するLerpの処理」は、イージングになるという認識で合っていますか?
また、それは必ずEase outのイージングになるということでしょうか?
例外や間違いがあればご指摘お願いします。

・質問2。
Time.deltaTimeを使っている理由や効果は何ですか?
指定時間でLerpが終点に収束したりするのでしょうか?
書籍のコードだけならば、何かの間違い、誤植かなと思ったのですが、
Unity公式アセットでもTime.deltaTimeを書いているので、何かしらの意味はあるのではないかと思っています。

・質問3。
heightDampingは、どういう役割を果たしていますか?
書籍のコメントの「遅延させるか」というキーワードが引っかかって、気になったのですが、
Lerpは初めから開始されているので(出遅れてLerpが動いているわけではないと思うので)、
遅延の意味がよくわからないと思いました。
イージングの調整値で合っていますか?
また、heightDamping * Time.deltaTimeが1だったら、一気に終点に来てしまうと思うので、
heightDamping * Time.deltaTime < 1 を満たすようなheightDampingの値を設定すればよいという認識で合っていますか?
(heightDamping自体は1未満であろうと1以上であろうと特に問題ないという認識で合っていますか?)
また、試したことで、heightDampingの時間で終点に辿り着かないという認識で合っていますか?
それともheightDampingは何か時間に作用するものでしょうか?

・質問4。
終点に収束するまでは、Lerpを動かして、収束、もしくは超えたら、
終点に設定するみたいなコードを書きたいのですが、
下記「if(i < j)」の条件だと、ずっとこのif文に引っ掛かるので、
終点への調整処理みたいなのは、どういった書き方をすればよいですか?

float j = 24; if(i < j){ i = Mathf.Lerp(i, j, Time.deltaTime * 5); Debug.Log(i); }else{ i = 24; Debug.Log("Break"); Debug.Break(); }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

質問1について
はい、イーズアウトの一種と言えそうですね。毎フレーム「残りの距離の0.0???...倍だけ目的地に近づく」ということを繰り返すわけですので、回数を重ねるほど残りの距離が短くなり、残りの距離に比例して1回の移動距離も減っていくのでどんどん減速していくでしょう。

質問2について
deltaTimeを組み込んでいるのは、フレームレートが変化しても動きをなるべく同じように見せようとしてのことかと思います。
たとえば30FPSの状況では毎秒30回のペースで目的地に近づいていくでしょうが、60FPSだと毎秒60回のペースになりますので、近づく割合が一定だと60FPSでは30FPSよりも素早く近づいていくように見えるでしょう。
そこで割合にdeltaTimeをかけると、60FPSの時のdeltaTimeは30FPSの時の半分ですから、1回の移動量も半分になってちょうどいい...という理屈かと思います。

質問3について
これについてはご質問者さんのおっしゃる「イージングの調整値」という認識で問題なさそうです。おそらく著者の方もそういう認識だろうと思いますが、「遅延」という言葉を使ってしまったのはまずかったかもしれませんね。おっしゃるように「動きを開始する時刻を遅延させる」ともとれてしまうような表現になっています。
heightDampingに関するその他の認識もその通りかと思います。heightDampingが表す値には時間の意味合いは薄く、もっと大雑把な調整値という認識でいいんじゃないでしょうか(数学的に考えると永遠に目的地に到達しないはずですしね...)。

質問4について
数学的にはいつまで待ってもi >= jにはならないでしょうから、条件としてはApproximatelyを使ったりMathf.Abs(j - i) < 0.001fみたいにあそびを設ければいいんじゃないでしょうか?

質問2について補足
移動量調整としてdeltaTimeをかけるだけでいいのか気になるかもしれません。確かに数学的に考えると不十分かもしれませんが、実用上は問題ないと思います。
下記のようなスクリプトを用意して...

C#

1using System; 2using UnityEngine; 3 4public class LerpAndDeltaTime : MonoBehaviour 5{ 6 public enum Mode 7 { 8 Coarse, 9 Fine 10 } 11 12 [SerializeField] private float deltaTime = 1.0f / 60.0f; 13 [SerializeField] private float duration = 2.0f; 14 [SerializeField] private Mode mode; 15 [SerializeField] private GameObject dotPrefab; 16 17 private float time; 18 19 private void Start() 20 { 21 var fineModeFactor = Math.Log(Math.Pow(1.0 - (1.0 / 60.0), 60.0)); 22 var y = 0.0f; 23 while (this.time < this.duration) 24 { 25 switch (this.mode) 26 { 27 case Mode.Coarse: 28 // 単純なdeltaTimeによる調整 29 y = Mathf.Lerp(y, 1.0f, this.deltaTime); 30 break; 31 case Mode.Fine: 32 // 60FPSのときのカーブに近づくよう補正した調整 33 var t = (float)(1.0 - Math.Exp(fineModeFactor * this.deltaTime)); 34 y = Mathf.Lerp(y, 1.0f, t); 35 break; 36 } 37 38 this.time += this.deltaTime; 39 40 Instantiate( 41 this.dotPrefab, 42 this.transform.position + new Vector3(this.time, y, 0.0f), 43 Quaternion.identity, 44 this.transform); 45 } 46 } 47}

フレームレートが変わるとイージングのカーブにどのような変化が生じるか見てみました。
単純にdeltaTimeを使った場合(Coarseモード)ですと、下図のように微妙にカーブが変わってしまいました。とはいえ、10FPSぐらいにまで落ち込んでようやくずれが見えてくる程度ですので、通常は気にする必要はなさそうです。

図1

一方Fineモードではもっとましな調整をしたつもりです(あんな式でいいのか検証不足なので、間違いがあるかもしれません)。

図2

ですがわざわざExpなんかを使ってまで調整精度を上げるだけの価値があるかというと微妙でした。

計算量削減、基準フレームレートとイージング調整値を変更できるように修正

C#

1using System; 2using UnityEngine; 3 4public class LerpAndDeltaTime : MonoBehaviour 5{ 6 public enum Mode 7 { 8 Coarse, 9 Fine 10 } 11 12 [SerializeField] private float deltaTime = 1.0f / 60.0f; 13 [SerializeField] private float duration = 2.0f; 14 [SerializeField] private Mode mode; 15 [SerializeField] private GameObject dotPrefab; 16 [SerializeField] private float damping = 1.0f; 17 [SerializeField] private double referenceFrameRate = 60.0; 18 19 private float time; 20 21 private void Start() 22 { 23 var y = 0.0f; 24 var referenceDeltaTime = 1.0 / this.referenceFrameRate; 25 while (this.time < this.duration) 26 { 27 switch (this.mode) 28 { 29 case Mode.Coarse: 30 // 単純なdeltaTimeによる調整 31 y = Mathf.Lerp(y, 1.0f, this.damping * this.deltaTime); 32 break; 33 case Mode.Fine: 34 // referenceFrameRate FPSのときのカーブに近づくよう補正した調整 35 var t = (float)(1.0 - Math.Pow( 36 1.0 - (this.damping * referenceDeltaTime), 37 this.referenceFrameRate * this.deltaTime)); 38 y = Mathf.Lerp(y, 1.0f, t); 39 break; 40 } 41 42 this.time += this.deltaTime; 43 Instantiate( 44 this.dotPrefab, 45 this.transform.position + new Vector3(this.time, y, 0.0f), 46 Quaternion.identity, 47 this.transform); 48 } 49 } 50}

FixedUpdateの世界の時間の流れと実際の時間の流れの比較

C#

1using UnityEngine; 2 3public class FixedTimeVersusRealTime : MonoBehaviour 4{ 5 private float previousFixedTime; 6 private float previousRealTime; 7 8 private void Start() 9 { 10 this.previousFixedTime = Time.fixedTime; 11 this.previousRealTime = Time.realtimeSinceStartup; 12 } 13 14 private void FixedUpdate() 15 { 16 var fixedTime = Time.fixedTime; 17 var realTime = Time.realtimeSinceStartup; 18 var fixedDeltaTime = fixedTime - this.previousFixedTime; 19 var realDeltaTime = realTime - this.previousRealTime; 20 21 Debug.Log($"Fixed:{fixedTime:F5} Delta:{fixedDeltaTime:F5} Real:{realTime:F5} Delta:{realDeltaTime:F5}"); 22 23 this.previousFixedTime = fixedTime; 24 this.previousRealTime = realTime; 25 } 26}

図

投稿2019/09/15 00:31

編集2019/09/16 05:40
Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/09/15 06:44

ご回答ありがとうございます。 確認させていただきたいのですが、 今まで、Time.deltaTimeを掛けるのは、 Translateなど物体移動のときに使うものとだけ認識していたのですが、 ご回答いただいて気づいたのですが、Translate等に限らず、 毎フレーム処理で、繰り返し呼び出して値を変化させるものには、 Time.deltaTimeを掛けたほうがよいということでしょうか? (30FPSの状況で毎秒30回のペースで処理を呼び出して値を変化させるのと、 60FPSの状況で毎秒60回のペースで処理を呼び出して値を変化させるのとでは、 60FPSでは30FPSよりも早く値が変化してしまうので、 変化する割合にdeltaTimeをかけて、60FPSの時のdeltaTimeは30FPSの時の半分なので、 1回の変化量も半分になって、30FPSと60FPSで変化に差異がなくなるということでしょうか?) Mathf.Expでの検証コードありがとうございます。 Time.daltaTimeの方でも30FPS~240FPSの範囲では問題ないということで、 安心しました。 Mathf.Expのほうは数式的な理解は追いつかなかったですが、 結果の画像から、そのようなコードで精細にFPSの差異をなくすことができるのですね。 ゲーム開発でも、何か正確な物理シミュレーションが必要なときは、 こういったコードで対処できるということが勉強になりました。
Bongo

2019/09/15 12:01

そうですね、移動に限らず回転・拡大縮小や、あるいは幾何学・運動学的プロパティに限らず色や透明度のようなものを変化させる場合でも、deltaTimeをかける処置の出番は多いと思います。おそらく、計算式の形がオブジェクトを移動する場合の式と似ていれば、やはり同様にdeltaTimeをかけてやるべきというケースが多いんじゃないでしょうか。 かといって何でもかんでもdeltaTimeをかけてしまうというのも考えもので、今回のように単純な掛け算では数学的に不適切であるという場合もあるかもしれません(まあ、今回は単純な掛け算だけで効果的に近似ができて有用ですが...)。 他にも「Input.GetAxis("Mouse X")」みたいに潜在的にフレームレート非依存性を持っている値にさらにdeltaTimeをかけてしまうと、かえってズレを大きくしてしまうといった罠がある場合もあるでしょう(参考:https://sleepygamersmemo.blogspot.com/2017/08/unity-mouse-input.html )。 やはりなるべく処理内容を把握した上でdeltaTimeをかけるかどうか判断するべきかと思います。ですが全部いちいち考えるのは手間かもしれませんので、ひとまずはゲームプレイに影響が大きいもの(当たり判定に関与するオブジェクトの移動など)から優先して検討するのでもいいように思います。 UIをかっこよく見せるためのアクセント的アニメーションとか、当たり判定の関与しない魔法発動エフェクトみたいなものは、見た目がおおむね問題なければフレームレート変動対応をシビアに行う必要はないかもしれません。 ※それとすみませんが、イージングカーブ実験用コードのFineモードの計算量にまだ削減の余地がありました(式変形が足りていませんでした)。指数関数と対数関数を1つずつ削除でき、超越関数の数を指数関数1回までに抑えることができそうです。 さすがに単純にdeltaTimeをかけるだけの調整よりは計算コストがかかりますが、案外気軽に使っても代価は安いかな...という気がしてきました。
退会済みユーザー

退会済みユーザー

2019/09/15 15:59

ご回答ありがとうございます。 長くなってしまってすみません、最後の質問をさせていただけたらと思います。 Time.deltaTimeなのですが、使う理由や効果は、次の2つになりますか? ・Update関数の呼び出しや、コルーチンのyield return nullのループのような毎フレーム処理では、  フレーム毎の呼び出しの時間間隔が一定ではない為(可変フレームレートの為)、  これらの毎フレーム処理で値の変化処理を行うと、フレーム間の値の変化量に差異が生じる。  この差異をTime.deltaTimeを掛けることによって無くし、変化量を一定にすることができる。 ・FPSが異なる他のデバイスとの差異を無くす為に、Time.deltaTimeを掛ける。  (前回のコメントの30FPSの状況と60FPSの状況のお話) 今まで、前者の方しか認識していなかったので、わからなかったのですが、 以前、下記URLのような質問をしまして、 https://teratail.com/questions/44392 このとき、質問③の疑問が解決してなくてモヤモヤしてたのですが、 今回教わったことで気づきまして、 「FixedUpdateやyield return new WaitForFixedUpdate()のループのような  固定フレームレートの繰り返し呼び出し処理で値を変化させる場合でも、  FPSが異なる他のデバイスとの差異を無くす為に、  Time.deltaTimeは掛けたほうがよい」ということで合っていますか? Input.GetAxis("Mouse X")はフレームレート非依存だったのですね。 勉強になります。ありがとうございます。 なるほど、UIアニメーションやエフェクトのスピードなどにずれが生じても問題ないですね。 ゲームプレイの本質に関わるところで、Time.deltaTimeを掛けることに気を付けたいと思います。 イージングカーブコードのご訂正ありがとうございます。 私の数学の理解が未熟の為、こちらのコードはゆっくり勉強させていただきたいと思います。
Bongo

2019/09/15 20:38

はい、まず前半部の2つについては妥当な認識かと思います。deltaTimeをうまく使うと、処理負荷の変化などによるフレームレートの変動だけでなく実行環境の違いによるフレームレートの変化にも併せて対応できるでしょう。 FixedUpdateでもdeltaTimeを使うべきかという件についてですが、こちらの場合はUpdateとは違ってdeltaTimeを使わなくても負荷の変化・実行環境の変化によらず動きは安定していて問題にならない場合がほとんどじゃないかと思います。ただし、動作の調整のために「Edit」→「Project Settings...」→「Time」の「Fixed Timestep」をいじったり、スクリプトからTime.fixedDeltaTimeを変更したりすると、deltaTimeを考慮していない場合は急に動きが変化して修正に手間がかかるかもしれません。個人的にはFixedUpdateでも適切にdeltaTimeを使っておいた方が望ましいように思います。 ですが、「Time.deltaTimeについて。」の質問③のコードの場合はまた違った問題がありそうです。「rb.MovePosition(rb.position + Vector3.up * Time.deltaTime * d);」のような形なら「毎秒dメートル進む」という動きをさせられるでしょうが、AddForceを使ったのではうまくいかないでしょう。おそらく最初は非常にゆっくり動き、そのまま眺めていると次第に加速していくはずです。あるいはもし重力が作用していれば、重力加速度に負けてまったく動かないかもしれません。 AddForceの引数に渡すべきなのは加える力を表すベクトルで、大きさの単位はニュートン(https://ja.wikipedia.org/wiki/%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%88%E3%83%B3_(%E5%8D%98%E4%BD%8D) )です。これにdeltaTimeをかけるケースはすぐには思いつきませんが、めったにないんじゃないでしょうかね...?
退会済みユーザー

退会済みユーザー

2019/09/16 04:11

ご回答ありがとうございます。 すみません、さらに勘違いをしていたかもしれないので確認させていただきたいのですが、 Updateのような可変フレームレートの呼び出しは、 デバイス(実行環境)ごとにFPSが異なり、 FixedUpdateのような固定フレームレートの呼び出しは、 デバイス(実行環境)が異なっても、Fixed Timestepで設定されている 時間間隔で共通して呼び出されるので、同じFPSになる。 ということで合っていますか? (FixedUpdateの呼び出しもデバイスが異なるとFPSが異なると勘違いしていました)。 なるほど、MovePositionなどには有用で、 AddForceには、ニュートンの値を渡すので、使うケースはなさそうということですね。
Bongo

2019/09/16 05:41

はい、そういうことです。もちろんタイムステップを0.02秒にしたとしてもUnityが本当にぴったり0.02秒ごとにFixedUpdateを実行するわけではないですが、Unityの世界の中のオブジェクトにとっては0.02秒ずつ時間が進んでいるように見えるので問題ないのでしょう。 FixedUpdateの世界の時間と現実の時間の比較を追記しましたが、FixedUpdateは時々とても早い間隔でループが回っています。Updateの時間の流れとタイミングを合わせるため、1フレーム中に複数回FixedUpdateが実行される場合があるというわけですね。
退会済みユーザー

退会済みユーザー

2019/09/16 06:50

ご回答ありがとうございます。 FixedUpdateの世界の時間と現実の時間の比較のご提示ありがとうございます、拝見しました。 なるほど、実際はFixed Timestep通りに実行されるというわけではないのですね。 全ての疑問が解決しました。 とても勉強になりました。 本当にありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問