ハツェの真時代傾向璋

興味を持ったことを書いていく鱗片的な場所から先の未来の場

U# 入門 ②

こんばんは。ハツェです。
今回も、Udonスクリプトで制作するための記事を書いていきます。
今回は変数同期について書いていきます。
これを読む人はある程度コードを書いて来た人だと思うので、スピードは速めに行きますね。
動作環境は、Unity : 2018.4.20f1、VRCSDK : 2020.04.09.16.59、U# : v0.15.4です。

目次


記事をバージョンアップしました

VRChatのネットワークアップデートにより、情報が古くなったため、改訂版の記事を用意しました。
当記事は、過去の情報として保存しておきますが、参考にならない箇所も多いため、以下の記事を読むことを強くお勧めします。
hatuxes.hatenablog.jp

前回

前回は導入と簡単な処理をしました。
hatuxes.hatenablog.jp

今回の概要

今回は簡単な変数操作と、同期について紹介します。
変数操作なら普通のコードとなんら変わりはないですが、同期の方法についてはVRChat独特の概念を持つので、少し混乱するかもしれません。
ですが、しっかりと脳で分離して考えると難しい話ではないと思います。
また、同期のいろはを書いているため前回よりも長めな記事となっています。あらかじめご了承ください。
では、やっていきましょう!

簡単な変数操作

試しに、Cubeをインタラクトしたらカウントアップする処理を作って見ようと思います。
まず、Scene上にCubeと数値を表示するためのCanvasとTextを用意しましょう。
数値を表示するTextは、代入されるので空白で構いません。

f:id:hatuxes:20200403231839p:plain

そしたら前回同様、CubeにUdonBehaviorをアタッチし、スクリプトを生成します。
スクリプトはこのような感じでよいでしょう。
using UnityEngine.UIをお忘れなく。

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

public class Counter : UdonSharpBehaviour
{
    public Text text;

    int _num;

    void Start()
    {
        _num = 0;
    }

    void Update()
    {
        text.text = _num.ToString();
    }

    public override void Interact()
    {
        _num++;
    }
}


VRChatで定義された関数は基本的に

public override void

の修飾子をつけてあげる必要があります。
スクリプト自体は特に難しいことはしていないので、説明する必要はないかと思います。

実行

実行すると以下のようになるかと思います。

f:id:hatuxes:20200404173712g:plain

同期について

今まではObjectSyncとかTriggerの同期で行っていましたが、Udonにも同期を行う仕組みがあります。
基本となる同期は以下の三つになると思います。

  1. UdonBehaviorによる同期
  2. 変数単体の同期
  3. 処理の同期

それぞれ説明していきます。

UdonBehaviorによる同期

UdonBehaviorにはそもそも同期をするかどうかのチェックボックスがあります。

f:id:hatuxes:20200404014550p:plain

ここで同期できるのは、TransformとAnimationです。
一番下のAllow Ownership Transfer on Collisionは、ぶつかった時にOwnerを移すかどうかのチェックボックスです。
プレイヤーとぶつかった時に何かをするといった処理をする場合のOwner移しに使用するかなと思います。

変数単体の同期

Udonになって、変数ごとに同期がとれるようになりました。
UdonGraphで言うところのここですね。

f:id:hatuxes:20200404035303p:plain

このSyncはワールド全員に同期するだけでなく、後から来た人にも同期します。
この後から来た人にも同期するというのが、変数同期の一番の強みです。
対応している変数の型はbool, char, byte, int, long, unsigned byte, unsigned int, unsigned long, float, double, short, unsigned short, string, Color, Color32, Quaternion, Vector2/3/4です。
U#でもこの変数同期は実装できます。
変数の属性として[UdonSynced]を指定してるだけです。
ただし、この属性をつけた変数はOwnerのみにしか編集できなくなります。
そのため、変数をいじる際にはSetOwner()等でOwnerを渡す必要が出てきます。

処理の同期

ぶっちゃけ処理を書くなら一番大事な部分だと思います。
処理同期は、変数同期とは異なります。
例えば扉をインタラクトしたら扉を開けるといった時、アニメーションを開始するという処理が同期していないと完成しないわけです。
その処理同期ですが、使うのはSendCustomNetworkEventです。 SendCustomNetworkEventに関数名を指定してあげることで、処理を同期させることができます。
因みにUdonGraphにももちろん同じノードがあります。

f:id:hatuxes:20200404042610p:plain

先程のカウントアップを変数同期で同期させてみる

まず、もう一つCubeと結果を表示するテキストをシーンに作りましょう。
こんな感じで私は作りました。(赤丸の部分に結果を表示します)

f:id:hatuxes:20200404044043p:plain

そしてCubeにアタッチするスクリプトは以下のようにします。

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

public class SyncCounter : UdonSharpBehaviour
{
    public Text text;

    //追加
    [UdonSynced(UdonSyncMode.None)]
    int _num;

    void Start()
    {
        _num = 0;
    }

    void Update()
    {
        text.text = _num.ToString();
    }

    public override void Interact()
    {
        //追加(Cubeに対するオーナーを変更)
        if (!Networking.IsOwner(Networking.LocalPlayer, this.gameObject)) Networking.SetOwner(Networking.LocalPlayer, this.gameObject);

        _num++;
    }
}


少し情報が多いので、一個ずつ解説していきますね。
まずは、後から来た人にも同様な値を見せたい変数に[UdonSynced]属性をつけます。

[UdonSynced(UdonSyncMode.None)]
int _num;

UdonSyncModeは同期の仕方を設定するオプションです。
設定はNone Linear Smooth NotSyncedの四つから指定できます。
それぞれただの同期 リニア補間同期 スムーズ補間同期 同期しないです。
それぞれ動かしてみた感じとしては以下の通りで、体感としては下図のような感じになります。

f:id:hatuxes:20200413161827p:plain

ちなみに現状、LinearSmoothには少々バグがあるため素直にNoneを使用するのがオススメです。

続いては27行目のOwner関係です。

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

[UdonSynced]属性のついた変数は、Ownerにしか設定できないという仕様がありましたね。
そのため、カウントアップをするために触った人をOwnerにする必要があります。
そこで、初回のみOwnerに設定してあげるという処理を27行目で行っています。

追記
Networking.GetOwner(playerList.gameObject) != Networking.LocalPlayerという比較演算子で記述する方法は、うまく機能しないという記事をお見かけしたので、VRChatから与えられている関数Networking.IsOwnerで対処しました。

実行

最終的な実行画面は以下の通りです。
これは三人称視点の映像です。

f:id:hatuxes:20200404174134g:plain

現状、Udonでは[UdonSynced]属性のついた変数はオーナーが設定された後のフレームでしか操作できません。
つまり、同じフレームでオーナーの受け渡しと変数の操作が出来ないということです。
そのため、初回インタラクト時のみカウントアップはされていません。

今度は処理同期を使ってみる

次に、処理同期を使った簡単なサンプルを紹介します。
概要としては、Cubeをインタラクトしたら上からパーティクルが数秒ポロポロと落ちてくるものです。
まず、インタラクトに使用するCubeと以下からダウンロードしたパーティクルをシーンに設置します。
(ちなみに、このパーティクルは自由に使ってもらって結構です。)
drive.google.com
だいたいこんな感じで設置します。

f:id:hatuxes:20200413162524p:plain

そしたらCubeに以下のようなスクリプトをアタッチしましょう。

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

public class PlayParticle : UdonSharpBehaviour
{
    public ParticleSystem Particle;

    public override void Interact()
    {
        if (!Particle.isPlaying) SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "Emit");
    }

    public void Emit()
    {
        Particle.Play();
    }
}


これで、一応完成となります。
少し解説しますね。
気になる所は20行目のここ。

if (!Particle.isPlaying) SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, "Emit");

SendCunstomNetworkEventが処理同期のための関数ということは同期自体の説明時にお話ししました。
この関数は第一引数に処理を同期させる人の種類、第二引数に同期させる処理を記述した関数の名前を指定します。
第一引数は現状VRC.Udon.Common.Interfaces.NetworkEventTarget.AllVRC.Udon.Common.Interfaces.NetworkEventTarget.Ownerの二つから選べます。
Allの方はインスタンスにいる全員に関数内の処理を実行します。
一方Ownerの方は、オーナーにのみ関数内の処理を実行します。
加えて、ifの中身はパーティクルの重複起動防止のために再生中かどうかの判定を行っています。
ちなみに、SendCunstomNetworkEventで呼ぶ関数はpublicである必要があります。

実行

実行画面は以下の通りです。(三人称視点の映像)

f:id:hatuxes:20200413165038g:plain

押した人は別の人なのに、ちゃんと自分の画面にもパーティクルが降り注いでますね。
これで処理同期が確認できたことになりました。

はい、今回は以上です。
今回も、サンプルプロジェクトはGithubに置いておきます。
github.com

次回

次回は、UIでUdonを使う方法について触れていきます。
hatuxes.hatenablog.jp