回答編集履歴

3

プロパティ名の誤りを修正

2019/12/19 18:02

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -140,7 +140,7 @@
140
140
 
141
141
  **追記**
142
142
 
143
- `transform.eulerAngles.y`を毎回0に書き換えるという方法だと、書き換え後の`transform.y`の方向は本来の向きからずれてしまうかと思います。ですが今回の場合では「向きを目標方向に近づける」→「オイラー角のYだけを0に書き換える」→「向きを目標方向に近づける」→「オイラー角のYだけを0に書き換える」...というプロセスをわずかずつ何度も繰り返すことによって、全体の連続性は保たれそうな感じです。ですので、そのやり方で問題がなさそうでしたらOKかと思います(後述しますが、本当にそのやり方でも全方向完璧に自然な回転を実現できるのかどうか不安はあるのですが...)。
143
+ `transform.eulerAngles.y`を毎回0に書き換えるという方法だと、書き換え後の`transform.up`の方向は本来の向きからずれてしまうかと思います。ですが今回の場合では「向きを目標方向に近づける」→「オイラー角のYだけを0に書き換える」→「向きを目標方向に近づける」→「オイラー角のYだけを0に書き換える」...というプロセスをわずかずつ何度も繰り返すことによって、全体の連続性は保たれそうな感じです。ですので、そのやり方で問題がなさそうでしたらOKかと思います(後述しますが、本当にそのやり方でも全方向完璧に自然な回転を実現できるのかどうか不安はあるのですが...)。
144
144
 
145
145
 
146
146
 

2

Y角度固定案を追記

2019/12/19 18:01

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -135,3 +135,141 @@
135
135
  }
136
136
 
137
137
  ```
138
+
139
+
140
+
141
+ **追記**
142
+
143
+ `transform.eulerAngles.y`を毎回0に書き換えるという方法だと、書き換え後の`transform.y`の方向は本来の向きからずれてしまうかと思います。ですが今回の場合では「向きを目標方向に近づける」→「オイラー角のYだけを0に書き換える」→「向きを目標方向に近づける」→「オイラー角のYだけを0に書き換える」...というプロセスをわずかずつ何度も繰り返すことによって、全体の連続性は保たれそうな感じです。ですので、そのやり方で問題がなさそうでしたらOKかと思います(後述しますが、本当にそのやり方でも全方向完璧に自然な回転を実現できるのかどうか不安はあるのですが...)。
144
+
145
+
146
+
147
+ 私の方でも何かY回転を0にする手がないか考えてみようと思い、下記のようなやり方を試してみました。前回同様にY周り角度の制限なしで向きを設定したあと、`transform.up`の向きを維持したままY角度を0にした回転に置き換えています。
148
+
149
+
150
+
151
+ ```C#
152
+
153
+ [SerializeField]
154
+
155
+ Transform TargetObject; //向きたい方向
156
+
157
+
158
+
159
+ Vector3 targetRelativePos = Vector3.up;
160
+
161
+ readonly float sqrMagnitudeThreshold = 0.1f * 0.1f;
162
+
163
+ readonly float speed = 0.1f;
164
+
165
+
166
+
167
+ void Update()
168
+
169
+ {
170
+
171
+ Vector3 relativePos = TargetObject.position - transform.position;
172
+
173
+ if (relativePos.sqrMagnitude > sqrMagnitudeThreshold)
174
+
175
+ {
176
+
177
+ targetRelativePos = relativePos;
178
+
179
+ }
180
+
181
+ Quaternion relativeRotation = Quaternion.FromToRotation(transform.up, targetRelativePos);
182
+
183
+ Quaternion targetRotation = relativeRotation * transform.rotation;
184
+
185
+ transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed);
186
+
187
+
188
+
189
+ // まず、元のローカル回転を使ってVector3.upを回したときにどの方角を向くかを求める
190
+
191
+ Quaternion oldRotation = transform.localRotation;
192
+
193
+ if (oldRotation.w < 0.0f)
194
+
195
+ {
196
+
197
+ oldRotation = new Quaternion(-oldRotation.x, -oldRotation.y, -oldRotation.z, -oldRotation.w);
198
+
199
+ }
200
+
201
+ Vector3 localUp = oldRotation * Vector3.up;
202
+
203
+
204
+
205
+ // localUpの各成分をもとにZ回転・X回転だけで同じ方向に向けるための角度を算出する
206
+
207
+ // このとき、Z角度を-90°~90°の範囲で設定するパターンと...
208
+
209
+ float phi1 = Mathf.Asin(Mathf.Clamp(-localUp.x, -1.0f, 1.0f)) * Mathf.Rad2Deg;
210
+
211
+ float theta1 = Mathf.Atan2(localUp.z, localUp.y) * Mathf.Rad2Deg;
212
+
213
+ Quaternion newRotation1 = Quaternion.Euler(theta1, 0.0f, phi1);
214
+
215
+ if (newRotation1.w < 0.0f)
216
+
217
+ {
218
+
219
+ newRotation1 = new Quaternion(-newRotation1.x, -newRotation1.y, -newRotation1.z, -newRotation1.w);
220
+
221
+ }
222
+
223
+
224
+
225
+ // Z角度を-180°~-90°、90°~180°の範囲で設定するパターンが考えられるが...
226
+
227
+ float phi2 = -180.0f - phi1;
228
+
229
+ float theta2 = -180.0f + theta1;
230
+
231
+ Quaternion newRotation2 = Quaternion.Euler(theta2, 0.0f, phi2);
232
+
233
+ if (newRotation2.w < 0.0f)
234
+
235
+ {
236
+
237
+ newRotation2 = new Quaternion(-newRotation2.x, -newRotation2.y, -newRotation2.z, -newRotation2.w);
238
+
239
+ }
240
+
241
+
242
+
243
+ // なるべく元の回転に近い方を採用する
244
+
245
+ Quaternion newRotation =
246
+
247
+ Quaternion.Dot(oldRotation, newRotation1) >= Quaternion.Dot(oldRotation, newRotation2)
248
+
249
+ ? newRotation1
250
+
251
+ : newRotation2;
252
+
253
+ transform.localRotation = newRotation;
254
+
255
+ }
256
+
257
+ ```
258
+
259
+
260
+
261
+ 動きは下図のようになりました。回転が2つの軸の角度だけに支配されていることを視覚的にわかりやすくしようと思い、[二軸ジンバル](https://ja.wikipedia.org/wiki/%E3%82%B8%E3%83%B3%E3%83%90%E3%83%AB)をイメージした台を置いてみました。
262
+
263
+
264
+
265
+ ![図](d0ff38e627061bd2e45757facd5a6bd3.gif)
266
+
267
+
268
+
269
+ 補足しますと、動かしている最中にインスペクターを観察するとY回転がちょくちょく180°回転してしまうかと思います。回転生成自体は`Quaternion.Euler(theta1, 0.0f, phi1)`といった具合にYが0になっているのですが、クォータニオンとオイラー角の相互変換をする上でのいたしかたない都合でこんなことになってしまいます(以前別の方の「[[Unity] オブジェクトのRotationの値を変化させたときの挙動について質問](https://teratail.com/questions/200674)」という件でもちょっと申し上げたことがありました)。
270
+
271
+
272
+
273
+ やってみた感想としては、だいたいの場合ではそれらしい見栄えになったものの、たまに姿勢がピョンと飛んでしまうことがあって不満が残りました(図の最後の方で-X方向に向けたときに顕著な飛びが起こっています)。これを解消しようとスムージングをかけたりするとピクピク現象を起こしてしまったりして、一筋縄ではいかなそうです。
274
+
275
+ 今回のように軸の回転を制限したり、あるいはご質問者さんの`transform.eulerAngles.y`書き換えによる回転固定を行うと姿勢の自由度が低下してしまって、どこかしらの方角で姿勢の急激に変化する特異点的な場所が発生するのは避けられないんじゃないかと思います。

1

改善案を追記

2019/12/19 17:50

投稿

Bongo
Bongo

スコア10807

test CHANGED
@@ -61,3 +61,77 @@
61
61
  }
62
62
 
63
63
  ```
64
+
65
+
66
+
67
+ **追記**
68
+
69
+ 非常にわかりやすい映像で参考になりました。
70
+
71
+ `Vector3.up`を真逆の方向である`Vector3.down`に向けて回すとき、その軌道がY軸周りのどの方角を経由するかは曖昧です。たとえ話として、もし地球が海も山もないなめらかな球体で今北極点に立っているとすると、そこから最短の経路で南極点へ行けと言われたときに、どの方角へ歩き始めても距離は同じです。ですが最終的に南極点へ到達して立ち止まったとき、自身のY軸周りの向きは選んだ経路によって変わってくるはずです。
72
+
73
+ この状況と似たような現象が起こっており、南極点付近だと目標地点がわずかにずれただけでも選択される最短経路が大きく変わり、最終的なY軸周りの向きがころころ変化してしまうのだろうと考えられます。
74
+
75
+
76
+
77
+ 改善案として「初期の頭の方向(`Vector3.up`)ではなく、現在の頭の方向(`transform.up`)から目標方向(`relativePos`)への回転を求め、それを現在の姿勢(`transform.rotation`)に上書きするのではなく追加適用する」というのはどうでしょうか?
78
+
79
+ 言葉で説明するとややこしくなってしまいすみませんが、下記のような回転方法ではどうでしょうか。
80
+
81
+
82
+
83
+ ```C#
84
+
85
+ [SerializeField]
86
+
87
+ Transform TargetObject; //向きたい方向
88
+
89
+
90
+
91
+ // 目標回転に代わって、目標方向を覚えておくようにする
92
+
93
+ Vector3 targetRelativePos = Vector3.up;
94
+
95
+
96
+
97
+ float sqrMagnitudeThreshold = 0.1f * 0.1f;
98
+
99
+ float speed = 0.1f;
100
+
101
+
102
+
103
+ void Update()
104
+
105
+ {
106
+
107
+ Vector3 relativePos = TargetObject.position - transform.position;
108
+
109
+
110
+
111
+ if (relativePos.sqrMagnitude > sqrMagnitudeThreshold)
112
+
113
+ {
114
+
115
+ targetRelativePos = relativePos;
116
+
117
+ }
118
+
119
+
120
+
121
+ // 現在の頭の向き、つまりtransform.upをtargetRelativePos方向に向ける回転を求め...
122
+
123
+ Quaternion relativeRotation = Quaternion.FromToRotation(transform.up, targetRelativePos);
124
+
125
+
126
+
127
+ // 目標姿勢は現在の姿勢に上記の相対回転を適用した姿勢とする
128
+
129
+ Quaternion targetRotation = relativeRotation * transform.rotation;
130
+
131
+
132
+
133
+ transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed);
134
+
135
+ }
136
+
137
+ ```