いろいろと質問して面倒をおかけした手前、私からも何か案を出したいと思って検討してみました。
発射前の時点であらかじめ経路曲線を決定してしまい、そこを指定時間かけて辿る...という方針ですが、いかがでしょうか?
投射物には下記のようなスクリプトをアタッチしており、インスペクター上で移動時間・初速度ベクトル・目的地を設定しておきます。
また、移動自体には関係ないのですが、デバッグ目的で時間・速さ・距離を画面上に表示させるためのText
をセットしています。
C#
1 using System . Collections ;
2 using System . Collections . Generic ;
3 using System . Linq ;
4 using UnityEngine ;
5 using UnityEngine . UI ;
6
7 public 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と比べてさほど余裕があるわけではありませんので、比較的等速に近い動きになっています。
一方、duration
は5秒のままで、initialVelocity
を(0, 5, 0)
とした場合の動きは下図のようになりました。
減速の余地が大きくなったので、だいぶ強烈な緩急が付きました。経路曲線も初速度ベクトルの方角に引きつけられてたわみが大きくなっています。しかし今回使用した曲線は比較的シンプルな2次ベジェ曲線ですので、特に左下のターゲットで顕著ですが経路曲線のカーブがけっこう鋭くなってしまいました。ご提示いただいた図のような円弧に近い軌道ではなくなってしまいすみません...