VRChatでUdonを使って同期をした話2

その2です。承前


目次

・グローバルとローカルを区別する
・値が変更された時?
・UdonSynced変数の同期を待つ


グローバルとローカルを区別する

タイミングを探る前に、グローバルとローカルを区別します。ここでは大まかに、同期されていればグローバル、同期されていなければローカルとします。Udonで実装された関数は基本的にローカルです。UdonSynced変数を参照したり全員に同じ関数を実行させるイベントを発行することでグローバルな処理が可能になります。

前回のソースコードのUpdate関数内ではローカルでtext.textにstrを代入していましたが、strはUdonSyncedが付けられているためグローバルであり、かつインスタンス内の全員がUpdate関数を毎フレーム実行するので結果としてテキストを同期することができていました。

今回はUpdate関数には同期処理を実装しないため、同期をする関数を実装してその関数をインスタンス内の全員に実行させる必要があります。インスタンス内の全員に関数を実行させるには、SendCustomNetworkEvent関数を使用します。

SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "SyncValue");

あとはSyncValue関数内に同期処理を実装すればいいですね。


値が変更された時?

変更された値を同期するのは、値が変更されたタイミングでよさそうです。ついでに後から入ってきた人にも同期されるように、OnPlayerJoinedでも同期しておきましょう。以下に例を示します。

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

public class SyncInteract : UdonSharpBehaviour
{
    public Text text;
    [UdonSynced]
    private string str = "";

    public override void OnPlayerJoined(VRCPlayerApi player)
    {
        if(Networking.LocalPlayer == player)
        {
            SyncValue();
        }
    }

    public override void Interact()
    {
        if (!Networking.IsOwner(Networking.LocalPlayer, this.gameObject)) Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
        str = text.text + "A";
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "SyncValue");
    }
    public void SyncValue()
    {
        text.text = str;
    }
}

新しくプレイヤーが入ってきた時にはその人だけ同期すればいいのでローカルでSyncValueを実行しています。Interact関数1行目の処理とその挙動についてはこちらを参考にさせて頂きました。

はい、Interact関数内の処理では同期されません。インタラクトした人以外からは一文字少なく見えています。インタラクト一回分遅れてる状態ですね。運が悪いとOnPlayerJoinedも一回分遅れたりします。

どうやらローカルで1フレーム中にUdonSynced変数の値の変更とSendCustomNetworkEventの実行をすると、UdonSynced変数の同期よりグローバルでのSendCustomNetworkEventでの関数実行の方が早く行われるようです。UdonSyncedの同期を待つ必要があります。


UdonSynced変数の同期を待つ

UdonSynced変数の同期を待つためにしばらく時間を置いてからSyncValueを実行したいですが、UdonSharpではコルーチンが使えないためこれも自前で実装してあげる必要があります。以下に例を示します。

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

public class SyncInteract : UdonSharpBehaviour
{
    public Text text;
    [UdonSynced]
    private string str = "";
    private int syncWaitCount = 0;

    private void Update()
    {
        if(syncWaitCount != 0)
        {
            syncWaitCount --;
            if(syncWaitCount <= 0)
            {
                SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "SyncValue");
                syncWaitCount = 0;
            }
        }
    }

    public override void OnPlayerJoined(VRCPlayerApi player)
    {
        if(Networking.LocalPlayer == player)
        {
            SyncValue();
        }
    }

    public override void Interact()
    {
        if (!Networking.IsOwner(Networking.LocalPlayer, this.gameObject)) Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
        str = text.text + "A";
        syncWaitCount = 10;
    }

    public void SyncValue()
    {
        text.text = str;
    }
}

上記のコードでは、インタラクト時にsyncWaitCountを10に設定し、Update関数内で毎フレームデクリメントして10フレーム待った後でSendCustomNetworkEventを実行しています。

同期されました。ただしVRChat側のサーバーの状況やインスタンス内のプレイヤーの環境によってはより長い時間待つ必要があります。そのため、待つフレーム数を動的に変えられるようにするといいかもしれません。また、定期的にReSyncする処理を入れるのも効果的です。

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