Udon#/Unityで詰まったところと解決法
はじめに
私が VRC SDK3 でワールドを作るにあたり、 UdonSharp (U#) や Unity で詰まったところと、その解決法を(もし見つかれば)書いていくページです。
ざっくり分類してるけど結構適当。
箇条書きだけど前後の項目と繋がってたり繋がってなかったり。
更新で変わったり間違ってたりするところがあるかもだけど許して。
順次追記します。
最終追記:2024/07/28
基本的な話
using でライブラリから引っ張ってくる系の処理の多くは Udon サポート外
簡単なものなら自分で実装するのが早い(ソートとか)
Method not exposed to Udon とか出てたらそのメソッドは使えない
文法機能
List とか Collection 系は全部使えないので、配列で上手くやるしかない
使える機能で実装された List や Dictionary はある
自分で作ったメソッドで ref を受け取ることはできない
Enum は
使えない使えるようになった継承は
使えない使えるようになったジェネリックメソッドは普通に使える
例外処理
Exception を catch できない
一度でも Exception が発生すると、以降そのクラスの処理が全部止まる(Updateとかも含めて)
事前チェックをして、例外を発生させない工夫が必要
null になる可能性のある変数を参照する場合は、先に変数が null と等しくないことをチェックしてから処理する
Destroy した直後のオブジェクトは null と等しくないケースに注意
VRCPlayerApi が有効かチェックする時や、Destroy される可能性のあるオブジェクトのチェックには VRC.SDKBase.Utilities.IsValid(object obj) が使えるらしい
変数で割り算をする場合、0 で割り算をしないようにチェックしてから割り算を実行する
Parse メソッドなどで文字列を数値に変換する場合、TryParse メソッドを使って、パースが可能なことをチェックしてからパースする
同期関連
[UdonSynced] を付けたフィールドは同期する
配列も同期されるようになったが、 string の配列はダメ
Synchronization Method が Continuous だと、変数のサイズが大きい(100KB超えるくらいらしい)と同期しない
float[100] は同期ダメだった
Synchronization Method を Manual にすれば OK
ただし、Manual の場合は RequestSerialization() を明示的に呼ばない限り同期されない
Networking.SetOwner でオーナーを自分に設定後、すぐに同期変数を変更しても、変更処理が行われることは担保される
しかし、同期変数の値を読んで、それを処理して同じ同期変数を更新する、という処理の場合は注意が必要(DBで言うところのコンフリクトが発生する)
A と B が同時に同期している文字列変数に追記する処理を考えた場合、まず A がデータを読み込みそれに追記、続いて B がデータを読み込みそれに追記、次に A がデータに追記済みデータを書き込み、最後に B が追記済みデータを書き込み、という順番に処理される可能性があり、この場合 A が追記したデータは消失する
[UdonSynced] を付けた配列に対して、配列自体を変更するような代入をすると、エラーにはならないが、すべての変数が同期しなくなる
public class SampleUdonClass : UdonSharpBehaviour
{
[UdonSynced]
private int[] a = new int[3];
public void SetNG(int[] b)
{
if (!Networking.IsOwner(Networking.LocalPlayer, this.gameObject)) {
Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
}
a = b; // C# の文法としては正しく、エラーにもならないが、実行後にこのクラスのすべての変数が同期しなくなる
RequestSerialization();
}
public void SetOK(int[] b)
{
if (!Networking.IsOwner(Networking.LocalPlayer, this.gameObject)) {
Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
}
// 要素を1つずつ代入するのはOKで、問題なく同期する
// (余談)要素数が多い場合は for や foreach で処理した方が良い
a[0] = b[0];
a[1] = b[1];
a[2] = b[2];
RequestSerialization();
}
}
OnDeserialization は RequestSerialization を呼ばなくても、2人目以降のプレイヤーが Join してすぐのタイミングでも Join したプレイヤーの環境で呼ばれる
Ownership関連
UdonBehaviour または VRC Object Sync がついたオブジェクトは、そのインスタンスにいる誰かが「オーナー」として設定される
明示的にオーナーを設定していないオブジェクトのオーナーは、そのワールドに最初に入った人になる
Networking.SetOwner を使って、オブジェクトのオーナーを変更できる
[UdonSynced] な変数の値を変更できるのはオブジェクトのオーナーのみ
VRC Object Sync のついたオブジェクトの Transform を操作できるのもオブジェクトのオーナーのみ
uGUI関連
TextMeshPro(TMP) が使える
Unity 上では日本語などのマルチバイト文字は正しく表示されないが、VRChat にアップロードすると正しく表示される
Unity 上でも正しく表示させたり、フォントをカスタムしたりしたい場合は自分でフォントアセットを作って設定する
ただし、 Udon から TMP の InputField にはアクセスできない回避策があるっぽいけど、上手くいかなかったVRC TMP Dropdown, VRC TMP InputField, VRC TMP Text 経由で各種 TextMeshPro にアクセスできるようになる(現在OpenBeta)
Canvas に BoxCollider が付いていない場合、VRC が勝手に (1, 1, 1) スケールのコライダーを付ける
このコライダーに当たって、 Ray (手から出る青いビーム) が Canvas の手前で不自然に消える
自前で z が 0.01 くらいの BoxCollider を付けておくといい感じになる
uGUI の要素の Navigation が None でない場合、コントローラーの入力を受け取って意図しない挙動になることがある
特に Slider は Navigation を None にしていないとスティック移動時にスライダーが移動してしまう
複数の Canvas を同一平面状に置くと重なり順が思ってたのと違う感じになることがある
Canvas の OrderInLayer を設定すると思った通りにできる(参考: https://developer.aiming-inc.com/ux/ugui-layer/ )
が、透過がおかしくなることがあったので、素直にCanvasをまとめた方がいい
レイヤーが UI になっていると、メニューを開いている時だけ操作できるようになるので、誤って押すと困るボタン(リセットボタンなど)に使えそう
逆に、普通に操作したいものは UI 以外(Defaultなど)のレイヤーに設定する
デバッグ関連
[Edit] -> [Project Settings...] -> [Udon Sharp] から、UdonSharp の各種設定ができる
デバッグに関する設定もいくつかある
ワールドのアップロード画面や、公式ページから、ワールドの World Debugging 設定を有効にすると、デバッグ画面を表示できる
Shift + チルダ(or半角/全角) + 数字(1から9) で開け、主に Udon のデバッグでは 3 で Debug.Log の出力や Udon の呼び出しなどを確認できる
その他
override した Interact() メソッドは UdonBehaviour.SendCustomEvent("Interact") や UdonBehaviour.Interact() では呼び出せない
override した Interact() メソッドを呼び出したい場合は UdonBehaviour.SendCustomEvent("_interact") を使う
他のUnityイベントも同様っぽいので、本来のメソッド名で呼び出せない時は先頭にアンダーバーをつけて先頭小文字を試すと良さそう
Udonコンパイラに無視して欲しい処理は #if !COMPILER_UDONSHARP で判定すればやってくれる
Reset() メソッドで初期値をフィールドに入れる処理とか
この内部に限り Udon で使えないメソッドも使えるのが嬉しい(もちろんこの部分はVRC上では動作しない)
時間のかかる処理をさせた場合、1フレームの Udon の処理に 10 秒以上かかると、無限ループと判断されて Udon の実行が停止する
System.Diagnostics.Stopwatch で時間を計測して 10 秒になる前に処理を打ち切る
wait や yield の仕組みは Udon にはないので、10 秒を超えて処理をしたい場合は SendCustomEventDelayedFrames を使って次のフレームに計算を持ち越す
配列をシャッフルするユーティリティメソッドがある
VRC.SDKBase.Utilities.ShuffleArray(int[] array)
この記事が気に入ったらサポートをしてみませんか?