ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

VRChat用に同期する水が打てる水鉄砲を作る

いきなりまとめ

  • 今まで作ってきた水鉄砲は、実は同期しない
  • VRCObjectSyncをつけてもTransformとRigidbodyしか同期しない
  • [UdonSynced]はかゆいところには手が届かない
  • ということでちゃんとSendCustomNetworkEvent()を使いましょう

今回やること

前回前々回で水が打てる水鉄砲が出来ましたが、実は今のままではこのオブジェクトは同期しません!なので、今回は水鉄砲が同期するようにしましょう。

VRCObjectSyncを使ってみた

VRChat上でオブジェクトを同期させたいと思ったときに最初に思い浮かぶのがオブジェクトにVRCObjectSync コンポーネントを付与することではないでしょうか。名前の通りこのコンポーネントを付与するだけでオブジェクトが同期するようになります。ただし、VRCObjectSyncによって同期されるのはリファレンスにも書いてある通りTransformとRigidbodyのみです。

f:id:prupru_prune:20220216204014p:plain
VRCObjectSyncで同期されるコンポーネント
したがって、VRCObjectSyncをつけただけでは見た目は同期されますが、水位が下がるというギミックやパーティクルの発射などは同期されません。ただし、オブジェクトの同期には必須のコンポーネントです(オブジェクト同期のための必要条件ではあるが十分条件ではない)。 streamable.com

[UdonSynced]を使ってみた

Unityではスクリプトでフィールドを定義する際にAttribute(属性)を付与することで、その属性に応じて様々な効果を持たせることが出来ます。VRChat専用のAttributeとして[UdonSynced]というものがあり、この属性を付与されたフィールドは複数クライアント上で同期されます。しかし、[UdonSynced]で同期できるフィールドの型はごく一部に限られているらしく、正直言って使いどころがあるのかわかりません。試しにMaterialに[UdonSynced]を付与してみましたが、以下のようなエラーを吐かれました。

Assets\Scenes\Props_UdonProgramSources\watergun.cs(10,12): System.NotSupportedException: Udon does not currently support syncing of the type 'Material'

要するに[UdonSynced]はMaterial型には対応してませんよということですね。

SendCustomNetworkEvent()を使ってみた

色々調べた結果、SendCustomNetworkEvent()という関数を使えばオブジェクトの同期がかんたんにとれるそうです。この関数の使い方はここを盛大に参考にしました。使い方としては、オブジェクトのオーナーの権限を設定した後に同期させたい処理を呼び出すっぽいです。

 if (!Networking.IsOwner(Networking.LocalPlayer, this.gameObject)) 
        Networking.SetOwner(Networking.LocalPlayer, this.gameObject);

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

結局、前回作った水鉄砲用のU#スクリプトを以下のように改修しました。

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

public class watergun : UdonSharpBehaviour
{
    private Material mt;
    private float wa;
    private float ini;
    private ParticleSystem childps;
    private Transform childtrans;

    void Start()
    {
        mt = GetComponent<Renderer>().material;
        wa = mt.GetFloat("_WaterAmmount");
        ini  = wa;
        childtrans = transform.Find("Muzzle/Particle");
        Debug.Log(childtrans.position);
        childps    = childtrans.gameObject.GetComponent<ParticleSystem>();
    }
    public override void OnPickupUseDown()
    {
        //LowerWater();
        //EmitWater();
        if (!Networking.IsOwner(Networking.LocalPlayer, this.gameObject)) Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "LowerWater");
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "EmitWater");
    }
  
    public override void OnDrop()
    {
        mt.SetFloat("_WaterAmmount", ini);
        wa = ini;
    }

    public void LowerWater()
    {
        wa -= ini/10.0f;
        mt.SetFloat("_WaterAmmount",wa);
    }

    public void EmitWater()
    {
        childps.Play();
    }
}

これにより無事MaterialとParticle Systemも同期させることが出来ました。

streamable.com

参考

phi16.hatenablog.com

fabniworld.com

tsubakit1.hateblo.jp