同期
対応(23)
Section titled “対応(23)”| 書き方 | 説明 | 注意 |
|---|---|---|
[UdonSynced] private int value; | 整数の同期 | |
[UdonSynced] private bool flag; | 真偽値の同期 | |
[UdonSynced] private string label; | 文字列の同期 | |
[UdonSynced] private Vector3 point; | 構造体の同期 | |
[UdonSynced] private Pair pair; | 自分で作った構造体の同期 | |
[UdonSynced] private Mode mode; | 列挙型の同期 | |
[UdonSynced] private int[] values; | 配列の同期 | |
[UdonSynced(UdonSyncMode.Linear)] | 補間つきの同期 | |
[UdonSynced(UdonSyncMode.Smooth)] | なめらかな補間 | |
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] | 手動同期 | |
[UdonBehaviourSyncMode(BehaviourSyncMode.Continuous)] | 連続同期 | |
[UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)] | 変数を同期しない方式 | |
public override void OnDeserialization() | 値の受信時 | |
[FieldChangeCallback(nameof(Value))] | 変数ごとの通知 | |
Value = 1; | 所有者側での代入 | |
public override void OnPreSerialization() | 送る直前 | |
public override void OnPostSerialization(SerializationResult result) | 送信後 | |
Networking.IsOwner(gameObject) | 所有者かどうかの判定 | |
Networking.SetOwner(Networking.LocalPlayer, gameObject) | 所有権を取る | |
public override void OnOwnershipTransferred(VRCPlayerApi player) | 所有権の移動後 | |
public override bool OnOwnershipRequest(...) | 要求の可否の決定 | |
[NetworkCallable] | 呼ばれる側の許可 | |
SendCustomNetworkEvent(NetworkEventTarget.All, ...) | 引数つきの呼び出し |
非対応(15)
Section titled “非対応(15)”| 書き方 | 説明 | エラー | 理由 | 代替 |
|---|---|---|---|---|
[UdonSynced] private Bag bag;(中に配列がある) | 配列を持つ struct を同期する | TUKI0103 | 未実装 | 配列だけ別のフィールドに出して、それぞれ同期する |
[UdonSynced] private Nothing nothing;(フィールドが無い) | 中身の無い struct を同期する | TUKI0103 | 設計 | 同期したい値をフィールドとして持たせる |
[UdonSynced(UdonSyncMode.Linear)] private Tagged tagged;(中に文字列がある) | 補間できない型を持つ struct に補間を指定する | TUKI0106 | 環境 | 補間なしにするか、補間できる型だけの struct に分ける |
[UdonSynced] private int[][] grid; | 配列の配列を同期する | TUKI0103 | 環境 | 1 本の配列にして添字を計算する(配列) |
[UdonSynced] private GameObject target; | オブジェクトへの参照を同期する | TUKI0103 | 環境 | Inspector で全員に同じ参照を差し込むか、対象に番号を振ってその番号を同期する |
[UdonSynced(UdonSyncMode.Linear)] private string label; | 補間できない型に補間を指定する | TUKI0106 | 環境 | 補間なしにする |
[UdonSynced] public int Value { get; set; } | プロパティに付ける | CS0592 | 環境 | バッキングフィールドに付けて、[FieldChangeCallback] でプロパティにつなぐ |
[UdonSynced] int v = 1; | ローカル変数に付ける(メソッドの中) | CS7014 | 環境 | フィールドにする |
[UdonSynced] private static int v; | static フィールドに付ける | TUKI0110 | 環境 | static を外して普通のフィールドにする |
[UdonSynced] private const int v = 3; | const フィールドに付ける | TUKI0110 | 環境 | 同上 |
[UdonSynced] private readonly int v; | readonly フィールドに付ける | TUKI0110 | 環境 | readonly を外す |
[UdonBehaviourSyncMode(Continuous)] と [UdonSynced] int[] | 送り続ける方式で配列を同期する | TUKI0107 | 環境 | Manual にする |
[UdonBehaviourSyncMode(Manual)] と [UdonSynced(Linear)] | 呼んだときだけ送る方式で補間を指定する | TUKI0107 | 環境 | Continuous にする |
[UdonBehaviourSyncMode(None)] と [UdonSynced] | 同期しない方式で同期する変数を持つ | TUKI0107 | 設計 | 方式を変えるか、属性を外す |
[UdonBehaviourSyncMode(NoVariableSync)] と [UdonSynced] | 変数を同期しない方式で同期する変数を持つ | TUKI0107 | 設計 | 方式を変えるか、属性を外す |
[UdonSynced] を付けられるのはフィールドだけです。
書いても既定と同じになる綴り
Section titled “書いても既定と同じになる綴り”移植したコードに出てくる次の 3 つは、そのまま通ります。どれも書かなかったときと同じ意味なので、 残しても外しても動きは変わりません。
| 綴り | 意味 |
|---|---|
[UdonBehaviourSyncMode(BehaviourSyncMode.Any)] | 方式を指定しません。付けなかったときと同じで、コンポーネント側の設定がそのまま使われます |
[UdonSynced(UdonSyncMode.None)] | 値をそのまま運びます。[UdonSynced] だけを書いたときと同じです |
[UdonSynced(UdonSyncMode.NotSynced)] | 同期しません。[UdonSynced] を付けなかったときと同じで、同期する変数になりません |
同期の仕組み
Section titled “同期の仕組み”公式ドキュメントが保証しているのは、最初に入ったプレイヤーが master になることと、
PlayerObject の所有者が本人で固定されることの 2 つだけです。値が届くこと・送った順に届くこと・
一度だけ届くことは、いずれも保証されていません。以下の図は保証ではなく、公式ドキュメントの説明に
もとづきます。
| 何を決める仕組みか | 同期するオブジェクトごとに「いまこのプレイヤーのもの」と決めます。対象は GameObject で、Networking.IsOwner(gameObject) のように渡して調べます |
| 何のために必要か | 同期する変数へ代入できるのは所有者だけです。所有者でないプレイヤーが代入しても、その値はほかのプレイヤーへ送られません |
| 最初の所有者 | 最初に入ったプレイヤーが master になり、SetOwner() を一度も呼んでいないオブジェクトの所有者は master です。保証されているのは master の決定規則だけで、そのオブジェクトの所有者が master であることは保証されていません |
| 人数 | 公式ドキュメントは 1 人と説明しています。ただし保証はされておらず、所有者が 2 人いる状態も、誰も所有していない状態も報告されています |
図 1 値を同期させたときの動き(手動同期)
Section titled “図 1 値を同期させたときの動き(手動同期)”sequenceDiagram
autonumber
participant O as 所有者
participant N as ネットワーク
participant P as 受け取る側
O->>O: 同期する変数へ代入する
O->>O: RequestSerialization() を呼ぶ
O->>O: OnPreSerialization() が呼ばれる
O->>N: そのプログラムの同期する変数を、まとめて一度に送る
O->>O: OnPostSerialization() が呼ばれる
N->>P: 受信する
P->>P: 受信した値を、変数へ 1 つずつ代入する
P->>P: 代入のたびに FieldChangeCallback が呼ばれる
P->>P: すべて代入し終えてから OnDeserialization() が呼ばれる
同期の注意点
Section titled “同期の注意点”| 箇所 | 注意 |
|---|---|
RequestSerialization() | 呼んだ回数だけ送られるわけではありません。何度呼んでも、次に送られるのは一度です |
| 送信 | 途中で失われることがあります。失われても送信側には通知されません |
OnPostSerialization() | 分かるのは送信を試みたところまでです。相手が受信したかどうかは分かりません |
| 受信側の代入 | 順序は決まっていません。複数の変数がそろった状態を見るには OnDeserialization() まで待ちます |
所有者側で呼ばれるもの
Section titled “所有者側で呼ばれるもの”| 図 1 の下半分 | 受け取る側の動作で、所有者では起きません |
OnDeserialization() | 値を受信したときに呼ばれます。所有者は自分が送った値を受信しないので通常は呼ばれませんが、公式ドキュメントに「所有者では呼ばれない」という記述はなく、所有者で呼ばれた事例が報告されています。所有者かどうかの判定には使えません |
FieldChangeCallback | 受信した値をフィールドへ直接入れず、プロパティの設定側を通す仕組みです。所有者は値を受信しないのでこの経路では実行されません(所有者がプロパティへ代入すれば、その代入自体で設定側が実行されます。これは同期とは無関係な C# の動作です) |
| 代入のしかた | 所有者 | 受け取る側 |
|---|---|---|
Value = 1(プロパティ) | 代入した時点で設定側が実行される | 受信時に設定側が実行される |
backing = 1(フィールド) | 実行されない | — |
両側で同じ処理が実行されているように見えますが、経路は別です。所有者がフィールドへ直接代入すると、 所有者側だけ処理が抜けます(所有者側での代入)。
図 2 手動同期と連続同期
Section titled “図 2 手動同期と連続同期”[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] を付けると手動同期、
[UdonBehaviourSyncMode(BehaviourSyncMode.Continuous)] を付けると連続同期になります。
図 1 は手動同期の場合です。
sequenceDiagram
participant O as 所有者
participant N as ネットワーク
O->>O: 同期する変数へ代入する
alt 手動同期
O->>O: RequestSerialization() を呼ぶ
O->>N: 送信する
O->>O: もう一度、同期する変数へ代入する
Note over O: 代入しただけでは送信されない
else 連続同期
O->>N: 送信する
O->>N: 送信する(値が変わっていなくても繰り返す)
Note over O: RequestSerialization() は働かない
end
手動同期(Manual) | 連続同期(Continuous) | |
|---|---|---|
| 送信のタイミング | 所有者による RequestSerialization() の呼び出し時 | 自動。値が変わっていなくても繰り返し送る |
| 同期する変数への代入時 | 送信されない。次に RequestSerialization() を呼ぶまで、その値は所有者の手元にとどまる | そのままにしておけば、次の送信に載る |
RequestSerialization() の呼び出し時 | 送信が予約される | 何も起きない。送信は増えない |
| 送信の詰まり | 保持して送り直す | 欠落して、ログにエラーが出る |
| 1 回の送信の上限 | およそ 280,496 バイト | およそ 200 バイト |
| 向いているもの | 得点や状態など、変わったときだけ送る値 | 位置や回転など、変わり続ける値 |
連続同期で OnPreSerialization() と OnPostSerialization() が呼ばれるかどうかは、公式ドキュメントに
記述がありません(一般には送信のたびに呼ばれるものとして扱われています。呼ばれる前提で軽くしておけば、
どちらでも困りません)。
送信量と回数の目安
Section titled “送信量と回数の目安”公式ドキュメントが数値を挙げているのは次の 3 つです。原文でも「約」「およそ」と付いた概数で、 VRChat 側の更新で変わることがあります。
| 何の量か | 目安 |
|---|---|
| 1 秒あたりに送信できる量 | 約 11 キロバイト |
| 1 回の送信の上限 | 手動同期でおよそ 280,496 バイト、連続同期でおよそ 200 バイト |
| 1 つのネットワークイベントを呼べる回数 | 既定で 1 秒あたり 5 回。[NetworkCallable(100)] のように書いて最大 100 回まで |
| 送信側の全体でイベントを呼べる回数 | 1 秒あたり約 100 回。動的に決まり、設定できません |
連続同期の約 200 バイトについては、VRChat の担当者が別の場所で約 256 バイトと述べていて、公式 ドキュメントの数値と食い違っています。小さいほうで見積もるのが安全です。
| 超えたとき(ネットワークイベント) | 捨てられずに送信側で順番待ちになります。消えはしませんが、届くのが遅れます |
| 超えたとき(同期する変数) | 上の表のとおりで、手動同期は保持して送り直し、連続同期は欠落します |
| 全体の上限 | 自分で指定したレートとは別なので、指定に収まっていてもこちらに当たることがあります |
| 連続同期の 200 バイト | そのプログラムに置いた同期する変数の全体に対する上限です。変数を増やすほど上限に近づきます |
| 1 回の送信 | ネットワーク上でも 1 つになるとは限りません。1024 バイトを超えると内部で分割されるので、1 回のつもりで呼んだ送信が回数の上限に当たることがあります |
次の数値は公式ドキュメントに書かれていません。
- 連続同期が 1 秒あたり何回送るか(二次の資料では「約 10 回」とされますが、実測か推定かが示されていません)
- 文字列の長さの上限(1 文字 2 バイトとして、上の上限に収まる範囲、という形でしか言えません)
- 配列の要素数の上限
図 3 所有権の遷移
Section titled “図 3 所有権の遷移”所有権は、プレイヤーがそれぞれ「いま誰が所有者か」を保持する形です。移すと呼んだ側が先に切り替わり、 ほかのプレイヤーへは遅れて伝わります。
sequenceDiagram
participant R as 要求した人
participant O as いまの所有者
participant P as ほかのプレイヤー
R->>R: SetOwner(自分) を呼ぶ
R->>R: OnOwnershipRequest() が呼ばれる
O->>O: 同じ OnOwnershipRequest() が、こちらでも呼ばれる
R->>R: 受けるなら、その場で所有者は自分になる
R->>R: OnOwnershipTransferred() が呼ばれる
R-->>P: ほかのプレイヤーへ伝わる
P->>P: ほかのプレイヤーから見た所有者も、要求した人になる
P->>P: OnOwnershipTransferred() が呼ばれる
OnOwnershipRequest() | 要求した人といまの所有者の両方で、それぞれのローカルで実行されます。戻り値が割れると食い違いが残るので、判定に使ってよいのは両者から確実に同じに見えるものだけです(実行中に変わらない静的な情報と、引数で渡ってくるプレイヤーの識別子。同期する変数は該当しません) |
| いまの所有者が退出済みのとき | 呼ばれず、自動で割り当てられます |
| 移している最中 | 要求した人が切り替わってからほかのプレイヤーへ伝わるまでのあいだは、所有者が 2 人いるように見えます |
PlayerObject | 入室したプレイヤーごとに 1 つずつ作られるオブジェクトです。所有者は本人で固定されていて、他人へ移せません(保証の範囲は Start と OnDeserialization の実行中で、常に正しいという意味ではありません) |
図 4 2 人が同時に所有権を取る
Section titled “図 4 2 人が同時に所有権を取る”sequenceDiagram
participant X as 1 人目
participant N as ネットワーク
participant Y as 2 人目
X->>X: SetOwner(自分) を呼ぶ
Y->>Y: SetOwner(自分) を呼ぶ
X->>X: 1 人目から見た所有者は、1 人目
Y->>Y: 2 人目から見た所有者は、2 人目
X->>N: 同期する変数へ代入して送る
Y->>N: 同期する変数へ代入して送る
N->>N: 先に着いたほうが所有者になる
N->>X: 勝ったほうの値を受信する
N->>Y: 勝ったほうの値を受信する
負けたほうの代入は、勝ったほうの値を受信した時点で消えます。どちらも自分の側では成功するので、 所有権の取得は排他制御になりません。
そのほかに起きること
Section titled “そのほかに起きること”- 衝突や持ち上げによって所有者が変わることがあります。このとき何も呼ばれません
- 所有者が退出すると自動で割り当てられます。割り当て先の規定はありません。
OnOwnershipRequest()は呼ばれず、退出の通知との前後関係も決まっていません - 図 4 の状態から戻らなくなることがあります。通信の遅延が小さい環境ほど起こりやすいようです
よくある勘違い
Section titled “よくある勘違い”| 勘違い | 実際 |
|---|---|
所有者は 1 人なので、SetOwner() を呼べば排他制御になる | 2 人が同時に SetOwner() を呼ぶと、どちらも自分の側では所有者になります。排他制御にはなりません |
OnDeserialization() が呼ばれたということは、自分は所有者ではない | OnDeserialization() が所有者で呼ばれた記録があります。所有者かどうかの判定には使えません |
| 所有者でないまま代入した値は、すぐ元に戻る | エラーは出ません。その値がいつ元に戻るかは公式ドキュメントに記述が無く、解説によって「数フレーム後」と「不明」で割れています |
| 途中から入ってきたプレイヤーにも、それまでのネットワークイベントが届く | 同期する変数の値は送られますが、ネットワークイベントは送り直されません |
| 入室した直後は、まだ何も呼ばれない | 入室したところで OnDeserialization() が呼ばれます。そこで音やアニメーションを鳴らしていると、入室した瞬間にまとめて鳴ります |
同期する変数
Section titled “同期する変数”[UdonSynced] を付けたフィールドが、同期の対象になります。
最も基本的な書き方です。
using UnityEngine;using Tsukimi;
public class AttributesSynced : TsukimiBehaviour{ [UdonSynced] private int value;
void Start() { value = 1; RequestSerialization(); Debug.Log(value); // => 1 }}真偽値の同期
Section titled “真偽値の同期”bool にも付けられます。
using UnityEngine;using Tsukimi;
public class SyncBool : TsukimiBehaviour{ [UdonSynced] private bool flag;
void Start() { Debug.Log(flag); // => false }}文字列の同期
Section titled “文字列の同期”string にも付けられます。
using UnityEngine;using Tsukimi;
public class SyncString : TsukimiBehaviour{ [UdonSynced] private string label = "a";
void Start() { Debug.Log(label); // => "a" }}構造体の同期
Section titled “構造体の同期”実行環境が持っている構造体は同期できます。
using UnityEngine;using Tsukimi;
public class SyncVector3 : TsukimiBehaviour{ [UdonSynced] private Vector3 point = new Vector3(1f, 2f, 3f);
void Start() { Debug.Log(point.x); // => 1 }}自分で作った構造体の同期
Section titled “自分で作った構造体の同期”自分で作った struct も、中のフィールドがすべて同期できる型なら同期できます。
using UnityEngine;using Tsukimi;
public struct Pair { public int A; public float B; }
public class S_sync_struct : TsukimiBehaviour{ [UdonSynced] private Pair pair;
void Start() { pair.A = 3; pair.B = 1.5f; Debug.Log(pair.A); } // => 3}列挙型の同期
Section titled “列挙型の同期”列挙型は中身が整数なので、そのまま同期の対象にできます。
using UnityEngine;using Tsukimi;
public class SyncEnum : TsukimiBehaviour{ private enum Mode { Off, On }
[UdonSynced] private Mode mode;
void Start() { Debug.Log((int)mode); // => 0 }}配列も同期できます。
using UnityEngine;using Tsukimi;
public class ArraysFieldSynced : TsukimiBehaviour{ [UdonSynced] private int[] values = new int[4];
void Start() { values[0] = 10; RequestSerialization(); }}補間つきの同期
Section titled “補間つきの同期”届いた値のあいだを埋める指定です。
using UnityEngine;using Tsukimi;
public class AttributesSyncedInterpolated : TsukimiBehaviour{ [UdonSynced(UdonSyncMode.Linear)] private float value;
void Start() { Debug.Log(value); // => 0 }}using UnityEngine;using Tsukimi;
public class R_sync_interp : TsukimiBehaviour{ [UdonSynced(UdonSyncMode.Linear)] private string label = "a";
void Start() { Debug.Log(label); }}なめらかな補間
Section titled “なめらかな補間”Linear と同じく届いた値のあいだを埋めますが、補間の方法が違います。
using UnityEngine;using Tsukimi;
public class SyncSmooth : TsukimiBehaviour{ [UdonSynced(UdonSyncMode.Smooth)] private float amount;
void Start() { Debug.Log(amount); // => 0 }}送るタイミング
Section titled “送るタイミング”[UdonBehaviourSyncMode] で、そのプログラムの同期の方式を決めます。
手動同期にします。
using UnityEngine;using Tsukimi;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]public class AttributesSyncModeManual : TsukimiBehaviour{ [UdonSynced] private int value;
void Start() { value = 1; RequestSerialization(); }}連続同期にします。
using UnityEngine;using Tsukimi;
[UdonBehaviourSyncMode(BehaviourSyncMode.Continuous)]public class AttributesSyncModeContinuous : TsukimiBehaviour{ [UdonSynced] private int value;
void Start() { value = 1; }}変数を同期しない方式
Section titled “変数を同期しない方式”同期する変数を持たない宣言です。
using UnityEngine;using Tsukimi;using VRC.Udon.Common.Interfaces;
[UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)]public class AttributesSyncModeNoVariableSync : TsukimiBehaviour{ private int hits;
public override void Interact() { SendCustomNetworkEvent(NetworkEventTarget.All, nameof(Ping)); }
public void Ping() { hits = hits + 1; Debug.Log(hits); }}using UnityEngine;using Tsukimi;
[UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)]public class SyncNoVariableSyncWithSynced : TsukimiBehaviour{ [UdonSynced] private int count;
public override void Interact() { count = 1; }}同期のイベント
Section titled “同期のイベント”同期に伴って呼ばれるメソッドです。基底が宣言しているので override で書きます。
同期した値が届いたあとに呼ばれます。
using UnityEngine;using Tsukimi;
public class SyncOnDeserialization : TsukimiBehaviour{ [UdonSynced] private int value;
public override void OnDeserialization() { Debug.Log(value); }
void Start() { Debug.Log(0); // => 0 }}変数ごとの通知
Section titled “変数ごとの通知”同期でこの変数に値が届くと、フィールドへ直接入るのではなく、指定したプロパティの設定側を通ります。
using UnityEngine;using Tsukimi;
public class AttributesFieldChangeCallback : TsukimiBehaviour{ [UdonSynced, FieldChangeCallback(nameof(Value))] private int backing;
public int Value { get { return backing; } set { backing = value; } }
void Start() { Value = 1; Debug.Log(Value); // => 1 }}所有者側での代入
Section titled “所有者側での代入”プロパティへ代入すると、その時点で設定側が実行されます。
using UnityEngine;using Tsukimi;
public class SyncFieldChangeCallbackOnWrite : TsukimiBehaviour{ [UdonSynced, FieldChangeCallback(nameof(Value))] private int backing;
public int Value { get { return backing; } set { backing = value; Debug.Log(100 + value); // => 101 } }
void Start() { Value = 1; backing = 2; Debug.Log(backing); // => 2 }}送る値を、送る直前に組み立てるためのエントリポイントです。
using UnityEngine;using Tsukimi;
public class SyncOnPreSerialization : TsukimiBehaviour{ [UdonSynced] private int value;
public override void OnPreSerialization() { value = 1; }
void Start() { RequestSerialization(); Debug.Log(value); // => 0 }}送れたかどうかを受け取れます。
using UnityEngine;using Tsukimi;using VRC.Udon.Common;
public class SyncOnPostSerialization : TsukimiBehaviour{ public override void OnPostSerialization(SerializationResult result) { Debug.Log(result.success); }
void Start() { Debug.Log(0); // => 0 }}所有者を調べたり、所有権を移したりする形です。
所有者かどうかの判定
Section titled “所有者かどうかの判定”そのオブジェクトの所有者かどうかを調べられます。
using UnityEngine;using Tsukimi;using VRC.SDKBase;
public class SyncIsOwner : TsukimiBehaviour{ [UdonSynced] private int value;
void Start() { if (Networking.IsOwner(gameObject)) { value = 1; RequestSerialization(); } }}所有権を取る
Section titled “所有権を取る”第 1 引数のプレイヤーを所有者にします。
using UnityEngine;using Tsukimi;using VRC.SDKBase;
public class SyncSetOwner : TsukimiBehaviour{ void Start() { Networking.SetOwner(Networking.LocalPlayer, gameObject); }}所有権の移動後
Section titled “所有権の移動後”所有権が移ったあとに呼ばれます。
using UnityEngine;using Tsukimi;using VRC.SDKBase;
public class SyncOnOwnershipTransferred : TsukimiBehaviour{ public override void OnOwnershipTransferred(VRCPlayerApi player) { Debug.Log(0); }
void Start() { Debug.Log(1); // => 1 }}要求の可否の決定
Section titled “要求の可否の決定”true を返すと要求を受け、false を返すと要求を断ります。
using UnityEngine;using Tsukimi;using VRC.SDKBase;
public class SyncOnOwnershipRequest : TsukimiBehaviour{ public override bool OnOwnershipRequest(VRCPlayerApi requester, VRCPlayerApi newOwner) { return true; }
void Start() { Debug.Log(1); // => 1 }}ネットワークイベント
Section titled “ネットワークイベント”変数の値ではなく、メソッドの呼び出しを送る形です。
呼ばれる側の許可
Section titled “呼ばれる側の許可”この属性を付けたメソッドだけが、ネットワーク越しに呼ばれます。
using UnityEngine;using Tsukimi;using VRC.SDK3.UdonNetworkCalling;
public class AttributesNetworkCallable : TsukimiBehaviour{ [NetworkCallable] public void Receive(int n) { Debug.Log(n); }
void Start() { Debug.Log(1); // => 1 }}using UnityEngine;using Tsukimi;using VRC.Udon.Common.Interfaces;
public class R_sync_no_callable : TsukimiBehaviour{ public void Receive(int n) { Debug.Log(n); }
void Start() { SendCustomNetworkEvent(NetworkEventTarget.All, nameof(Receive), 1); }}引数つきの呼び出し
Section titled “引数つきの呼び出し”送る側で値を並べると、受け取る側の引数に入ります。
using UnityEngine;using Tsukimi;using VRC.Udon.Common.Interfaces;using VRC.SDK3.UdonNetworkCalling;
public class ArraysSendAsNetworkEvent : TsukimiBehaviour{ private int[] scores;
void Start() { scores = new int[] { 1, 2, 3 }; SendCustomNetworkEvent(NetworkEventTarget.All, nameof(Receive), scores); }
[NetworkCallable] public void Receive(int[] values) { Debug.Log(values.Length); }}