質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.35%
Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

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

Q&A

解決済

3回答

2995閲覧

指定秒数で指定距離に到達する加速度がマイナスの等加速度直線運動のような移動を求めたい

tomop

総合スコア14

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

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

1グッド

0クリップ

投稿2020/07/25 07:20

編集2020/07/25 11:21

Unityで移動する処理を作っています。
オブジェクトが指定位置に移動する処理なのですが、表題の通り、ただ移動するだけではなく、このようなあらかじめ定められた距離に対して、速度が減衰して、到達する処理を求めたいと思っております。
初速が大きく、徐々に減衰して、指定時間後に指定距離移動する処理がうまく求められないでいます。
具体的には、添付gifの初速度=10のときに、折り返さないで位置に到達するようにしたいです。
等加速度直線運動の公式から、s=v0t+1/2at^2を利用して、加速度aを求めましたが、それだけではだめなようです。

ご教授いただければ幸いです。

該当のソースコード

C#

1public class Test : MonoBehaviour 2{ 3 public float t; 4 public float initSpd; 5 public Transform t1, t2; 6 7 IEnumerator Start() 8 { 9 var p = t1.position; 10 while (true) { 11 t1.position = p; 12 var v0t = initSpd * t; 13 var distance = Vector2.Distance(t1.position, t2.position); 14 var direction = (t2.position - t1.position).normalized; 15 var a = (2 * (distance - v0t)) / (t * t); 16 var v = initSpd; 17 // 指定秒数間、繰り返し 18 yield return Utils.Coroutine.WhileForSeconds(t, () => 19 { 20 t1.Translate(direction * v * Time.deltaTime); 21 v += a * Time.deltaTime; 22 }); 23 } 24 } 25}

時間=3, 初速度=0
イメージ説明
時間=3, 初速度=3
イメージ説明
時間=3, 初速度=10
イメージ説明

追記:ボールはこのようにカーブすることもあります。
赤い線が距離になります。適切なマイナス加速度"a"が求められれば、あとは毎フレーム方向を変えるだけと考えておりました。達成したいことは、指定した時間に、減衰していく速度で、ランダムな方向に移動し、初期の2点間の距離分移動したいです。
イメージ説明

Bongo👍を押しています

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

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

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

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

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

ozwk

2020/07/25 13:59 編集

初速0のときなんかは明らかに加速していってますがそれはいいんですか? 「減衰していく速度」を満たしているとは思えません。 そもそも「減衰していく速度」とはなんですか? 言葉通りに受け取れば充分な時間経過で速度の絶対値が0に近づくってことかと思いますが、等加速度運動では不可能です。 初速と初期位置と目標位置と到達時刻が与えられて、到達時刻で目標位置上にいればいいというのはわかりますが、それ以外がよくわかりません。到達時の速度はどうなっててほしいのかとか。 質問としては頭で思い浮かべてる動きを数式として表せないということに行き着くと思うのですが、 どういう動きをさせたいのかがよくわからないので 横軸に時間、縦軸に速度と位置(目標位置も添えて)を描いた2つのグラフを複数パターン用意してください
tomop

2020/07/25 14:58

初速0のものはソースコードのパラメータバリエーションとして上げたサンプルとなります。 解決したい課題は本文中にある >具体的には、添付gifの初速度=10のときに、折り返さないで位置に到達するようにしたいです。 というものでした。等加速度運動でなくても問題ありません。 距離到達時の速度は、初速から減衰して、0になることを想定しています。 自分のつたない知識では等加速度直線運動の公式からなにがししてできるのかなと考えたのですが、 難しいようですね。勉強になります。 指定時間、指定初速、指定距離という条件のもと、速度が減衰する様の動き方で、距離を到達(到達時の速度は0)する方法があればと思って模索していた次第です。
Bongo

2020/07/26 11:42

「指定時間、指定初速、指定距離という条件のもと」とおっしゃる内の、「指定距離」というのはどういう意味でしょうか? ご提示いただいた図の赤線のことだとすると、目的地が上~右の場合ではスタート地点と目的地を直線で結んだ長さであるように見えますが、目的地が左下の場合では最短距離ではなく黒矢印の長さを示している様子で、統一された規則がないように見えます。 私の解釈だと、スタート地点と目的地を直線で結んだ長さというのはスタート地点の位置と目的地の位置が与えられれば自動的に決定されてしまうため別途指定する余地はないはずなので、「指定距離」というのは図の黒矢印の長さのことではないかと思ったのですが...ご質問者さんの意図はどのようなものでしょうか?
tomop

2020/07/26 12:05

指定された距離という意味でした。上図ですとt1-t2間の距離です。 赤い線に直線の意図はありません。仰る通り、黒矢印の長さであります。 黒線は移動の軌跡サンプルで、黒線の長さは全て同じということを赤線で提示したつもりが、確かに分かりずらいものとなってしまいました。ただ黒線のちょんちょんをつければよかったです。
Bongo

2020/07/26 12:58

なるほど、ありがとうございます。 「時間=3, 初速度=10」のケースについてですが、ご提示の図のようにSphereがCubeを通り過ぎてから後戻りしてCubeの位置に到着する...という動きにはしたくないとのことですね。 たとえばCubeとSphereが3離れた位置関係にあったとして、その条件でSphereの初速度ベクトルはCubeの方角を向いていて、さらに指定距離が5だったとすると、特に対策をとらなければ確かにおっしゃるとおりCubeを通り過ぎて4の位置まで行ってから1戻ってくる動きになりそうな気がします。 この問題をどのように回避するのが望ましいでしょうか?Cube到達までの道のりの長さが5であるという条件を満たしたまま「Cubeを貫通してから戻る」という動きをさせないようにするとなると、たとえば初速度ベクトルの向きを書き換えて横にそれさせる...なんて案が思い当たりますが、そうすることは許されるでしょうか? それとも、初速度ベクトルの向きは当初の指定通りCubeの方角でないと困るでしょうか。その場合はおそらく発射後に軌道をそらせ、クエスチョンマークのような経路をとらせて道のりを稼ぐことになりそうです。軌道がちょっと複雑になりますので、たぶんコードが複雑になってしまうかと思いますが...
tomop

2020/07/26 13:47 編集

>たとえばCubeとSphereが3離れた位置関係にあったとして、その条件でSphereの初速度ベクトルはCubeの方角を向いていて、さらに指定距離が5だったとすると… CubeとSphereが3離れた位置関係にある場合は、指定距離は3になります。指定距離はオプション値ではありません。CubeとSphereが任意の位置にくるので、その2点間の距離は、指定された距離、という意味合いでした。 追記の内容は単純な線形補完で2点間の距離をただ移動する処理は考えていなかった旨を説明するために作成しました。移動したい位置があるのではなく(そうとも言えますが)、指定距離の分、移動したいということも伝えたかったです。 >この問題をどのように回避するのが望ましいでしょうか? 質問内容は >>初速が大きく、徐々に減衰して、指定時間後に指定距離移動する処理がうまく求められないでいます。 です。 具体的にはt1が初速度10で3秒でぴったりt2に到達する計算方法があれば教えてほしいです。(時間、初速度は任意ですし、t1-t2間の距離も任意です。)でも今書いてて思ったんですけど、初速度が10m/sで距離が10m未満なら普通に飛び越えちゃいますよね。 最初の初速度を適用する前に、初速度をもとに計算したマイナス加速度を適用するようなことをしてしまえば、そのような値を求めることは可能なのでしょうか。
tomop

2020/07/26 13:44

>初速度が10m/sで距離が10m未満なら普通に飛び越えちゃいますよね。 すみません、1秒後はそうですけど、開始から1秒までにマイナス方向の作用があればそうはならないですね。
tomop

2020/07/26 13:54

>具体的にはt1が初速度10で3秒でぴったりt2に到達する計算方法があれば教えてほしいです。 正確にはこうでした。 具体的にはt1が初速度10で3秒でぴったり折り返さないでt2に到達する計算方法があれば教えてほしいです。 >「時間=3, 初速度=10」のケースについてですが、ご提示の図のようにSphereがCubeを通り過ぎてから後戻りしてCubeの位置に到着する...という動きにはしたくないとのことですね。 はい!
guest

回答3

0

いろいろと質問して面倒をおかけした手前、私からも何か案を出したいと思って検討してみました。
発射前の時点であらかじめ経路曲線を決定してしまい、そこを指定時間かけて辿る...という方針ですが、いかがでしょうか?

投射物には下記のようなスクリプトをアタッチしており、インスペクター上で移動時間・初速度ベクトル・目的地を設定しておきます。
また、移動自体には関係ないのですが、デバッグ目的で時間・速さ・距離を画面上に表示させるためのTextをセットしています。

C#

1using System.Collections; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5using UnityEngine.UI; 6 7public class DecelerationMotion : MonoBehaviour 8{ 9 [SerializeField] private Text timeIndicator; // 経過時間表示 10 [SerializeField] private Text speedIndicator; // 速さ表示 11 [SerializeField] private Text travelIndicator; // 進行距離表示 12 13 [SerializeField] private float duration = 5.0f; // 移動時間 14 [SerializeField] private Vector3 initialVelocity = new Vector3(0.0f, 10.0f, 0.0f); // 初速度 15 [SerializeField] private Transform target; // 目的地 16 17 private IEnumerator Start() 18 { 19 // スタート地点 20 var origin = this.transform.position; 21 22 // ゴール地点 23 var destination = this.target.position; 24 25 // 移動時間をチェックする 26 // 移動時間が0だと計算不能なので、位置を即座に目的地に移動して終わる 27 if (this.duration <= 0.0f) 28 { 29 this.transform.position = destination; 30 yield break; 31 } 32 33 // 初速度の向きが目的地の向きを向いていないときに経路を曲げてみせるための 34 // コントロールポイントの位置を決める 35 // このとき、スタートとゴールを結ぶ直線に垂直な平面を超えないように位置を制限して 36 // 目的地を通り過ぎてから後戻りする経路にはならないようにする 37 var controlPointVector = this.initialVelocity; 38 if (new Plane(origin - destination, destination).Raycast( 39 new Ray(origin, controlPointVector), 40 out var enter)) 41 { 42 controlPointVector = Vector3.ClampMagnitude(controlPointVector, enter); 43 } 44 45 var controlPoint = origin + controlPointVector; 46 47 // 経路の長さを求め、確認のために長さをコンソールに出力してみる 48 var pathLength = MeasurePathLength(origin, controlPoint, destination); 49 Debug.Log($"Path length for {this.target.name} : {pathLength}"); 50 51 // 初期速さをチェックする 52 // もしそれが経路上を目標時間かけて等速運動するときの速さよりも遅い場合、スタート後の速さは 53 // 遅くなる一方なので時間内に到着できないはず 54 // そこで初期速さの最低値は等速運動時の速さとする 55 var linearSpeed = pathLength / this.duration; 56 var initialSpeed = Mathf.Max(this.initialVelocity.magnitude, linearSpeed); 57 58 // この方式の曲線では、経路上の位置を求めるためのパラメーターと経路上の進行距離が比例しない 59 // そこで、後でそのズレの補正に使う進行距離テーブルを作っておく 60 var progressTable = GetProgressTable(origin, controlPoint, destination).ToList(); 61 62 // 線形の位置パラメーターを変調させて、initialSpeedから減速していく非線形の変化に変えるためのパラメーターを求める 63 var progressExponent = (initialSpeed * this.duration) / pathLength; 64 65 // アニメーションを行う 66 // 動かす上でtravelやcurrentPositionは不要だが、動きのデバッグのため用意した 67 var time = 0.0f; 68 var travel = 0.0f; 69 var currentPosition = origin; 70 while (time < this.duration) 71 { 72 yield return null; 73 74 var deltaTime = Time.deltaTime; 75 time += deltaTime; 76 77 // まず時間経過とともに線形の動きをする進行率を得て... 78 var rawNormalizedProgress = Mathf.Clamp01(time / this.duration); 79 80 // それを非線形の動きに変調させ... 81 var normalizedProgress = 1.0f - Mathf.Pow(1.0f - rawNormalizedProgress, progressExponent); 82 83 // 進行距離テーブルを参考にして進行パラメーターを得る 84 var progressParameter = GetProgressParameter(normalizedProgress * pathLength, progressTable); 85 86 // 進行パラメーターと対応する曲線上の点をサンプリングし、そこを次の位置とする 87 var nextPosition = EvaluatePosition(origin, controlPoint, destination, progressParameter); 88 var deltaPosition = nextPosition - currentPosition; 89 currentPosition = nextPosition; 90 this.transform.position = currentPosition; 91 92 // 参考情報として、経過時間・現在の速さ・進行距離を画面上に表示する 93 var deltaTravel = deltaPosition.magnitude; 94 var speed = deltaTravel / deltaTime; 95 travel += deltaTravel; 96 this.timeIndicator.text = time.ToString("F2"); 97 this.speedIndicator.text = speed.ToString("F2"); 98 this.travelIndicator.text = travel.ToString("F2"); 99 } 100 } 101 102 private static Vector3 EvaluatePosition(Vector3 p0, Vector3 p1, Vector3 p2, float t) 103 { 104 var it = 1.0f - t; 105 var f0 = it * it; 106 var f1 = it * t * 2.0f; 107 var f2 = t * t; 108 return (p0 * f0) + (p1 * f1) + (p2 * f2); 109 } 110 111 private static float MeasurePathLength(Vector3 p0, Vector3 p1, Vector3 p2, int loopCount = 1024) 112 { 113 var length = 0.0f; 114 var currentPosition = p0; 115 for (var i = 1; i <= loopCount; i++) 116 { 117 var nextPosition = EvaluatePosition(p0, p1, p2, (float)i / loopCount); 118 length += (nextPosition - currentPosition).magnitude; 119 currentPosition = nextPosition; 120 } 121 122 return length; 123 } 124 125 private static IEnumerable<float> GetProgressTable(Vector3 p0, Vector3 p1, Vector3 p2, int loopCount = 1024) 126 { 127 var length = 0.0f; 128 var currentPosition = p0; 129 for (var i = 1; i <= loopCount; i++) 130 { 131 var nextPosition = EvaluatePosition(p0, p1, p2, (float)i / loopCount); 132 length += (nextPosition - currentPosition).magnitude; 133 currentPosition = nextPosition; 134 yield return length; 135 } 136 } 137 138 private static float GetProgressParameter(float progress, List<float> progressTable) 139 { 140 if (progress <= 0.0f) 141 { 142 return 0.0f; 143 } 144 145 var searchResult = progressTable.BinarySearch(progress); 146 if (searchResult >= 0) 147 { 148 return (float)(searchResult + 1) / progressTable.Count; 149 } 150 151 var upperIndex = ~searchResult; 152 if (upperIndex >= progressTable.Count) 153 { 154 return 1.0f; 155 } 156 157 var lowerIndex = upperIndex - 1; 158 var upperValue = progressTable[upperIndex]; 159 var lowerValue = lowerIndex < 0 ? 0.0f : progressTable[lowerIndex]; 160 return (lowerIndex + 1 + Mathf.InverseLerp(lowerValue, upperValue, progress)) / progressTable.Count; 161 } 162}

durationを5秒、initialVelocity(0, 1, 0)とした場合の動きは下図のようになりました。
Target1は発射地点の上方5mに位置しており、採用された経路は2点を直線で結ぶ形となっています。初期速さが1m/秒なので、移動時間5秒では減速できる余地がなく等速直線運動になっています。一方、それ以外のターゲットは経路の長さが5m未満なので減速の余地があり、次第に動きがゆっくりになっています。とはいえ、経路の長さが限界長さ5mと比べてさほど余裕があるわけではありませんので、比較的等速に近い動きになっています。

図1

一方、durationは5秒のままで、initialVelocity(0, 5, 0)とした場合の動きは下図のようになりました。
減速の余地が大きくなったので、だいぶ強烈な緩急が付きました。経路曲線も初速度ベクトルの方角に引きつけられてたわみが大きくなっています。しかし今回使用した曲線は比較的シンプルな2次ベジェ曲線ですので、特に左下のターゲットで顕著ですが経路曲線のカーブがけっこう鋭くなってしまいました。ご提示いただいた図のような円弧に近い軌道ではなくなってしまいすみません...

図2

投稿2020/07/26 22:24

Bongo

総合スコア10811

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

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

tomop

2020/07/28 10:16 編集

回答ありがとうございます。 コード書いていただいた上に図示まで作成いただいて、多大な労力をかけていただき恐縮です。 コードを私の方でも実際に走らせてみました。 ご提示いただいてる設定値ですと、ログに出力されてるlength(Textの3番目)なのですが、ここがtarget2-4においても、"5"になるのが条件でした。 >あらかじめ定められた距離 >初期の2点間の距離分移動 というのは、このことを指していました。分かりずらくて申し訳ないです。 イージングを使うことは考えてなかったのですが、初速度値からイージング係数(なんて言うか知りませんが0-1のやつ)を指数関数的な減衰曲線を計算しているのですね。 ですが仰られている通り、"円弧に近い軌道"が再現したい部分もありました。 昨日、距離についてイージングし、差分を足していくような処理にすれば思ったような挙動になることに気づきました。 上手く伝えられないのでコードを張ります。 IEnumerator Start() {  var p = t1.position;  while (true) {   t1.position = p;   var distance = Vector2.Distance(t1.position, t2.position);   var direction = (t2.position - t1.position).normalized;   var stime = Time.time;   var hist = 0f;   // update   yield return Utils.Coroutine.WhileForSeconds(t, () =>   {    var n = (Time.time - stime) / t;    var delta = Mathf.Lerp(0, distance, Easing(n)) - hist;    hist += delta;    t1.Translate(direction * delta);   });  } } これであれば、directionを適当に処理してしまえば、当初想定通りに近い処理となりそうです。 ですがEasing(n)の部分に、ご提示コードの様に初速度的な値を組み入れることはできてなかったです。 Bongo様の回答からそれについて着想を得られることができました。大変勉強になりました。 ものの数時間でこのようなコードを書けてすごいです…尊敬いたします。
guest

0

ベストアンサー

等加速度直線運動の公式から、s=v0t+1/2at^2を利用して

それで求められるのは「指定時間後に指定距離移動する」軌道です。
そして見たところそれは正しく実現できています。
加えて「折り返さないで」、つまり終点での速度が0という条件ですが、加速度一定では「指定時間後」か「終点で速度0」のどちらかしか満たせません。
等加速度でなくてよいとのことなので、適切な加速度変化を求める式を作ってください。
カーブについてはとりあえず無視するとして、
到達時刻と終点の速度0の2つの条件を立式して、例えば加加速度一定・初期加速度任意の条件を付ければ、加加速度と初期加速度の2つの未知数で1通りの解が出ると思います。
微積分はできますか?

投稿2020/07/26 16:08

ikadzuchi

総合スコア3047

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

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

tomop

2020/07/28 10:33 編集

回答ありがとうございます。 具体的に説明していただきありがとうございます。 時間と初速度両方与えちゃうと等加速度直線運動ではできないんですね。 微積分ですか、本棚にそのような文字が書かれた本が見えますが、はい、自信を持ってできないと言えます。 この公式の1/2が積分で求められていることは知ってます。笑 ヒントを示してくれてありがとうございます。 ちょっとずつ調べながら数学的な解を探していこうと思います。
guest

0

速度を2段階変化させることで理想の挙動を得ることができました。
ありがとうございました。

C#

1IEnumerator Start() 2{ 3 var p = t1.position; 4 while (true) 5 { 6 t1.position = p; 7 var direction = (t2.position - t1.position).normalized; 8 var distance = Vector2.Distance(t1.position, t2.position); 9 10 var v0 = initSpd; 11 var M = distance; 12 var T = duration; 13 var tm = M / v0; 14 var vm = v0 / T * tm; 15 var accel1 = (vm - v0) / (tm - 0); 16 var accel2 = (0 - vm) / (T - tm); 17 Debug.Log($"vo={v0}, M={M}, T={T}, tm={tm}, vm={vm}, accel1={accel1}, accel2={accel2}"); 18 var v = initSpd; 19 var stime = Time.time; 20 var hist = 0f; 21 // update 22 yield return Utils.Coroutine.WhileForSeconds(T, () => 23 { 24 t1.Translate(direction * v * Time.deltaTime); 25 hist += v * Time.deltaTime; 26 if (Time.time - stime <= tm) 27 v += accel1 * Time.deltaTime; 28 else 29 v += accel2 * Time.deltaTime; 30 }); 31 Debug.Log($"経過時間={Time.time - stime}, 移動距離={hist}, v={v}"); 32 } 33} 34

投稿2020/07/29 12:21

編集2020/07/29 12:24
tomop

総合スコア14

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問