【Unity.UdonGraph】VRChatのワールドギミック作例集

世のUdon解説が軒並みU#でGraphの作例が少なすぎるので、私が自分のワールドで使っている(ものの改良・調整版)UdonGraphの作例をぶん投げる記事です。なお作者はプログラミング素人です。

 

1.はじめに

1.この記事を読むのに前提となる知識

まずはこのへんを読んでくれ。

Udon のはじめかた|toh|note

sdk3のudonでミラーをオンオフする(ついでに同期させる)|かくちゅ|note

Udon初めの初歩の初歩|tenehimu|note

 特に上二つは絶対読んでできれば実践しておいたほうがいい。最初はコピペでいいのでとにかくやってみる。するとあとからだんだん理屈がわかってくるので。

以下は、「UdonGraphの基礎は分かるけど具体的に”どうすれば何ができるのか”が分からない」という人に向けて、「少なくともこうすればこう動く」という作例を紹介していく。「Udon触るの初めてです」という人に分かるようには書いていないし分かる人から見ても省略しすぎてわかりにくいかもしれない。

2.この記事での凡例について

思いつくままに書いているので書き方はあまり統一されていないのだが、ノードの名前の表記はすべて「○○.△△」といった形になっている(はず)。これはノードを探すときに「まず○○で検索して、次に△△で検索して出てくるもの」という意味。U#(C#)のスクリプトの形式とは一切関係ない。

 

2.作例集と解説少々

1.オン/オフ切り替え系

上の参考記事ではオブジェクト(ミラーとか)だったものの対象が、AudioSourceだったりColliderだったりMeshRendererだったりUdonBehaviorだったり、まあとにかく「オフ→オン(オン→オフ)する」タイプのGraph。

1-1.AudioSource

BGMや環境音をつけたりけしたりする。ミラーと同じくらいよく使うやつ。

基本の構造は、

「”PlayOnAwake”にチェックしてオブジェクト自体をオンオフする」または、

「AudioSourceにAudioclipの再生/停止を命令する」のどちらか。

①の場合はミラーと同じなのでGraphは省略。gameobject変数に、目的のAudioSourceがついたオブジェクトを指定すればよい。

②の場合。基本構造は①と同じで、「GameObject.SetActive」のノードを「AudioSource.Play(Stop)」に差し替える。使う変数は「AudioSource」。

f:id:It__was__rainy:20210512002129p:plain

1-2.Collider/MeshRenderer/UdonBehavior ect...

上記のような「あるオブジェクトの特定のコンポーネント」をオンオフする。

基本の構造は、

①「目的のコンポーネントに対応した変数を使う」

②「GameObject変数から目的のtypeのコンポーネントを取得する」

のどちらか。

①の場合。変数は目的のコンポーネントと同じ名前のもの。GameObjectでいうSetActiveと同じような働きをするのがSet enable。

f:id:It__was__rainy:20210512002043p:plain

 

②の場合。変数はGameObject。typeは目的のコンポーネント。GameObject.GetConponentは指定したオブジェクトのさらに特定のコンポーネントを取得する。

f:id:It__was__rainy:20210512002110p:plain

 

これの利点は、「自分を対象とするオンオフ機能のあるオブジェクトを量産したい」ときに、変数にいちいち対象を指定しなくていいこと。(GameObjectや後述のTransformの変数は特に指定がない場合UdonBehaviorのついたオブジェクト自身が対象になる。ここであげたコンポーネントや上のAudioSourceは指定しないとNoneになる。)

「きらきら星の階段」に踏むと出現する階段があるが、あれは②の方法で作った階段を大量にコピペして作っている。

 

2.オブジェクト移動・回転系(Transformをいじる)

オブジェクトを移動させたり回転させたりする。プレイヤーのワープもここで扱う。

2-1.オブジェクトの移動・追従

使う変数・ノードは「Transform」系統。「Transrate」と「Set position」の何が違うのかは自信がないんですが、Transrateは移動距離の指定(相対的)でSetpositionが座標の指定(絶対的)?公式スクリプトリファレンス英語なのでちゃんと読めてるかどうかわからない。

→「Translate」は「現在地から、X.Y.Z軸方向に指定した値だけ平行移動する」、「Set position」は「現在地にかかわらず、X.Y.Zに指定した座標に飛ばす」といった処理になるらしい。

 

追従の場合はupdateで始めて移動先に追従先オブジェクトの座標を取得して渡す。画像は追従先がプレイヤーなのでやや複雑だが、普通のオブジェクトとかなら「Transform.Get position」で座標を取得すればいいと思います多分。

f:id:It__was__rainy:20210512004149p:plain

「指で触って弾けるピアノのギミック」に使用した、プレイヤーの指のボーンにオブジェクトを追従させる仕組み。

2-2.オブジェクトの回転

変数・ノードは「Transform」系統。これもたぶん上と同じ感じで「Rotate」と「Set rotation」がある。

f:id:It__was__rainy:20210512002102p:plain

「別のオブジェクトのRotationを反映させる」。

カメラの遠隔シャッターでカメラ本体の向きを調整するために使用。

2-3.プレイヤーをワープさせる

使うノードは「Networking.Get locaoplayer」「PlayerAPi.TeleportTo」。ワープ先の位置・向きは手打ちで指定してもいいし、適当なオブジェクトを置いてそいつのTransform値を取得して代入してもいい。後者が楽。

f:id:It__was__rainy:20210512002022p:plain

 

3.Animatorを操作する

オブジェクトにAnimatorコンポーネントをつけてAnimatorControllerを放り込み、そいつの中身をいじる。使う変数・ノードは「Animator」系統。

3-1.Animationを再生する

「Animator.Play」で特定のステートを再生できる。レイヤーが複数あるとレイヤー名も指定しないといけないらしいが、どう記述するのか忘れました。まあアニメーション再生するならステート名で指定するより後述の変数切り替えでステート遷移させるほうが確実なのでそっち使いましょう。

f:id:It__was__rainy:20210512034836p:plain

3-2.変数を切り替える

AnimatorControllerの中身をアバター3.0でやってるみたいに設定したうえで、ExpressionMenuで操作するみたいに変数を切り替えるGraph。使うノードは「Animator.Set○○」(指定したい変数と同じ型)。

f:id:It__was__rainy:20210512035415p:plain

3-3.speedをいじる(疑似的な一時停止)

 Animatorの各ステートにはアニメーションの再生スピードを設定する項目があるが、その値をいじることができる。これを0にするとアニメーションの一時停止を再現できる。使うノードは「Animator.Set speed」。

f:id:It__was__rainy:20210512035458p:plain

f:id:It__was__rainy:20210512035520p:plain

元々はAnimatorのspeedの値を取得して、0かそうでないかを判定して一時停止と再生を再現したかったのだが、なぜかうまくいかなかったのでスイッチを二つ用意してスイッチごと切り替える方法をとっている。脳筋か?

 

4.Colliderの衝突を検知する

ColliderとRigidbodyのあるオブジェクトはCollider同士の衝突を判定できる。

基本的な処理の流れとしては、「OnTriggerEnter(OnCollisionEnter)で何らかのオブジェクトの衝突を検知する」→「衝突してきたオブジェクトが何であるかを判定する」→「目的のオブジェクトだった場合次の処理に進む」といった感じだと思われる。

Enterが当たったとき、Exitは当たってから離れたとき、Stayは使ったことないけど多分接触してる間ずっと処理が続く。

4-1.OnTrigger~

 衝突する/されるオブジェクトの少なくとも1つにおいて、Colliderコンポーネントの「IsTrigger」が有効であり、かつRigidbodyコンポーネントを持つ場合に用いる。(IsTriggerとはざっくりいうと当たり判定をなくす。)使用する変数はGameObject、ノードは「Collider.Get GameObject」「GameObject.Equal」

f:id:It__was__rainy:20210512035026p:plain

4-2.OnCollision~

衝突する/されるオブジェクトの両方が当たり判定とRigidbodyコンポーネントを持つ場合に用いる。基本の構造は上と同じだが、「Collider.」系統だったところが「Collision.」系統になっていることに注意。

f:id:It__was__rainy:20210512035143p:plain

4-3.OnPlayerTrigger~

 プレイヤーがオブジェクトに接触したことを検知する。特に条件を付けない場合、自分であれ他人であれとにかく誰かが接触すると処理が始まる。つまりグローバル。ローカルにしたい場合は「接触したプレイヤーが自分であるかどうか」の判定を挟む(後述)。

Trigger系統なので、対象のオブジェクトはIsTriggerが有効である必要がある(Rigidbodyは必要ない)。当たり判定がなくなるので、地面とかに設定する場合は別にコライダーを用意しておかないとすり抜ける。

「当たり判定がなくなって困るならCollision系統使えばいいじゃないか」と思うかもしれない。私も思った。だが動作しないんだよ。なんで?どうもOnPlayerCollision~系統は使えないらしいが…

f:id:It__was__rainy:20210512002119p:plain

「プレイヤーの接触で音を鳴らす」

5.skyboxを切り替える

使用する変数はMaterial、ノードは「RenderSettings.Set(Get) skybox」。

「今のskyboxがnewvaliableじゃないとき、newvaliableのskyboxに切り替える」。

f:id:It__was__rainy:20210512001953p:plain

6.ランダムな値を生成する

「Random.Range」というノードを使うと最大値と最小値を指定してその中からランダムな値を1つ取り出せる。[]のついた変数は「配列」というものらしく、複数の対象を並べることができる。この配列の中から1つ選んで表示する、というのが下のGraph。

f:id:It__was__rainy:20210512002034p:plain

floatは「~以上~以下」だがintだと「~以上~未満」なので注意。

7.値を保存する

変数をD&DするときにCtrlを押しながらやると「Set valiable」というノードになる。これを使うと変数に値を代入できるので、例えば「動かしたオブジェクトを初期位置に戻したい」というときに使える。 

f:id:It__was__rainy:20210512024140p:plain

「Cube」の初期位置をVector3という変数に保存し、「Sphere」の移動先の座標として使っている。

8.”AまたはBまたはCまたは…”を再現する

「Array.IndexOf」というノードを使うと、「AのときもBのときも同じ処理を起こしたい」という仕組みを一つのUdonbehaviorで完結させられる。下の例は、Colliderの衝突相手の判定に複数のオブジェクトを指定できるGraph。なぜそうなるのかという理屈は下のツイート参照。

f:id:It__was__rainy:20210512002139p:plain

「fingers」で指定したオブジェクトすべてをColliderの接触相手として判定する。

 

9.プレイヤーが自分であるかどうかを判定する

前でちょっと触れた、オブジェクトに接触したり、椅子に座ったりしているプレイヤーが「自分かどうか」を判定するやつ。使うノードは「Networking.Get LocalPlayer」「PlayerAPi.Equal」。

f:id:It__was__rainy:20210512025546p:plain

「座ったプレイヤー」が「自分」と「等しいかどうか」

3.UdonBehaviorとuGUIを連動させる

CanvasUIパネルからUdonの処理を呼び出す。自分がやったのはSliderだけなので紹介はそれだけだが、Toggleなら同じようにしてinteractを呼び出すようにすればいいと思う。

なお、私が試した限りではどうあがいても同期しなかった。同期するSliderについてはAnimatorを使うシステムが公開されてるのでそちらをどうぞ↓。

Synced slidersに自作の機能を加える - Qiita

 

1.Sliderでオブジェクトのシェイプキーを変化させる

1-1.UdonGraphの組み方

使う変数は「UISlider」「SkinedMeshRenderer」、ノードは「SkinedMeshRenderer.Setblendshapeweight」「UISlider.Get Value」。どのシェイプキーを動かしたいかは「SkinedMeshRenderer.Setblendshapeweight」の「index」で指定する(上から順に・0から数えること)。

 

画像は同期させたくて頑張ってた跡なので余計なものが多い。グループ化してる部分はいらないしどうあがいても同期しないのでcustomevent化する必要もない。あとAdditionも「0を足す」設定なのでこれもいらない。

f:id:It__was__rainy:20210512030604p:plain

「一番上のシェイプキー」に「Sliderの値」を代入する。
1-2.Slider側の設定

「On Value Change」に対象のUdonBehaviorがあるオブジェクトを指定し、「UdonBehavior.OnValidate()」を選ぶ。今回動かすのはシェイプキーなのでMinValue・MaxValueをシェイプキーの最大・最小値に合わせる。

f:id:It__was__rainy:20210512031448p:plain

4.応用・番外編

1.Eventノードを組み合わせて処理を行う条件をつくる

Eventノードには「EnterとExit」「UpとDown」のように、条件的に対になっているものがある。これをうまく組み合わせると、例えば「オブジェクトを持っている時だけ」「椅子に座っている時だけ」といった条件を再現できる。できるが、多分”オブジェクトがPickupされてるかどうか””プレイヤーが椅子に座ってるかどうか”をBranchで判定する仕組みは作れるのでこんなことしなくてもいいと思います。作ったときは思いつかなかったんです…。

1-1.「持っている時だけあるUdonBehaviorが有効になる」

カメラの遠隔シャッターを持っている時だけ、Rotationの共有を有効にするために使用。

f:id:It__was__rainy:20210512033703p:plain

 

1-2.「自分が座っている時だけあるオブジェクトが表示される」

自分が椅子に座っている時だけ、椅子の高さを変えるスイッチを表示するために使用。

f:id:It__was__rainy:20210512033853p:plain

2.椅子の高さを変える

「今の椅子のy座標」に「0.1/-0.1を足す」ことで、interactするたびに上がったり下がったりする。

f:id:It__was__rainy:20210512034325p:plain

3.Questにおけるワールド入室時のBGM鳴らない問題の回避

f:id:It__was__rainy:20210512035839p:plain

4.視界ジャック

canvasのrender modeを「Screen SpaceーCamera」にすることで視界ジャックをすることができる。オンオフはGameObjectで組める。↓はワールド設置カメラで撮った画像を視界ジャックでスクショしやすくするシステム

f:id:It__was__rainy:20210512133859p:plain

f:id:It__was__rainy:20210512133933p:plain

5.デバッグログ

「Debug.Log」ノードを使うと、変数の値や「Get~」系のノードで取得した情報をConsoleに表示できる。思った値が取れているか、動かない場合どこに原因があるのかの判断に役立つ。f:id:It__was__rainy:20210512134716p:plain

f:id:It__was__rainy:20210512134742p:plain

Vector3変数「CubePosFirst」にちゃんと初期位置が代入できていることがわかる。

5.おわりに

とりあえず思いつく限りで汎用性のありそうなものは全部書きました。思い出したら追記するかもしれません。また、「これ解説してほしい」とか「この項目もっと詳しく」という要望があれば、私に答えられれば追記したり別記事立てたりします。しますが、作者はプログラミング素人故分からんことのほうが多いと思いますので期待はしないでください(そもそも解説したやつでも人に教えてもらったものが多い)。逆に言うと素人でもここまでできるようになる。UdonGraphすごい。SDK2でええやんとか言わない。

 

解説したGraphの一部を使用したギミックをBoothで配布していますので、中身が気になる方はどうぞ↓

雨宿り - BOOTH