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

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

詳細はこちら
Unity

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

Q&A

解決済

1回答

1890閲覧

Time.deltaTimeとfor文

退会済みユーザー

退会済みユーザー

総合スコア0

Unity

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

0グッド

0クリップ

投稿2021/01/22 17:04

編集2021/01/23 08:01

前提・実現したいこと

こちらの質問において、Time.deltaTimeが掛かっていないことに気づき、
自分なりに解釈したのですが、その認識で合っているか教えていただけませんか?
また、Time.deltaTimeに関して、Lerpのイージング処理やQuaternion.EulerでSinを動かす処理に関しても、
気づいたことがあり、こちらも質問させていただけたらと思います。
ご教示お願いします。

試したこと

・質問1。
こちらの質問において、
Time.deltaTimeを掛けてなくてもよい理由を以下のように考えましたが、この認識で合っていますか?

まず、前提として、経過時間を換算する処理は下記になると思うのですが、
これはfpsが異なっても、下記コードで必ず(正確に(?))経過時間を取得できると認識しています。

C#

1 float seconds = 0; 2 void Update(){ 3 seconds += Time.deltaTime; 4 Debug.Log(seconds); // 経過時間 5 }

以下のようなコルーチン内におけるfor文では、fpsが異なるどのデバイスにおいても、
変数timeは一律に経過時間を取得し、this.animationLength未満の経過時間までfor文が回るので、
下記コードは、fpsが異なっても、Time.deltaTimeを掛けずに、一律にthis.animationLengthの経過時間で、
LerpメソッドやSmoothStepメソッドの補間値tを0~1まで遷移させる処理が実装できている、
という認識で合っていますか?

C#

1 for (float time = 0.0f; time < this.animationLength; time += Time.deltaTime) 2 { 3 var t = time / this.animationLength; 4 // Time.deltaTimeを掛けない補間値tを使った処理。LerpメソッドやSmoothStepメソッド等。 5 yield return null; 6 } 7 // 補間値t=1の調整処理

・質問2。
Lerpのイージング処理では、質問1と違って、Time.deltaTimeを掛けることが必要と思い、
下記コードを組んでみたのですが、一瞬で、startPointからendPointに移動してしまいます。
for文の中で2つのログを取ってみたのですが、問題なさそうでした。
下記コードはどのように修正すればイージング処理になりますか?

C#

1 float animationLength = 5.0f; 2 3 IEnumerator MoveCoroutine(Transform move, Transform startPoint, Transform endPoint){ 4 move.position = startPoint.position; 5 for (float time = 0.0f; time < animationLength; time += Time.deltaTime){ 6 move.position = Vector3.Lerp(move.position, endPoint.position, Time.deltaTime * 5); 7 yield return null; 8 Debug.Log(time); 9 Debug.Log("t:" + Time.deltaTime * 5); 10 } 11 }

 また、上記コードが修正できたとしても、「animationLength時間でイージングされる」だけであって、
「animationLength時間でendPointの場所までイージング」はされないですよね?
Lerpのイージング処理で、「指定時間で、指定終点まで到達される」ことは実装不可能と考えているのですが、
合っていますか?(それとも実装可能ですか?)
イージング系のアセットを使えば解決するとは思いますが、Lerpの実装において教えていただきたいです。

・質問3。
こちらの質問ですが、
デバイス間のfpsの差異をなくすためには、Time.deltaTimeを掛けて、以下のようにすべきでしょうか?
それとも不要ですか?
最初は必要に思えたのですが、「Time.time - startTime」を考慮すると不要なのか、わからない状態です。

C#

1 void Update() 2 { 3 transform.rotation = startRotation * Quaternion.Euler(0f, 0f, Mathf.Sin((Time.time - startTime) * speed * Time.deltaTime) * maxAngle); 4 }

追記①

・時間を指定しないイージング。

C#

1 float speed = 0.5f; 2 IEnumerator MoveCoroutine(Transform move, Transform startPoint, Transform endPoint){ 3 move.position = startPoint.position; 4 while(move.position != endPoint.position){ 5 move.position = Vector3.Lerp(move.position, endPoint.position, Time.deltaTime * speed); 6 float distance = (endPoint.position - move.transform.position).sqrMagnitude; 7 if(distance < 0.0001){ 8 move.position = endPoint.position; 9 } 10 yield return null; 11 } 12 Debug.Log("finish"); 13 }

追記②

・easeOutElasticの元のコード。

function easeOutElastic(x: number): number { const c4 = (2 * Math.PI) / 3; return x === 0 ? 0 : x === 1 ? 1 : pow(2, -10 * x) * sin((x * 10 - 0.75) * c4) + 1; }

・試したコード(EaseOutElastic)

C#

1 static float EaseOutElastic(float t) 2 { 3 const float c4 = (2.0f * Mathf.PI) / 3.0f; 4 5 t = Mathf.Clamp01(t); 6 return t == 0 ? 0 : t == 1 ? 1 : Mathf.Pow(2, -10 * t) * Mathf.Sin((t * 10 - 0.75f) * c4) + 1; 7 } 8 9 float animationLength = 5.0f; 10 IEnumerator MoveCoroutine(Transform move, Transform startPoint, Transform endPoint){ 11 move.position = startPoint.position; 12 for (float time = 0.0f; time < animationLength; time += Time.deltaTime){ 13 move.position = Vector3.Lerp(startPoint.position, endPoint.position, EaseOutElastic(time / this.animationLength)); 14 yield return null; 15 } 16 } 17 18 static float EaseOutExpo(float t) 19 { 20 t = Mathf.Clamp01(t); 21 return t < 1.0f ? 1.0f - Mathf.Pow(2.0f, -10.0f * t) : 1.0f; 22 } 23 24 static float EaseOutCubic(float t) 25 { 26 var it = 1.0f - Mathf.Clamp01(t); 27 return 1.0f - (it * it * it); 28 }

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

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

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

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

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

y_waiwai

2021/01/22 22:45

その元質問を提示しましょう
guest

回答1

0

ベストアンサー

質問1について

おそらく妥当なお考えかと思います。後述の質問3の件と同じく、Time.deltaTimeをかけるとかえっておかしくなってしまうでしょうね。

質問2について

これは原因不明ですね...ご提示のコードを試してみましたが、ちゃんとイーズアウトするような動きをしており、不審な点は見当たりませんでした。他の所に原因があるんでしょうかね?コンソールにmove.positionも出力してみて、位置がどのように変化しているか調べてみるのも手がかりになるかもしれません。

animationLength時間でイージングされるだけ」とおっしゃるのはその通りですね。以前「Lerpの戻り値を次の開始位置として更新するLerpの処理」で申し上げたように、数学的には永遠に目的地に到達しない形になっています。
animationLength時間で目的地に到達させたい場合は、「SmoothFollow.csの分からない箇所に関して。」のコメント欄で申し上げたように「animationLength秒かけて0.0~1.0へ線形に変化する値をイージング関数で変調させ、その値でLerpを行う」という手が使えるかと思います。

C#

1 float animationLength = 5.0f; 2 3 IEnumerator MoveCoroutine(Transform move, Transform startPoint, Transform endPoint){ 4 move.position = startPoint.position; 5 for (float time = 0.0f; time < animationLength; time += Time.deltaTime){ 6 move.position = Vector3.Lerp(startPoint.position, endPoint.position, EaseOutExpo(time / this.animationLength)); 7 yield return null; 8 Debug.Log(time); 9 Debug.Log("t:" + Time.deltaTime * 5); 10 } 11 } 12 13 // https://easings.net/#easeOutExpo をベースにしたもの 14 // 指数関数的カーブで、緩急はきつめです 15 static float EaseOutExpo(float t) 16 { 17 t = Mathf.Clamp01(t); 18 return t < 1.0f ? 1.0f - Mathf.Pow(2.0f, -10.0f * t) : 1.0f; 19 } 20 21 // https://easings.net/#easeOutCubic をベースにしたもの 22 // 3次関数のカーブで、EaseOutExpoより穏やかです 23 static float EaseOutCubic(float t) 24 { 25 var it = 1.0f - Mathf.Clamp01(t); 26 return 1.0f - (it * it * it); 27 }

質問3について

ここではTime.deltaTimeをかける必要はないでしょう。おっしゃるようにTime.time - startTimeが時間の要素...つまり初期時刻からの経過時間を表していて(単位は秒)、それにspeed(例えるならば内部モーターの回転速度...単位はラジアン/秒)をかけることでTime.time - startTime秒経過時点での回転量(単位はラジアン)が得られています。これにさらにTime.deltaTime(単位は秒)をかけてしまうと、何を表しているのか分からない値になってしまうはずです。

##追記

時間を指定しないイージングのコード変更案

C#

1 float speed = 0.5f; 2 3 IEnumerator MoveCoroutine(Transform move, Transform startPoint, Transform endPoint){ 4 move.position = startPoint.position; 5 // ループの継続条件は常にtrueとしてしまい... 6 while(true){ 7 move.position = Vector3.Lerp(move.position, endPoint.position, Time.deltaTime * speed); 8 float distance = (endPoint.position - move.transform.position).sqrMagnitude; 9 if(distance < 0.0001){ 10 // ループ終了条件を満たしたらbreakでループを脱出する 11 move.position = endPoint.position; 12 break; 13 } 14 yield return null; 15 } 16 Debug.Log("finish"); 17 }

EaseOutElasticのコルーチン部分の修正

Vector3.LerpUnclampedの説明文にはVector3.Lerpの説明文にある「パラメーターtは0以上1以下の範囲にクランプされる」といった内容が含まれていません。「Unclamped」の名前の通りクランプされないというわけですね。

C#

1 float animationLength = 5.0f; 2 IEnumerator MoveCoroutine(Transform move, Transform startPoint, Transform endPoint){ 3 move.position = startPoint.position; 4 for (float time = 0.0f; time < animationLength; time += Time.deltaTime){ 5 move.position = Vector3.LerpUnclamped(startPoint.position, endPoint.position, EaseOutElastic(time / this.animationLength)); 6 yield return null; 7 } 8 }

投稿2021/01/22 23:02

編集2021/01/23 10:45
Bongo

総合スコア10811

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

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

退会済みユーザー

退会済みユーザー

2021/01/23 08:06 編集

ご回答ありがとうございます。 ・質問1について。  妥当とのことで、ご回答いただきありがとうございます。理解できました。 ・質問2について。  質問2について、5点質問させていただけたらと思います。多くなってしまってすみません。  以前の質問のご提示ありがとうございます。提示を失念していました。  move.positionを出力してみて気づいたのですが、補間値tの値をよく調べてみたら、  一瞬、1を超えているログがありました。  そこで、Time.deltaTimeのログを出してみた所、  以下のように2フレーム目で異常に大きい値が出ていました。  0.02  0.3333333  0.0325021  0.0096783  0.0026622  この2フレーム目のTime.deltaTimeの0.3333333秒についてですが、  この瞬間だけをfpsとして換算すると、  1÷0.3333333≒3で、3fpsという認識で合っていますか?(●質問2-1)  他の瞬間のfps、例えば、Time.deltaTimeが0.02秒の時などは、1÷0.02=50で、  50fpsで標準的なfpsが出ていると思うのですが、  3fpsという異常なfpsになってしまう瞬間もあるのですね。  もちろん、UnityはまずまずのスペックのPCで動かしてます。  そう考えると、補間値tのように1を超えたらまずい値で、Time.deltaTimeを掛ける場合は、  掛ける数が1よりも大きい値ぐらいだったら、バグになる可能性があることも  考慮したほうがよいということでしょうか...(●質問2-2)。  コードを「Time.deltaTime * 0.5f」にしたところ、正常なイージング動作が確認できました。  なるほど、animationLength秒かけて、補間値tを0.0~1.0へイージング関数で遷移させれば、  指定時間で、指定終点までイージング処理できるということですね。  以前、ご教示いただいていましたね、すみません。    すみません、ご提示いただいたコードに関してですが、今回のfor文では、for文の性質上、  for文の中で(for文の処理部において)、  timeは、animationLength以上になることはないという認識で合っていますか?  そして、以前の質問させていただいた内容(https://teratail.com/questions/316286)と同様、  厳密には、for文を抜けた後で、補間値t=1の調整処理が必要ということでしょうか?(●質問2-3)  move.position = Vector3.Lerp(startPoint.position, endPoint.position, 1));    また、質問欄に追記①として、時間を指定しない指定終点までのイージング処理を考えて、  実装したコードを記載したのですが、  このコードをよりスマートに書く改善案等があれば教えていただけませんか?(●質問2-4)  以前、https://easings.net/の存在をご教示いただい際、  その時点ではまだC#に書き直す方法がわかっていなく、  後々勉強しようと思っていたのですが、  今回、EaseOutExpoメソッドやEaseOutCubicメソッドのC#版を教えていただき、  なんとなく書き直す方法が理解できたので、後は気になっている関数として、  easeOutElastic関数をC#版に書き換えて、質問欄の追記②に記載したのですが、  こちらのコードを試したところ、変な動きになってしまい、  easeOutElastic関数のC#版のコードを教えていただけませんか?(●質問2-5)  記載コードにおいて、ご提示いただいたEaseOutExpoメソッドやEaseOutCubicメソッドでは、  正常に動作することを確認しました。 ・質問3について。  Time.time - startTimeで、fpsが異なるデバイス間でも一律に初期時刻からの経過時間(秒)が  取得できているので、Time.deltaTimeを掛ける必要はないということですね。  理解できました、ありがとうございます。
Bongo

2021/01/23 10:46

質問2-1、2-2について そうですね、3FPSになっているようです。ゲームスタート直後とかですと、諸々の初期処理などでそんな風になることがあるかもしれません。処理負荷の影響を受けそうな実験をする場合、スタート直後は避けてコルーチンで数秒待機してからとか、キー操作やマウスクリックで実験開始するようにした方がいいかもしれませんね。 そして、1を超えるべきでないところにこういった予期しない現象で大きな値が入ってしまうと、異常動作を起こす可能性も大いにありうるでしょう。Unityの用意しているLerpのたぐいは1以上の値が来ても1にクランプされ、異常に遠くまで吹っ飛んでしまうようなことはないだろうと思いますが、それでも今回のようにいきなりゴールまで到達してしまってアニメーションにならないケースも考えられますね。 他にもTime.deltaTime関連の考慮事項として、デルタタイムはMaximum Allowed Timestepを超えないらしいという記事もありました(http://posposi.blog.fc2.com/blog-entry-242.html )。時間測定だとかでおかしなずれがある場合、こういったことを疑ってみるべきかもしれません。 質問2-3について はい、おっしゃる通り、ループ内に突入したということはループ内ではtime < animationLengthの条件は満たされているはずです。 そしてループを脱出したときにはtime >= animationLengthであるはずですが、整数を1ずつカウントアップするループと違って、最後のループでtimeがanimationLengthぴったりの点を飛び越えてループが終了する可能性が高いでしょう。そこで適宜最後の調整をやってやるといいかと思います。 ぴったりt=1に合わせているということをコード上で明確にするため、コメントいただいた通りに move.position = Vector3.Lerp(startPoint.position, endPoint.position, 1)); としてもいいでしょうし、シンプルに move.position = endPoint.position; でもいいかと思います。 質問2-4について さしあたり大きな問題は思い当たりませんね。あえて申し上げるとしたら、現状のコードのループ継続条件はmove.position != endPoint.positionとなっています。Vector3同士の比較の実装は public static bool operator ==(Vector3 lhs, Vector3 rhs) { float num1 = lhs.x - rhs.x; float num2 = lhs.y - rhs.y; float num3 = lhs.z - rhs.z; return (double) num1 * (double) num1 + (double) num2 * (double) num2 + (double) num3 * (double) num3 < 9.99999943962493E-11; } の結果を反転させたものになっており、要するにVector3同士の二乗距離が微小値未満かどうかを判定基準にしています。ですが同様の判定をご質問者さん自身もループ内で行っており、わずかながら無駄があると言えるかもしれません。ループ条件を削除したバージョンを追記しました。 質問2-5について EaseOutElastic自体は正しく動作しているようです。ですがEaseOutElasticの値の変化のグラフをご覧いただきますと、曲線が縦軸上限の1を突き抜けている箇所があるのを見て取れるかと思います。最初の方で申し上げましたが、Lerpは補間値を自動的に0~1に制限してくれるため目的地を通り過ぎる動きをしないのだと思います。 追記しましたように、コルーチン部分をLerpUnclampedを使うように変更してみてはいかがでしょう。
退会済みユーザー

退会済みユーザー

2021/01/23 12:30

ご回答ありがとうございます。 ・質問2-1, 2-2について。  0.3333333秒という値は、Maximum Allowed Timestepで丸められていた値だったのですね。  なるほど、ゲームスタート直後で動作が重いかもしれないので、  少し待ってから動作させたほうがよいということですね。  補間値など1を超えるべきでない値に関しては、注意が必要とのことで理解できました。 ・質問2-3について。  for文脱出時に、timeはanimationLengthを超えている可能性が高いので、  調整処理が必要とのことで理解できました、ありがとうございます。 ・質問2-4について。  なるほど、ご提示のコードで、よりコンパクトな実装にできるのですね。  とても勉強になります、ありがとうございます。  ・質問2-5について。  なるほど、EaseOutElasticの戻り値が1を超えていたわけですね、気づきませんでした。  ご提示のコードを試させていただき、正常に動作することを確認いたしました。 全ての疑問点が解決し、理解できました。 詳しい解説とご教示、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問