一定間隔で処理をするやつの正攻法と裏技
こんばんは。ハツェです。
今回はミニtipsみたいなお話です。
SDK2時代のOnTimerをUdonならどう実装するかみたいな話です。
動作環境は、Unity : 2019.4.30f1、VRCSDK : 2021.09.03.09.25、U# : v0.20.3です。
目次
今回の概要
一定間隔で処理を行う方法の一例を紹介します。
参考程度に見てもらえればと思います。
通常の例
だいたい「Unity 一定間隔」とかで検索すると、Updateで時間ごとに処理を走らせるコードが出てくると思います。
[SerializeField] private float _timeInterval; private float _timeElapsed; private void Update() { _timeElapsed += Time.deltaTime; if (_timeElapsed > _timeInterval) { /*-- 一定間隔で実行したい処理 --*/ // 経過時間を元に戻す _timeElapsed = 0f; } }
これ自体はよくある実装例であり、かつ単純なコードで済みます。
DelayedSecondsを使った例
一方で、コルーチンを使った方法というのもまたUnityに存在しています。
ですが、Udonではコルーチンを使うことは現状出来ません。
しかし、SendCustomEventDelayedSeconds
を使うことで似たようなことが出来ます。
[SerializeField] private float _timeInterval; private void Start() { DoFuction(); } public void DoFuction() { /*-- 一定間隔で実行したい処理 --*/ if (_timeInterval > 0) { SendCustomEventDelayedSeconds(nameof(DoFuction), _timeInterval); } }
この方法を使うことで、Updateの毎フレーム処理を軽減することが出来ます。
しかし、SendCustomEventDelayedSeconds
は中断機能などを備えていないため、一度実行させてしまったら、対象の関数は絶対実行されます。
なので、やっぱり途中でやめる!みたいなものを追加したい場合は、if文に条件を加えたりする必要があると思います。
あとがき
今回は、SendCustomEventDelayedSeconds
を使ったOnTimer代替実装を紹介しました。
おそらく非同期実装だと思っているので、複雑なものを実装するときは少し躊躇するかなとも思っています。
他の方法としては過去に、Sphereをバウンドさせ、着地した時に処理を走らせることで、一定間隔ごとに処理を行わせるみたいなテクニックもありました。
それ以外にも面白い方法とかありましたら、是非ともコメントにて教えて頂けると記事ネタにもなるので助かります。
以上です!ありがとうございました。
入門 ②の処理をOnValueChangedを用いて書いてみる
こんばんは。ハツェです。
お久しぶりです。
今回は、最近Udonに実装された変数の値が変わった時に発生するイベントOnValueChangedについて取り上げます。
今回も、この記事はコードを基礎的に書ける方を対象としています。予めご了承ください。
動作環境は、Unity : 2019.4.29f1、VRCSDK : 2021.08.11.15.16、U# : v0.20.2です。
目次
対象読者
以下を理解している方を前提に書いています。あらかじめご了承ください。
もし、Udonの同期処理の仕組みを知らないという方は、以下の記事を一度ご参照の上、当記事をお読みいただければと思います。
hatuxes.hatenablog.jp
今回の概要
今回は、Udon入門②にて取り上げた同期変数の例をOnValueChangedで書き換えるという話題について取り上げます。
OnValueChangedとは
OnValueChangedとは、指定した変数の値が代入もしくはSetProgramVariableで変更された時に呼び出されるイベントです。
OnValueChanged自体は、同期されているかどうかに関わらず、全ての変数に適用出来ます。すいません。OnValueChangedは同期変数にのみ対応していました。
OnValueChangedは、変数ごとに個別の処理を記述出来ること、OnDeserializationよりも更新速度が速いことがメリットになっています。
docs.vrchat.com
OnValueChangedをU#で実装する
OnValueChangedの実装方法は、UdonGraphとU#で異なります。UdonGraphの実装方法は割愛します。
U#では、C#のプロパティのような機能を用いて実装します。
おおよそ以下の通りです。
[UdonSynced, FieldChangeCallback(nameof(SyncedVariable))] private int _syncedVariable; public int SyncedVariable { get => _syncedToggle; set { _syncedVariable = value; // 以下、任意の処理 } }
必須な実装は以下の通りです。
- OnValueChangedイベントを適用する変数にFieldChangeCallback属性を付ける
- 適用する変数に対応させるプロパティを記述する
- 適用する変数のFieldChangeCallback属性の引数に、2番目で作ったプロパティの名前を設定する
これで、OnValueChangedイベントが適用した変数にて発生するようになります。
そのため、もし変数が変わった時に動作させたい処理があるなら、setterの部分に記述することで動作するようになります。
感覚としては、OnDeserializationのようにsetterを扱うイメージです。
公式の例については、以下を参照してください。
github.com
実際に改変してみる
ここからは、実際に改変した例を紹介します。
Ownerに処理を渡す例の改変例
元のスクリプトは以下のものです。
hatuxes.hatenablog.jp
これをOnValueChangedを用いた処理に改変してみました。少し変数名とかを変えて見やすくしています。
using UdonSharp; using UnityEngine.UI; using VRC.SDKBase; [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] public class ViaOwner_Countup_System_WithOnValueChanged : UdonSharpBehaviour { [UdonSynced(UdonSyncMode.None), FieldChangeCallback(nameof(CountData))] private int _countData; // データ本体 public Text DisplayDataText; // データを表示するText public Text OptionText; // 誰がOwnerかを表示するText // OnValueChanged用のプロパティ public int CountData { get => _countData; set { _countData = value; DisplayCountData(); // Owner以外のデータ表示処理 } } private void Start() { // Onwerかどうかを表示 SetOptionalText(Networking.LocalPlayer); } // Cubeをインタラクトしたときに呼ばれる public override void Interact() { if (Networking.LocalPlayer.IsOwner(this.gameObject)) { // Ownerが押したら、純粋にカウントアップ CountUp(); } else { // Owner以外が押したら、Ownerにカウントアップさせるように命令する SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(CountUp)); } } // Ownerが値を+1する処理 public void CountUp() { CountData++; // データ更新 RequestSerialization(); // 同期更新 } // 同期変数の値をUIに表示する処理 public void DisplayCountData() { DisplayDataText.text = _countData.ToString(); // データ表示更新 } // プレイヤーがOwnerかどうかをテキストで表示させる処理 public void SetOptionalText(VRCPlayerApi player) { if (player.IsOwner(this.gameObject)) { // Owner側の処理 OptionText.text = $"<color=red>{player.displayName} is Owner!</color>"; } else { // Owner以外の処理 OptionText.text = $"{player.displayName} isn't Owner"; } } }
改変としては、OnDeserializationをOnValueChangedに書き換えただけです。
この方がコードとしても見やすくていいですね。ゴチャゴチャしてない感じ。
Ownerを移行して処理する例の改変例
元のスクリプトは以下のものです。
hatuxes.hatenablog.jp
これを同様に改変してみました。以下の通りです。
using UdonSharp; using UnityEngine.UI; using VRC.SDKBase; [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] public class OwnerTransfer_Countup_System_WithOnValueChanged : UdonSharpBehaviour { [UdonSynced(UdonSyncMode.None), FieldChangeCallback(nameof(CountData))] private int _countData; // データ本体 public Text DisplayDataText; // データを表示するText public Text OptionText; // 誰がOwnerかを表示するText // OnValueChanged用のプロパティ public int CountData { get => _countData; set { _countData = value; DisplayCountData(); // Owner以外のデータ表示処理 } } private void Start() { // Onwerかどうかを表示 SetOptionalText(Networking.LocalPlayer); } // Cubeをインタラクトした時に呼ばれる public override void Interact() { // 新にOwnerになる var player = Networking.LocalPlayer; Networking.SetOwner(player, this.gameObject); if (player.IsOwner(this.gameObject)) { // カウントアップ処理 CountUp(); } } // Ownerが移行した際の処理 // このイベントはインスタンスにいる全員に発行されるので、Network.LocalPlayerを用いる // 引数のplayerは新たなオーナーを指す public override void OnOwnershipTransferred(VRCPlayerApi player) { SetOptionalText(Networking.LocalPlayer); } // Ownerが値を+1する処理 public void CountUp() { CountData++; // データ更新 RequestSerialization(); // 同期更新 } // 同期変数の値をUIに表示する処理 public void DisplayCountData() { DisplayDataText.text = _countData.ToString(); // データ表示更新 } // プレイヤーがOwnerかどうかをテキストで表示させる処理 public void SetOptionalText(VRCPlayerApi player) { if (player.IsOwner(this.gameObject)) { // Owner側の処理 OptionText.text = $"<color=red>{player.displayName} is Owner!</color>"; } else { // Owner以外の処理 OptionText.text = $"{player.displayName} isn't Owner"; } } }
改変としては先ほど同様、OnDeserializationをOnValueChangedに書き換えただけです。
一つ良くなった点として、OnDeserializationで書いていた時と違い、RequestSerialization()
を遅延しなくてもしっかりと動作出来たということがありました。
これは、OnValueChangedが速く同期出来るという恩恵によるものです。
まとめ
結論として、OnValueChangedは結構早く同期してくれるよということでした。
余談ですが、以前に私がUdonのツイートをした際に、CyanLaserさんから「同期された変数を用いる場合は、SendCustomNetworkEvent
を用いない方がいいよ」というリプライを頂きました。
With OnVariableChanged everything can be handled in two events. When dealing with Synced variables, OnDeserialization or OnVariableChange should be used and not SendCustomNetworkEvent. This causes race conditions as synced data may not arrive in time.
— CyanLaser (@CyanLaser) 2021年8月16日
Translated with DeepL.
SendCustomNetworkEvent
を用いてしまうと、同期変数とSendCustomNetworkEvent
で同期頻度が異なることから、予想し得ない処理が発生する可能性があるため、極力SendCustomNetworkEvent
内で同期変数を処理しない方が良いみたいです。現段階で私がこの新しい設計に慣れていなかったので、今回この記事を書くことで理解を深められたらなと思い、記事を書くことにしました。
この記事が、誰かの役に立つといいなとは同時に思っています。
今回も、サンプルプロジェクトはGithubに置いておきます。
2.3のシーンが今回の内容のものになっているので、詳しく見たい方はご覧ください。
github.com