Udon#/Unityで詰まったところと解決法

はじめに

私が VRC SDK3 でワールドを作るにあたり、 UdonSharp (U#) や Unity で詰まったところと、その解決法を(もし見つかれば)書いていくページです。
ざっくり分類してるけど結構適当。
箇条書きだけど前後の項目と繋がってたり繋がってなかったり。
更新で変わったり間違ってたりするところがあるかもだけど許して。
順次追記します。
最終追記:2024/07/28

基本的な話

  • using でライブラリから引っ張ってくる系の処理の多くは Udon サポート外

  • 簡単なものなら自分で実装するのが早い(ソートとか)

  • Method not exposed to Udon とか出てたらそのメソッドは使えない

文法機能

  • List とか Collection 系は全部使えないので、配列で上手くやるしかない

  • 自分で作ったメソッドで ref を受け取ることはできない

  • Enum は使えない 使えるようになった

  • 継承は使えない 使えるようになった

  • ジェネリックメソッドは普通に使える

例外処理

  • Exception を catch できない

  • 一度でも Exception が発生すると、以降そのクラスの処理が全部止まる(Updateとかも含めて)

  • 事前チェックをして、例外を発生させない工夫が必要

    • null になる可能性のある変数を参照する場合は、先に変数が null と等しくないことをチェックしてから処理する

      • Destroy した直後のオブジェクトは null と等しくないケースに注意

    • 変数で割り算をする場合、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 を同一平面状に置くと重なり順が思ってたのと違う感じになることがある

  • レイヤーが UI になっていると、メニューを開いている時だけ操作できるようになるので、誤って押すと困るボタン(リセットボタンなど)に使えそう

  • 逆に、普通に操作したいものは UI 以外(Defaultなど)のレイヤーに設定する

デバッグ関連

  • [Edit] -> [Project Settings...] -> [Udon Sharp] から、UdonSharp の各種設定ができる

    • デバッグに関する設定もいくつかある

  • ワールドのアップロード画面や、公式ページから、ワールドの World Debugging 設定を有効にすると、デバッグ画面を表示できる

その他

  • override した Interact() メソッドは UdonBehaviour.SendCustomEvent("Interact") や UdonBehaviour.Interact() では呼び出せない

    • override した Interact() メソッドを呼び出したい場合は UdonBehaviour.SendCustomEvent("_interact") を使う

    • 他のUnityイベントも同様っぽいので、本来のメソッド名で呼び出せない時は先頭にアンダーバーをつけて先頭小文字を試すと良さそう

    • https://udonsharp.docs.vrchat.com/events

  • Udonコンパイラに無視して欲しい処理は #if !COMPILER_UDONSHARP で判定すればやってくれる

    • Reset() メソッドで初期値をフィールドに入れる処理とか

    • この内部に限り Udon で使えないメソッドも使えるのが嬉しい(もちろんこの部分はVRC上では動作しない)

  • 時間のかかる処理をさせた場合、1フレームの Udon の処理に 10 秒以上かかると、無限ループと判断されて Udon の実行が停止する

    • System.Diagnostics.Stopwatch で時間を計測して 10 秒になる前に処理を打ち切る

    • wait や yield の仕組みは Udon にはないので、10 秒を超えて処理をしたい場合は SendCustomEventDelayedFrames を使って次のフレームに計算を持ち越す

  • 配列をシャッフルするユーティリティメソッドがある

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