見出し画像

VRChat備忘録⑥ ワールド固定のやり方(おにぎりを軌道に乗せよう)

こんにちは、こむぎ* と申します。
VRChatの片隅を漂う無言勢の様な何かです。

今回は、以前の記事で書いた Parent Constraint の応用である、ワールド固定のやり方・原理について書いてみたいと思います。

以前の記事 ↓


***2022/11/23追記***

こちらの記事を書いた後で、より簡単な手順でのワールド固定の方法が発見されました。こちらの記事をベースに新しい手順で説明した記事を書きましたので、今後はそちらの方法の方がいいかも知れません。
そちらがうまく動かない場合などは、こちらの旧手順を使ってみてください。

以下旧手順。


はじめに

今回も、まず最低限の手順を書き、その後で解説を入れたいと思います。

正直やることはそんな多くないのですが、原理が分かりにくく説明しながらだとややこしくなりますので…

また、今回からは流石にアニメーション作成の細かい操作手順なんかは省きます。


手順1.アバターを原点に移動させる

スクリーンショット (518)

まず最初に、アバターの位置を原点(Position 0,0,0)にしておきましょう。
ここがズレていると後の手順で不具合が出る可能性が有ります。


手順2.アバター直下に空のGameObjectを2つ作る

スクリーンショット (519)

まず、Hierarchy上でアバター最上部を選択し、右クリックから空のGameObjectを2つ作成します。


スクリーンショット (523)

そしたら、それぞれの名前を、"Avatar_Center"、"World_Center" に変更します。

名前は分かればいいので、気に入らなければ好きな名前でもいいですよ。


手順3.固定したいオブジェクトの用意

スクリーンショット (524)

次に、先程作成した "World_Center" の配下に、ワールド固定で置きたいオブジェクトを配置します。

今回はおにぎりを投入しました。おにぎりが嫌ならおむすびでもいいですよ。


スクリーンショット (528)

オブジェクトを投入したら、前回のParent Constraint の記事の要領で、ワールド固定していない時に持たせる場所を設定しましょう。

手順を覚えてる方は、Parent Constraintの設定(サイズ・ポジション合わせ、目印の空オブジェクト用意、Sources登録、Zero押して有効化)まで行って、次の "手順4.ワールド固定用の設定を準備" に飛んでください。
手に持ったものを置く、だけなら Sources は手の一つでOKです。
上記画像みたいになってればOKです。

一応、以降で簡単に手順を書いておきます。


スクリーンショット (464)

今回は通常時は右手に持たせたいので、おにぎりをサイズ調整し、右手で持っている位置に合わせます。

スクリーンショット (465)

位置合わせが完了したらオブジェクトの配下に空のGameObjectを作成。自分が分かる名前に変えておきます。
今回は "Onigiri Hand.R Point" です。


スクリーンショット (468)

作成した空のGameObjectを追従させたいボーンの配下に移動させます。今回は右手のボーン(Hand.R)配下に。

Parent Constraintでの接続位置を複数にしたい場合(剣を手と腰で移動出来るようにしつつワールド固定もしたいとか)は、続けて二箇所目以降の位置合わせ→空のGameObjectの作成→適切なボーン配下へ移動、をやっておきましょう。以前のParent Constraint の記事と同じです。


スクリーンショット (529)

そうしたら、以前のParent Constraintの記事と同様に、おにぎりにParent Constraint のコンポーネントをつけましょう


スクリーンショット (531)

次は Sources に枠を追加して、先程用意した右手に持たせる目印の "Onigiri Hand.R Point" をドラッグ&ドロップで登録。

Parent Constraint の接続箇所を複数にするならその分だけ枠を作って入れておきましょう。


スクリーンショット (532)

最後にZeroのボタンを押して、初期化&有効化します。

これでオブジェクトの用意とParent Constraint の準備は完了です。


手順4.ワールド固定用の設定を準備

スクリーンショット (534)

最初に用意した "World_Center" に、Position Constraint と、Rotation Constraint を付けます。


スクリーンショット (536)

Position Constraint と、Rotation Constraint、両方の Sources の枠を一つ増やし、両方とも最初に作った "Avatar_Center" を登録します。


スクリーンショット (537) - コピー

次は、Position Constraint  Weigh を 0.5、Sources の数値を -1 にします。
Rotation Constraint と間違えないようにしてください。


スクリーンショット (537)

次は、Rotation Constraint  Weigh を 1、Sources の数値を -1 にします。
Position Constraint と間違えないようにしてください。


スクリーンショット (538)

最後に、両方の Zero を押して初期化&有効化しておきます。


手順5.Parent Constraint ON/OFFのアニメーションの用意

スクリーンショット (550)

次は、先程固定したいオブジェクト(今回はおにぎり)に付けた、Parent Constraint のコンポーネントを丸ごとOFF/ON するアニメーションを作っていきます。

後は、上記画像部分を OFF/ON するアニメーション作成及び、アニメーター&パラメーター&ExpressionsMenu でそれを切り替えられるようにするだけです。
物の出し入れと基本同じなので、出来る方は以下手順を読まずとも、サクサク組んでしまってOKです。
Parent Constraint がONの間は手に追従し、OFFにするとその場で固定される仕組みなので、EntryからはONのStateを繋いでおいた方が良いでしょう。
そこまで出来れば手順は完了なので、後は仕組みが読みたければ最後の7.仕組み解説をどうぞ。


さて、一応アニメーション録画以降も簡単に書いておきます。


スクリーンショット (479)

空のアニメーションを用意して


スクリーンショット (549)

適当にワールド固定用と分かる名前に変えます。
今回は "Onigiri Stop" です。


スクリーンショット (550)

お好きな録画手順(もしくは手動)で、おにぎりにつけたParent Constraint の 大本のチェックをOFFにするアニメーションを作成します。


スクリーンショット (554)

次は手に持つための、Parent ConstraintをONにするアニメーションを用意します。

今作ったParent Constraint をOFFにするアニメーションをCtrl+D で複製して、適当に名前を変えます。
今回は手に持つから "Onigiri Pick" にしました。


スクリーンショット (490)

複製したアニメーションを開いて、数値を1に。
Parent Constraint をONにするアニメーションが出来ました。


手順6.アニメーター・ExpressionsMenu等の用意

スクリーンショット (544)

アニメーション切り替え用のパラメーターを作っていきます。
アニメーターのParametersタブと、ExpressionParameters 両方に、適当な名前のBool型のパラメーターを作ります。
今回は "Onigiri Switch" です。


スクリーンショット (546)

アニメーターに新しいLayerを作成します。今回はワールド固定なので "Onigiri World" にしました。
Layer の歯車ボタンから、Weight を1にするのを忘れないようにしましょう。
また、新規Stateを2つ作って、名前を変更しておきます。
Entryから繋がってる方が手に持つ方、もう一つがワールド固定用です。
今回は手持ちが "Onigiri Pickup" 、ワールド固定用が "Onigiri Stop" です。


スクリーンショット (547)

それぞれのStateに、持つ方にはParent Constraint をONのアニメーション、ワールド固定用のStateにはParent Constraint をOFFのアニメーションを登録します。
いつものようにWrite Dfaults をOFFにするのも忘れずに。


スクリーンショット (551)

手に持つStateからワールド固定用のStateへの矢印は、画像右の様に先程用意したパラメーターがON(true)になったら遷移するようConditions を設定します。
Has Exit Timeのチェック外し、Transition Duration(s)も0にしときましょう。


スクリーンショット (551)

逆向きのStop→Pickupの矢印はパラメーターがfalseになったら遷移するよう設定しましょう。


スクリーンショット (553)

後はExpressionsMenuに、先程のパラメーターをToggleで切り替えるボタンを作って完成です。


動作確認用動画。最後に動作確認してみましょう。
というわけで、無事に海王星の軌道におにぎりを置くことが出来ました。タイトル回収です。



7.仕組み解説

さて、ここからはどういった仕組みで上記ワールド固定を実現しているかを、私が触って理解したような気がする範囲で説明してみたいと思います。

詳しい原理をまとめて解説しているサイトは見つけられなかった為、推測も含まれます。間違ってたらご指摘ください。


まず、VRChatではアバター側から直接ワールドの座標を指定して何かをする事は出来ないようです。
普通に出来るのは、自分のアバターからの相対的な座標を指定出来るだけなので、基本的には自分のアバターが動けば一緒に動いてしまいます。

上記ワールド固定のシステムは、先人の方が工夫して、疑似的にワールドに基準となる地点を作り、そこを基準に物を固定している素晴らしいシステムなのです!

もうちょっと具体的な話をすると…


1.疑似的に世界の中心を作る

スクリーンショット (559)

こちらの画像、今回最初に作った "Avatar_Center"、"World_Center" の動きが分かりやすいように、配下にCubeを入れたものになります。
正方形の①が "Avatar_Center" の下、細長い②が "World_Center" の下です。
※アバターは原点から少しズラしています。

細かい仕組みは順を追って説明しますが、まず、動画でキューブの動きを見てみてください。

正方形のCubeは自分にくっついて動き、動画後半の細長いキューブは全く動かなかったのが映っていたかと思います。
それぞれ、"Avatar_Center"、"World_Center" の下に無造作に入れてあるだけなので、実質 "Avatar_Center"、"World_Center" が同じように動作している訳です。

"Avatar_Center" は、自分のアバターの直下に入れてある為、アバターの上下左右の動き、向きの変更(回転)の影響を受けます。一言で言うと足元にくっ付いてきます。
こいつは、自分のアバターの位置の目印になるだけの存在です。

その上で "World_Center" は、上記手順の数値で Constraint を設定する事で

・"World_Center" は、"Avatar_Center" を対象にした Position Constraint の働きで、「アバターと反対方向に動き、結果として元の位置に戻る」
・"World_Center" は、"Avatar_Center" を対象にした Rotation Constraint の働きで、「回転しなくなる」

といった動作が起きています。
(設定した細かい数値の意味は後で説明します。)

結果として、"World_Center" は、アバターがいくら動いても、ワールドの中心から全く動かない仕組みになっています。

この仕組みで、まずはワールドの原点に基準となる物が作られました。


2.Parent Constraint 解除時の動作

後は、Parent Constraint の仕様を利用して、ワールド固定したいオブジェクト(今回はおにぎり)が "World_Center" についていくようにしています。

最初、おにぎりにの Parent Constraint はONになっており、Sources に右手(に仕込んだ目印)が指定されている為、手について移動しています。

そして、上記手順で作ったように Parent Constraint をオフにするとどうなるかと言うと…

スクリーンショット (563)

元々の構成が上記画像のようになっている為、Parent Constraint から解き放たれた Onigiri は、本来のツリー階層通りに、"World_Center" にくっついて動くようになります。
そして、Parent Constraint をオフにして解除した時の動作として、座標はそのままで本来のツリーに戻る、といった仕様が有るようです。

・結果、Onigiri は手にくっ付いて移動した位置のまま、"World_Center" の配下に戻る。
→"World_Center" は先程説明した仕組みでワールド中心から動かない。
→Onigiriも、手放した位置で動かなくなる。

と流れで、Onigiri は義理の親(右手配下目印)ではなく、真の親の元に帰り、親子ともども末永く動かなくなるのでした。

ちなみにParent Constraint を再度ONにすると、右手の目印の中心に戻っていくので振出しに戻ります。


3.Position Constraint、Rotation Constraint の数値について

スクリーンショット (564)

最後に、今回2種類のConstraintに設定した数値の意味を説明します。

かなり長くなりますので、とりあえずなんとなく仕組みが分かれば良い!という方は前の項番までの説明で十分です。
入力した数値の意味がわからないと気持ち悪い!という方は以下をお読みください。

ただし、大体有っているとは思いますが、正式な情報源を見つけられませんでした。各種サイトの情報を確認後、自分でいじってみて、理解したと思っている内容になります。
特にRotation constraint は納得のいかない動きをする為、バグの可能性も0ではありません。今後動作しなくなる可能性もある事をご了承ください。



4.Position Constraint の数値について詳細

まず先に、Position Constraint について説明します。
ちょっと複雑なので、まずイメージから説明します。

Position Constraint の Weight という数値は、拘束の強さ・影響力を表します。
Weight は0~1 で設定出来るのですが、1の場合は拘束力MAXで、Position Constraint を付けたオブジェクトの動きは、完全にPosition Constraint で制御されます。
具体的には、Sources に設定した物の動きの影響を受け、ツリー上で親になっているオブジェクトがいくら動いても、全く影響を受けなくなります。

ですが、Weight が1未満の場合、徐々に拘束力が弱くなり、無視されていた親の動きにも影響を受けるようになります。
Weight 0.7 なら、Position Constraintの影響7割、親の影響3割。
Weight 0.5 なら、Position Constraintの影響5割、親の影響5割。
Weight 0 なら、Position Constraintの影響は受けなくなり、完全に親の動きに影響されるようになります。


そして、Sources の数値は、Sources に登録した物からどの程度影響を受けるか、の数値になります。1なら1倍(等倍)、2なら2倍、-2なら反対方向に2倍動きます。

Position Constraint で動く量については、両方の数字を掛け合わせた物になります。


そしてオブジェクトの最終的な動きは、Position Constraint の動作分 + 元々のツリー上での親の動作の影響を受ける分、の合計値分動くことになります。

文章だけだと分かりにくいですか?
なら、以下に茶番を載せるので読んでみてください。
大体わかった人は茶番は飛ばして大丈夫です。

・茶番スタート
----------------------------------------------------

スクリーンショット (587)

上記の様に、親・子・先生が居たとします。
親・子は、その名の通りツリー上で親子関係に有ります。
Position Constraint さえ無ければ、子は親の事を100%聞いてくれる筈でした…

しかし子には、Position Constraint が付いており、Sources に先生が指定されているのです。
この時、Weight と Sources の数値によって、子供は親と先生どちらの言う事をどの程度聞くのかが変わってきます。
どの様な数値の時に、どの程度親と先生の言う事を聞くか見て行って見ましょう。


・Weight 1、Sources 1

スクリーンショット (587)

Weight(影響力)が MAX の 1 なので、子供は完全に先生の言う事に影響を受けます。親が何を言って来ても、プラスもマイナスも影響を受けません。シカトです。
Sources が 1 なので、先生の言う事は、先生が言った分だけ素直に聞きます。
100分勉強しなさい!と言われたら100分勉強します。


・Weight 0.7、Sources 1

スクリーンショット (588)

Weight(影響力)が 0.7 になりました。
先生の言う事に7割影響を受け、残りの3割は親の言う事も聞いてくれるようになりました。
Sources が 1 なので、先生の言う事は、言われた事の7割を聞きます。
親から200分勉強しなさい!と言われたら3割は聞くので60分、先生からも100分勉強しなさい、と言われたら7割の70分勉強します。
合計130分勉強する事になりました。偉いです。


・Weight 0.5、Sources -2

スクリーンショット (589)

Weight(影響力)が 0.5 になりました。
ついに親の言う事も50% 聞いてくれるようになりました。
先生からの影響も50% 受けるのですが…。なんと、Sources がマイナスになってしまいました。影響を受ける方向は反対になってしまいました。
Sources が -2 なので、先生の言う事は、言われた事の倍反発します。
親から200分勉強しなさい!と言われたら5割は聞くので100分は勉強の予定を立てます。
しかし、先生から30分でいいから勉強しなさい、と言われたら、影響力半分0.5✕-2倍で30分勉強時間を減らしてゲームをする事でしょう。先生に対する反抗期です…
合計で、100-60で40分しか勉強しない事になってしまいました。


・Weight 0、Sources -5

スクリーンショット (590)

ついに、Weight(影響力)が 0 になりました。
親の言う事を100% 聞いてくれるようになった代わり、先生からは良くも悪くも影響を受けなくなってしまいました。
Sourcesが -5 ですが、すでに先生の影響は全く受けないのでこの数値に意味はありません。
親から60分勉強しなさい!と言われたら、100% 親の影響だけ受けるので、素直に60分勉強します。
ですが、先生から頼む…5分で良いから勉強してくれ…と言われても、影響度0なので全く勉強時間が増えることは有りません。また、マイナスの影響も受けないので減る事も無いのです。先生との信頼関係は終わってしまいました…
最終的には合計で、60+0で、親に言われた60分だけ勉強する事になります。
----------------------------------------------------
・茶番終了


今回の "World_Center" の Position Constraint の設定は、Weight 0.5、Sources -1 でした。
その場合、Sources に設定した "Avatar_Center" の動きに対して -1、ただし Weight が 0.5 で半分の影響しか受けない為、-1 × 0.5 = -0.5 で、"Avatar_Center" の動きの反対方向に半分だけ動く、といった動作になります。

今求めた数値は Position Constraint 分の動きになります。
今回はWeight 0.5 なので、残りの 0.5 は本来のツリー構造での動きの影響を受けることになります。

"World_Center" の親はアバターの最上段なので、アバターを動かした量×0.5、で、アバターと同じ方向に半分だけ動きます。

そして、"Avatar_Center" はアバターの直下に入っている為、どちらも移動量は同じになります。

最終的に、"Avatar_Center" の移動量(アバター本体と同じ) × -0.5 + アバター本体の移動量 ×0.5 = 0 という式が成り立ち、最終的な移動量はアバターをどれだけ動かしても 0 になるのです。

これが、ワールド固定時の Position Constraint の動きです。



5.Rotation constraint の数値について詳細

次に、Rotation Constraint について説明します。

が、こちらは Position Constraint とほぼ同じ動きをします。
Weight の意味は同じで、数字次第で Rotation Constraint のSourcesに登録した物と、親の回転と、両方の影響を受けます。
Weight の数字の意味も同じで、1 ならRotation Constraint の影響のみで、0.5 なら半分ずつ影響を受けます。

ただ一つ Position Constraint と違う部分は、Sources に指定出来る数値です。
プラスの数値を指定している限りは、Position Constraint と同じです。
Sourcesに登録した物の回転量 × Sourcesの数字 × Weight の数字、で回転量が計算されます。


スクリーンショット (584)


先程の茶番の構成の、"親" の配下に "子" という構成で、"子" に Rotation Constraint を設定し、Sources に "先生" を指定しました。
Weight 0.5、Sources 0.5 です。
親が120度回転、先生が反対方向に-100度回転した場合、

・Weight 0.5 なので、先生の影響・親の影響半分ずつ
・Rotation Constraint 分の回転については、
 先生の -100度 × Weight 0.5 × Sources 0.5 で、-25度。
・親からの影響は、120度 × 0.5 で、60度。
・60-25で、最終的に35度回転

となります。
基本的な考え方、計算は Position Constraint と同じです。
Sources がプラスで有る限りは。


では、Rotation constraint の Sources にマイナスの指定をするとどうなるのかと言うと…

Rotation Constraint 分の回転が、ピタっと止まります。
Position Constraint の時のように、反対に動いたりはしません。固まってしまいます。
-0.1 でも、-100 でも、全く動かないので動きは変わりません。(たぶん)

あくまでも止まるのは、先程説明したうちの Rotation constraint分、だけになります。
Weight が1未満の場合の、本来の親からの影響は残ります。


スクリーンショット (585)

例として上記画像のように Weight 0.5、Sources -1 として、先程の例と同様に親が120度回転、先生が反対方向に-100度回転した場合…

・Weight 0.5 なので、先生の影響・親の影響半分ずつ
・Rotation Constraint 分の回転については、
 先生の 回転は-100度 だが、Sourcesがマイナスなので動作しない。
・親からの影響は、120度 × 0.5 で、60度。
・0+60で、最終的に親の影響の60度のみ回転

となります。

それを踏まえたうえで、今回の "World_Center" の Rotation constraintの設定は、Weight 1、Sources -1 でした。
その場合、Weight が 1 なので、Rotation constraint の影響のみ受け、親の回転の影響は一切受けなくなります。
ただし、Sources が -1 の為、Rotation constraint も動作しなくなり、"Avatar_Center" の影響も受けなくなります。

結果として、アバター読み込み時の世界の中心で、全く回転しない状態になります。

これで、Rotation constraint の説明は終わりです。


というわけで、
Position Constraintの働きで初期地点からの移動を防ぎ、
Rotation constraintで回転も止め
結果としてワールド原点から動かない不可視のオブジェクトがそびえたつ、といった仕組みでした。


長くなりましたが、これでワールド固定及び、それに使った機能の説明は終わりです。お付き合いいただきありがとうございました。

次回は、ワールド固定を利用した残像の作り方か、Rotation Constraint を使ったアバターの分身(同期して動くアバター)のどちらかを書いてみたいと思います。


おまけ ワールド固定の構成プラスワン

今回は学習用と言う事で、ワールド固定の仕組みを最小の構成で作りました。

スクリーンショット (591)

このように、ワールド中心の動かないオブジェクト配下に直接おにぎりをINしました。

ですが、この組み方だと固定するオブジェクト自体(今回はおにぎり)が固定位置を覚えている為、Parent Constraint  をONにして手に戻した際に、固定位置がリセットされてしまいます。

同じ固定位置にまた戻したい場合や、その他の目的にワールド固定を利用したい場合は、間に一つオブジェクトを挟むことでワールド固定位置を別に記録することができます。


スクリーンショット (595)

まず、上記手順で固定したいオブジェクト(おにぎり)に設定したのと同じ要領で、"World_Center" 配下に空のオブジェクトを作って、先程と同じようにParent Constraint を設定します。

これで、先程おにぎりを固定したのと同じ要領で、空のオブジェクトをワールド固定できるようになりました。


スクリーンショット (597)

あとは、アバター直下にでもおにぎりを入れて、Parent Constraint の接続先に、先程のワールド固定出来る空のオブジェクトを指定するだけです。

こうする事で、ワールド固定をする仕組みと、おにぎりの位置を変える仕組みが分離される為、おにぎりを手に戻してもワールド固定の位置の記録は変わりません。
その代わり、管理するパラメーターやアニメーション、アニメーターを組む数は増えることにはなりますが…
※アニメーターなどの具体的な組み方は割愛します。


こんな感じで、ワールド固定した位置に戻すことが出来ます。

ちなみにおにぎりがゆっくり移動してるのは、アニメーターの矢印部分でTransition Duration(s)を0.5ぐらいにしてあるからです。
ココが0じゃないとゆっくり遷移します。
今までの説明では瞬時に持ち替えるために0にしてましたが、お好みで。

この空のオブジェクトをとりあえずワールド固定する仕組みは色々応用出来るので、とりあえず覚えておくと芸の幅が広がります。

おわり。

この記事が気に入ったらサポートをしてみませんか?