はじめに
VRChat(でワールド制作)を始めて3年くらい経ちました。この文章を書き始めたときは2年だと思っていたんですが、ちゃんと数えたら3年でした。噓でしょ、時の流れ早すぎ……。
それはともかく、私はワールドを作り始めてから今までずっとUdonGraphをこねていました。プログラミングなんて縁があるとも思っていなかったのですが、今では知識 0 から知識 0.5 くらいにはなった気がします(最大値が100くらいのとき)。
最近はシェーダーとかエディタ拡張とかをちょこっと書いてみたりもしています。そのおかげか、スクリプトを読んだり書いたりすることへの抵抗感が薄くなってきたので、これを機にUdonSharpとも戦ってみることにしました。
Graphでやっていたことがベースになるので書き方さえ覚えればあんまり難しくはないのですが、ひとつだけ、同期変数に関連したあれこれの書き方が難しいというか、Graphと違いすぎて理解に時間がかかったので備忘録を兼ねて書いておくことにしました。
というわけで前置きが長くなりましたが、UdonSharpでの同期変数の書き方について、Graphと比べながら中身を見ていきます。素人が自己解釈で好き勝手やってるので間違ってたら教えてください。
前提知識
・UdonGraph
【VRChat】Udon Graph 入門【SDK3】 #VRChat - Qiita
・UdonSharp
UdonSharpメモ ~(最初だけ)はじめての人に向けて書いてみる~
入門 ②の処理をOnValueChangedを用いて書いてみる - ハツェの真時代傾向璋
・同期の話
(理屈の部分だけ、スクリプトはわからなくても多分ダイジョブ)
UdonGraph編
とりあえず、同期変数を使った簡単な例として「グローバルかつLateJoinerにも同期するオブジェクトON/OFF」をGraphでつくります。
誰かがスイッチをInteractしたら、その人をスイッチのOwnerにして同期変数を変更します。この時、Set Value ノードでは sendChange が有効になっています。これにより、Value Change ノードを使えば同期変数の変更を自動的に検知して任意の処理(ここではtargetObjectの表示/非表示)を行うことができます(ManualSyncMode の場合は、Set Value の直後に RequestSerialization ノードを呼びます)。
・synced を有効にする(左上Variablesのところ)
・Set Value ノードで sendChange を有効にする
・Value Change ノードから任意の処理を行う
視覚的にも直感的にもやっていることがわかりやすく、手間も少なくていい感じですね。
UdonSharp編
では、上のGraphをU#のスクリプトに書き直してみます。
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
public class SyncedObjectToggle : UdonSharpBehaviour
{
public GameObject targetObject;
[UdonSynced, FieldChangeCallback(nameof(syncedValue_Property))]private bool syncedValue;
public bool syncedValue_Property
{
set
{
syncedValue = value;
targetObject.SetActive(syncedValue_Property);
}
get =>syncedValue;
}
void Start()
{
syncedValue = targetObject.activeSelf;
}
public override void Interact()
{
Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
syncedValue_Property = !syncedValue_Property;
}
}
Graphとの対応をざっくり書くとこんな感じ。たぶん。
まず変数の定義のところで、同期変数には [UdonSynced] という属性をつけます(変数の頭についている[]で囲まれた部分を属性というらしいです)。Graphでは変数の synced にチェックを入れていますが、これがその部分にあたります。
さらに、[FieldChangeCallback] という属性もつけます。これがどうやら Set Value ノードの sendChange チェックにあたるらしい……のですが、Graphとは違ってなにやら syncedValue_Property という別の名前が追加されています。そして、青で囲まれた部分でこの新しい名前について何か書いているようです。
この青の部分が、私がはじめ理解できなかったところです。
変数とプロパティ
syncedValue が変数なのに対して syncedValue_Property はプロパティというそうですが、私は今までこの2つの違いを認識していませんでした。
変数とプロパティについて、私は上記を参考にふわっと理解しているのですが、変数を操作するときに間に立ってくれる代理人のようなイメージをしています。
U#で Value Change を書くときは、この代理人を通して変数を操作してあげる必要があります。
同期変数とプロパティの部分を見てみます。
[UdonSynced, FieldChangeCallback(nameof(syncedValue_Property))]private bool syncedValue;
public bool syncedValue_Property
{
set
{
syncedValue = value;
targetObject.SetActive(syncedValue_Property);
}
get =>syncedValue;
}
Graphでは、syncedValue という同期変数の変更を伝えたいとき、sendChange を有効にしていました。
U#では、変更を伝えてくれる代理人の名前をつける必要があります。なんでもいいのですが、とりあえず後ろに _Property とつけてこれがプロパティであるとアピールしておきます。忘れたときに読み返しても混乱しなさそうなので。
そうしたら、具体的に何を伝えるのかということを代理人に教えておきます。
get=>syncedValue;
とは「syncedValue_Property を読み取るときは、syncedValue と同じ値を返す」みたいな意味だと思います。たぶんそんな感じ。多分。
set { syncedValue = value; }
とは、「syncedValue_Property を操作したときは、syncedValue にも同じ値を渡す」みたいな意味でしょう。きっと恐らく。
裏を返せば、set{} の中身は syncedValue_Property を操作したときに呼ばれるということなので、ここに任意の処理を書いておくことでGraphでいう Value Change ノードと同じことができるのだと思います。ということで、今回はオブジェクトのアクティブ状態を変更する処理を書きました。もちろん CustomEvent も呼べます。
これで代理人に必要な情報を与えることができたので、自由にこの同期変数を操作できるようになりました。
注意するべきなのは、値を変更するときは必ず代理人を通す、つまりプロパティの方を扱います。偉い人とお話するときは窓口を通してアポをとらなければいけないのと似ていますね。でも会えない可能性がある偉い人とは違って、プロパティさんは必ず変数さんにアクセスしてくれます。
逆に読み取るときは、変数でもプロパティでもどっちでもいいみたいです(常に同じ値なのでそれはそう)。偉い人が今どんなお仕事をしているのか、公開されている情報を見る分には許可は必要ないのと同じです。ただプロパティさんは変数さんが持っているのと必ず同じ情報を返してくれるので、正確性は100%です。
実際の動作部分の記述ではほとんどプロパティさんが呼ばれて変数さんは名前だけのお飾りみたいになることもあります。もう全部あいつ一人でいいんじゃないかな?でもプロパティさんは変数さんがいないと空っぽなので、周りから見えなくても変数さんは必要なんですね。何それエモい。
おわりに
流れが変わってしまいそうなので章を分けて話を戻します。
U#の同期変数と Value Change について、素人が嚙み砕いた理解を書いてみました。理解するのに半日くらいかかりましたがわかってしまうとなんてことはないですね。はやくU#こわくないになりたいです。これからもU#と戦っていきます。
ここまでお付き合いいただきありがとうございました。