この記事はVCIアドベントカレンダーの記事です。
内容を三行で
- 単純にあるものの周りを回る(例:太陽の周りを地球が回る )のをスクリプトでやると、SetPositionを毎フレーム適用することになる
- それだと重くなるためカクカクになってしまう
- 空のオブジェクトを用意してSetAngularVelocityをするとなめらかで軽い
地球を回したい!(公転させたい)
太陽系モデルを作ろうと思ったとき、公転させなきゃねということで、とりあえず作ってみた際の試行錯誤を記事にしてみました。
以下のように回したときに、いかに軽くするかというお話です。
#VCsakkinonanahttps://t.co/3ItSmatSrM#バーチャルキャスト #virtualcast pic.twitter.com/d49I7tbrud
— さっきのなな☆彡vket5 デフォルトキューブ (@sakkinonana) December 15, 2020
前提条件として、太陽系の惑星は8個あり、また地球の月を始めとしたメジャーな惑星や準惑星が十個ほどあります。
したがって全アイテムを出した場合の公転する(自転も)天体は20個ほどになります。
アニメーションでやることもできるとは思いますが、シミュレーターなので、位置を指定できたほうが良いということでスクリプトで実現することにしました。
とりあえず思いついた以下のようなコードで回してみました。実際は軌道半径や時間はちゃんと計算して出しますが、今回の記事では重要ではないので簡略化してあります。
コード
sph = vci.assets.GetTransform("Sphere")
cub = vci.assets.GetSubItem("Cube")
local radius = 4
local time = 0
cub.SetLocalPosition(Vector3.up)
cub.SetAngularVelocity(Vector3.zero)
sph.SetLocalPosition(Vector3.zero)
function update()
time = vci.me.FrameCount/2000
sph.SetLocalPosition(Vector3.__new(math.sin(time)*radius,1,math.cos(time)*radius))
end
数が多いと重くなる
ところが、問題が置きました。いざ全部実装して並べてみると案外重い(当時)。色々調べるとsetpositionが多すぎて重くなっている面もありました。せっかく毎フレーム移動させても、フレーム落ちしては結果として動きがカクカクになってしまいます。泣く泣くsetpositionを間引きしましたがそうすると結局なめらかではありません。
#VCsakkinonanahttps://t.co/3ItSmatSrM#バーチャルキャスト #virtualcast pic.twitter.com/6SdeizDdgH
— さっきのなな☆彡vket5 デフォルトキューブ (@sakkinonana) December 15, 2020
考察
いつフレーム落ちするのかを事前に予知するのは難しく、そのせいでフレームごとに位置を指定したくなるのですが、そうするとその分計算が増え余計に負担をかけてしまいます。また、前回のアイテムの運動と回転について(1)でも述べたように、時間とPosition系の関数のタイミングがどうも合ったり合わなかったりしているようなので、これが原因かと考えました。
空オブジェクトを挟んでSetAngularVelocityを使う
一方で、fixedupdateという物があって、コチラは何があっても定間隔で更新してくれるではないですか。じゃあunityの物理演算に任せたほうがどういう場合に計算を細かくしてどういう場合にサボれるのかよく作られているはずだから、そちらのほうがいい結果が出るじゃないかと思いました。
VCIの階層構造
Cubeが空オブジェクト(わかりやすくするため立方体をいれているけど、なくてOK)です。
Cubeの設定
コード
sph = vci.assets.GetTransform("Sphere")
cub = vci.assets.GetSubItem("Cube")
-- 軌道半径を適当に入れる
local radius=4
-- -- velocityを使う方法
-- 初期化
cub.SetLocalRotation(Quaternion.Euler(0,0,0))
cub.SetVelocity(Vector3.zero)
cub.SetLocalPosition(Vector3.up)
-- 回す
sph.SetLocalPosition(Vector3.left*radius)
cub.SetAngularVelocity(Vector3.up)
結果
ではデバッグコンソールの統計情報を見てみましょう。
今回は最初に紹介した愚直パターンと、SetAngularVelocityを使う方法、加えてsubitemだと重くなりがちなので非subitemで同じく愚直パターンを行った場合の3パターンで比較してみました。
それぞれの数値が大きいほど重く、小さいほど軽いです。筆者はoculus rift-sのためmax80fpsです。
愚直パターンSetPosition(subitem)
SetPosition(非subitem)
提案手法(SetAngularVelocity)
大分減らせたのではないでしょうか。ActionCountPerSecはほぼゼロに、また、三角関数の計算とそもそも関数の実行がないためかluaProcessingTimePerFrameもほぼ0です。
愚直パターン | SetPosition(非subitem) | 提案手法(SetAngularVelocity) | |
---|---|---|---|
This | 79.1 | 76.68 | 0.01 |
column | 0.03 | 0.02 | 0 |
will | 0.16 | 0.16 |
余談1
ちなみに、スクリプトを更新直後はレートが落ちていて数値がやや暴れますが時間が立つとActionCountPerSec(ALL)が落ちてきてほぼ0になります。
余談2
回転中心と回転体の動きを分けて記述する際にも便利です。ルービックキューブのような偏心回転体や、螺旋運動などは空オブジェクトを利用すると便利ですね。