🛫

Cluster Creator Kit でそれっぽい飛行機を実装する

2021/12/04に公開

この記事は クラスター Advent Calendar 2021 4日めの記事です。
昨日は @neguse_k さんの『OSSのglTFライブラリにPull Requestを出した件について』でした。自分たちが使う必要十分に留めず contribute してるの偉すぎるし、その結果 maintainer の方が "I like this approach!" と言ってマージしてるのもほっこりしました。

こんにちは、クラスター株式会社でソフトウェアエンジニアをやっているねおりんです。
Cluster Creator Kit の開発などをしています。

Cluster Creator Kit は、メタバースプラットフォーム cluster で体験できるワールドの構築・アップロードツールキットです。
11月にリリースした Creator Kit の「乗り物」機能では、アバターをアイテムに固定、さらに移動操作を委譲できるようになったことで、様々な乗り物を作れるようになりました。

ところで、乗り物の中でも空を飛ぶもの、飛行機の挙動は奥が深く、単純な力の加え方をすると宇宙船のような動き(?)になってしまいます (もちろんそれはそれでよい / 操作はカジュアルでしやすい)。これを少しリアルな挙動に近づけたい場合は、空気力学的な効果を雑な近似で実装するだけでもかなり動きがよくなります。
この記事では、 Creator Kit でちょっとリアルっぽい飛行機の挙動を作る方法と、その考え方を紹介します。

一般的な (スクリプティングが可能な) Unity での実装については、参考に記載のページもぜひ参照してみてください。揚力の解説などがわかりやすく、大変参考になりました。

Trigger

Steer Item Trigger の move input triggers でピッチング・ローリングの制御をするので、 input を move に入れつつ CalcForces でトルクと揚力の計算をします。
また additional axis input を使ってプロペラの制御 (推力の計算) をしていますが、特別なことはしていないので説明を割愛します。

さらに velocity が変化したときには CalcDrags で抗力の計算も行います。ここで velocity は local space (Space の指定が自身)であることがポイントです。
On Velocity Changed Item Trigger

Gimmick

torque (トルク) と、 liftVector (揚力) を反映します。すべて Space は自身です。

drag angularDrag (抗力) は Animator を使って rigidbody の Drag と Angular Drag を書き換えます。

Animator を使ってパラメーターを書き換えるときは Motion Time が便利です。

最大10だと少し心もとないけど100もあれば実用上十分だろうと判断したので、0~100のレンジで animation を用意しました。

このとき curve を linear にするのを忘れないようにしましょう。(あえて curve を掛けたいときには Logic でなく animation でやるというテクニックもありそうです)

Logic

それでは Logic です。とにかくめっちゃ雑な近似で計算していきます。

座標系はすべて local space です。(global space で計算しようとすると、機体の姿勢を知る必要があるためです)

揚力とトルク

揚力は迎角と速度 (つまり velocity) から計算します。Gimmick で (自身の上方向にではなく) vector を適用しているように、揚力ベクトルは機体に対して直角ではなく、気流 (進行方向) に対して直角になるのがポイントです。

トルクは、昇降舵と補助翼によるピッチング・ローリングの再現なので、速度と迎角の影響を考慮します。

(画像の下に擬似コードを用いた解説があります)


CalcForces() {
  length:Float = Vector3.Length(velocity)
  // ゼロ除算の回避
  if (length > 0) WhenVelocityGreaterThanZero()
  if (length <= 0) WhenVelocityIsZero()
}

WhenVelocityGreaterThanZero() {
  // 進行方向の正規化
  normalized:Vector3 = velocity / length
  // 迎角 (気流に対する翼の角度 = 進行方向に対する機体の正面) による効果の代わりに内積
  aeroFactor:Float = Vector3.Dot(Vector3.forward, normalized)
  // 2乗で適当に curve を表現
  aeroFactor:Float = aeroFactor * aeroFactor
  // 速度 (正面方向)
  forwardSpeed:Float = Math.Max(velocity.z, 0)
  // 揚力係数を速度と迎角から計算
  liftFactor:Float = forwardSpeed * forwardSpeed
  liftFactor:Float = liftFactor * aeroFactor
  // 揚力ベクトルは気流に対して直角 (翼に対して直角でないことに注意)
  // 逆方向に進んでいるときに上下反転しないように2回外積を取る
  liftVector:Vector3 = Vector3.Cross(normalized, Vector3.up)
  liftVector:Vector3 = Vector3.Cross(liftVector, normalized)
  // 揚力ベクトルに揚力係数を掛ける
  liftVector:Vector3 = liftFactor * liftVector
  // トルクの大きさは速度と迎角の影響を受ける
  torque.x:Float = move.y
  torque.z:Float = move.x
  torque:Vector3 = torque * forwardSpeed
  torque:Vector3 = torque * aeroFactor
}

WhenVelocityIsZero() {
  aerodynamicEffect:Float = 0
  liftVector:Vector3 = Vector3.zero
  torque:Vector3 = Vector3.zero
}

抗力

抗力にも、本来は迎角を元にした係数が掛かるのですが、より単純な速度による係数のみでも十分それっぽくなりました。

CalcDrags() {
  length:Float = Vector3.Length(velocity)
  // 係数は適当
  drag:Float = length * 0.001
  drag:Float = drag + 0.1
  // 中略 (入力によって airbrake を加える場合はここで)
  forwardSpeed:Float = Math.Max(velocity.z, 0)
  angularDrag:Float = forwardSpeed * 0.1
  // Animator の Motion Time に入れるために /100
  drag:Float = drag * 0.01
  angularDrag:Float = angularDrag * 0.01
}

おわりに

物理っぽい挙動を作るときは、雑な近似でも十分にそれっぽくなるのでぜひ試してみてください!
今回紹介した以外にも、空気力学的な効果として、機体のピッチを進行方向に向けようとする力などもわずかに加えてあげるとさらにそれっぽくなりそうです。

ちなみにこの記事で紹介した飛行機は はとりんひこうじょう で乗れます。滑走路で加速していくと揚力でふわっと離陸するのを体験してみてください。

明日は @tommy96 さんの『clusterに入ってからの戦いの歴史を書く』です。クラスター、戦いらしいです😱

参考

また Standard Assets の Aeroplane の実装も参考にしました。

Discussion