🍜

VRC Object Sync付きのPickupオブジェクトにManual同期のUdonスクリプトを使う

2023/08/12に公開

おことわり

この記事はVRChatがきっかけでプログラミングを始めた初心者が書いています。
間違ったことを書いていたらごめんなさい。参考程度に読んでもらえると幸いです。

この記事は2023年8月12日時点の情報です。UdonSharpのバージョンなどは以下の通り。

  • Unity 2019.4.31f1 (64-bit)
  • VRChat SDK - Base 3.2.2
  • VRChat SDK - Worlds 3.2.2
  • Udon Sharp 1.1.9

Udonの同期モード「Continuous」

よく使われるUdonの同期モードとしてNone, Continuous, Manualがあります

その中のContinuousは常に同期変数の値を勝手に更新してくれる同期モードで、とってもお手軽なのですが、大量に使用してしまうと通信がパンクしてしまい、同期がうまく取れなくなったり、2023年年明けのVRChatアップデート以降は、時間経過とともにインスタンスマスターだけが重くなる不具合も発生するようになってしまいました。

自分が作ったアセットで、このインスタンスマスターだけが重くなる問題が発生し、他の方が原因を調べてCannyに登録してくださっていました。 (下記リンク)
ありがとうございます。そしてアセットを使ってくださっていた方々ごめんなさい...

Continuousの仕組み自体が重いということは以前からも知られており、Vketなどの入稿規定ではContinuousの使用を禁止。同期処理を行う場合はManualのみを使用するように制限が設けられたりしています。

https://feedback.vrchat.com/bug-reports/p/1277-heavy-fps-drop-on-instance-master-caused-by-clogging-synchronization

VRC Object Syncの制限

VRChatにはPickupオブジェクトなどの位置や姿勢を簡単に同期できるVRC Object Syncというコンポーネントが用意されており、これはVketなどのイベントでも使用が許可されていることが多いと思います。

しかし、VRC Object Syncを付けたオブジェクトには、どういう訳か、同期モードがNoneとContinuousのUdonスクリプトしか付けられず、Manual同期のUdonスクリプトは付けられません。

Object Sync cannot share an object with a manually synchronized Udon Behaviour
というエラーが出てしまします

この制限が結構厄介で、例えば下図のようにCubeを持った状態でUseしたらパーティクルが再生されるクラッカーを作ることを想定します。
https://twitter.com/Hyper_Mesh/status/1719172111372300791?s=20
一番思いつきやすい仕組みはこんな感じかと思います

CubeにはVRC Pickupコンポーネントを付けて手に持てるように。
位置と姿勢はVRC Object Syncコンポーネントで同期。
パーティクルの再生同期用にUdonスクリプトを付け、OnPickupUseDown()イベント内でSendCustomNetworkEvent()を使って全員にパーティクルを再生させる...

しかし、そうするとUdonスクリプトの同期モードはContinuousにせざるを得なくなります。
そしてContinuousを禁止しているイベントにはせっかく作ったクラッカーを出品できなくなってしまいます(泣)

VRC Object Syncの制限の回避方法

下図のようにCubeに同期モードNoneの中間Udonスクリプトを付け、同期モードManualのUdonスクリプトへの情報の受け渡しを行わせます。これによってContinuousを使わずに同期を取ることができるようになります。

「同期モードNoneの中間Udonスクリプト」 → 「同期モードManualのUdonスクリプト」の情報の受け渡しにはSendCustomEvent() を使用します。

例えば以下のように書くことで、inspectorで指定されたReceiverUdonオブジェクトに付けられたUdonスクリプト内のTest()というメソッドを実行させることができます。

public UdonSharpBehaviour ReceiverUdon;
ReceiverUdon.SendCustomEvent("Test");

各オブジェクトのinspector画面とUdonスクリプトを以下に載せていきます

・PickupObj (VRC_ObjectSync) オブジェクト

・ReceiverUdon (ManualSync) オブジェクト

・PartyCrackerParticle オブジェクト

・SenderUdon.cs (同期モード:None)

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;

// 同期モードをNoneに設定 (NoneならVRC_ObjectSyncと共存できる)
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]


public class SenderUdon : UdonSharpBehaviour
{
    // 同期モードがManualのReceiverUdon.csがアタッチされているオブジェクト。inspectorで指定する
    public UdonSharpBehaviour ReceiverUdon;


    // 自分がこのスクリプトがついているオブジェクトを手に持った時に呼ばれるイベント
    public override void OnPickup()
    {
        // 自分のReceiverUdon内のEvent_OnPickup()メソッドを実行
        ReceiverUdon.SendCustomEvent("Event_OnPickup");
    }

    // 自分がこのスクリプトがついているオブジェクトを持った状態でUSEボタンを押した時に呼ばれるイベント
    public override void OnPickupUseDown()
    {
        // 自分のReceiverUdon内のEvent_OnPickupUseDown()メソッドを実行
        ReceiverUdon.SendCustomEvent("Event_OnPickupUseDown");
    }
}

・ReceiverUdon.cs (同期モード:Manual)

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;

// 同期モードをManualに設定
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]


public class ReceiverUdon : UdonSharpBehaviour
{
    // パーティクルオブジェクト。inspectorで指定する
    public ParticleSystem ParticleObject;


    // 自分のSenderUdonから実行されるメソッド
    public void Event_OnPickup()
    {
        // 自分がこのオブジェクトのオーナーになる
        Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
    }

    // 自分のSenderUdonから実行されるメソッド
    public void Event_OnPickupUseDown()
    {
        // 自分(オーナー)を含めた全員にPlayParticle()メソッドを実行させる
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "PlayParticle");
    }

    // パーティクルを再生するメソッド
    public void PlayParticle()
    {
        ParticleObject.Play();  // パーティクルを再生
    }
}

完成品 (UnityPackage)

持った状態でUseしたらパーティクルが再生されるCube型のクラッカーのUnityPackageです。

ダウンロードデータ一式のライセンスはCC0 (クリエイティブ・コモンズ・ゼロ)です。
いかなる権利も保有しません。自由に使ってください。
https://drive.google.com/file/d/1Aii3DIN9dRrPysjvorb6yVZ-BQHpAcM8/view?usp=sharing

Discussion