【引数対応！】新・SendCustomNetworkEventの使い方

**はじめに**
ようやく`UdonSharpのSendCustomNetworkEvent`に引数の受け渡し機能が追加されましたね！！
このアップデートによって`SendCustomNetworkEvent`の使い方が少し変わったので、新しくまとめてみました

# もくじ

0. 新しいSDKにするには
  
1. 新しいSDKでできるようになったこと
  
2. SendCustomEventとSendCustomNetworkEventの違い
  
3. SendCustomNetworkEventの簡単な使い方
  
4. 新しいSDKで追加された便利なもの
  

# 0. 新しいSDKにするには

新しいSendCustomNetworkEventを使うにはVRChatSDK 3.8.1以上が必要です

まだアップデートしていない人はVCCからアップデートしておきましょう！

![20250525081716imagepng](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3962799/f762ebf6-b069-44de-ba7c-2b97126fefc4.png)

(VRChatSDK - Worldsと- Baseの2種類がありますが、両方ともアップデートしておけば良いと思います)

# 1. 新しいSDKでできるようになったこと

新しいSDKでは

- `SendCustomNetworkEvent`に引数の受け渡し機能の追加
  
- `NetworkEventTarget.Others`、`NetworkEventTarget.Self`の追加
  
- NetworkEventを呼び出したプレイヤーの取得
  
- `VRC.Udon.Common.Interfaces.NetworkEventTarget.~~~`を短く記述
  

ができるようになりました！

これ以外にも追加されたものはありますが、ここでは紹介しません...（難しく私にはわかりませんでした）

詳しくは[公式サイトの説明](https://creators.vrchat.com/worlds/udon/networking/events/)をごらんください

# 2. SendCustomEventとSendCustomNetworkEventの違い

今回ここで主に取り扱う`SendCustomNetworkEvent`と似たものとして`SendCustomEvent`というものがあります

両者の違いは**ネットワーク同期するかどうか**です
`SendCustomNetworkEvent`はネットワーク同期しますが、`SendCustomEvent`はネットワーク同期しません

`SendCustomNetworkEvent`を使うと自分以外のプレイヤーにもその動作を行わせることができますが、`SendCustomEvent`だと自分にしかその動作を行わせることができません

（その代わりにSendCustomEventには`SendCustomEventDelayedSeconds`というn秒後にその動作を行わせることができる、というメソッドがあったりします）

また、**新しいSDKではSendCustomNetworkEventで引数が使えるようになりましたが、SendCustomEventでは引数は使えません！！！**

そのSendCustomEventで引数が使えないという制限の回避策として今回のアップデートで`NetworkEventTarget.Self`が追加されたようです。そのことについては5で解説します

:::note info

C#での呼び出しとの比較↓

:::

<details><summary>C#での呼び出しとの比較</summary>

`SendCustomEvent`は同期の機能がないもの、と説明しましたが同期が必要でない部分であればわざわざこれを使わなくても良かったりします

じゃあどうやるのといえば大したことではないのですが、C#のメソッドの呼び出しと同じように記述することでUdonSharpでも同じようにメソッドを呼び出すことができます

```csharp:SendCustomEventを使用した例
public class WhiteCube : UdonSharpBehaviour
{
    void Start()
    {
        SendCustomEvent("Greet");
    }

    public void Greet()
    {
        Debug.Log("こんにちは！");
    }
}
```

```ruby:出力結果
  こんにちは！
```

上記の例は`SendCustomEvent`を使った例ですが、これは以下のように書くこともできます

```csharp:SendCustomEventを使用しない例
public class WhiteCube : UdonSharpBehaviour
{
    void Start()
    {
        Greet();
    }

    public void Greet()
    {
        Debug.Log("こんにちは！");
    }
}
```

```ruby:出力結果
  こんにちは！
```

このようにどちらで書いても出力結果は変わりません
しかし両者には明確な差があります。

**`SendCustomEvent`を使用した場合、そのメソッドの返り値を受け取ることができません！！**
２つ目の例のやり方ではそのメソッドの返り値を受け取ることが可能です

なのでぶっちゃけあんまり`SendCustomEvent`を使う場所はない気がするのですが、`SendCustomEventDelayedSeconds`にはしっかりと使う場面があります

その場面とは、
n秒後に指定の動作をさせたいというときです。

今まででUnityを触ったことのある人はコルーチン使えばいいやん、と思ったかもしれませんがUdonSharpではコルーチンは使用不可能です

具体的なコードについてはハツェさんの[一定間隔で処理をするやつの正攻法と裏技 - ハツェの真時代傾向璋](https://hatuxes.hatenablog.jp/entry/2021/09/16/141518)をごらんください

</details>

# SendCustomNetworkEventの簡単な使い方

**新しくなった機能を利用するにはVRC.SDK3をインポートするのが（おそらく）必須です！！**

## 引数について

SendCustomNetworkEventの引数は

第1引数　：メッセージを送る対象
第2引数　：実際に実行させるメソッドの名前(String)
第3引数～：メソッドに渡す引数（任意）

となっています。

- 第1引数
  
  メッセージを送る対象をここで指定します
  選ぶことのできるものは以下のとおりです
  
  | 指定できる種類 | 意味  |
  | --- | --- |
  | NetworkEventTarget.All | そのワールドにログインしている全員に送信する |
  | NetworkEventTarget.Owner | そのオブジェクトのオーナにのみ送信する |
  | NetworkEventTarget.Others | 自分以外の全員に送信する |
  | NetworkEventTarget.Self | 自分のみに送信する |
  
  アップデートでは`Others`と`Self`が追加されたようです
   また、従来のやり方では`VRC.Udon.Common.Interfaces.NetworkEventTarget.~~~`と、呪文レベルで長かった文章が`NetworkEventTarget.~~~`で良くなっています
  
  （短縮した書き方を使用する場合には`using VRC.Udon.Common.Interfaces;`を追加しておきましょう）
  

---

- 第2引数
  
  実行するメソッドの名前を指定します
  名前はStringで与える必要があります。直接`"hogehoge"`と指定しても良いですし、`nameof(hogehoge)`として与えても良いです
  
  （公式的にはnameofを使用する方法を推奨している気がしますが、UdonSharpの記事では前者の直接文字列で指定する方法をよく見る気もします。まあどっちでもいいと思います）
  
  ---
  
- 第3引数以降
  
  メソッドに渡す引数を設定することができます
  ここで設定できる引数の上限数は8個までで、同期変数として使用できる型ならおそらく全て利用可能です
  
  よく使いそうな型としては`bool、int、long、float、Vector2、Vector3、char、String、VRCUrl`などがサポートされています
  
  現在サポートされているすべての型を見るには[公式サイト](https://creators.vrchat.com/worlds/udon/networking/network-details/#synced-variables)をごらんください
  

## 簡単な例

まず簡単な例をここで紹介します

```csharp:WhiteCube.cs
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
using VRC.SDK3.UdonNetworkCalling;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class WhiteCube : UdonSharpBehaviour
{
    void Start()
    {
        SendCustomNetworkEvent(NetworkEventTarget.All, "Greet", "hogehoge");
    }

    [NetworkCallable]
    public void Greet(string name)
    {
        Debug.Log(name + "さん、こんにちは！");
    }
}
```

```ruby:出力
hogehogeさん、こんにちは！
```

まず、使い始めるときには`using VRC.SDK3;`とusingの部分に追加するようにしましょう！
これを追加することによって新しく追加されたものを簡単に利用できるようになります

そして呼び出したいメソッドは、必ずpublicで宣言するようにして、**`[NetworkCallable]`属性を付与するようにしましょう！**
（引数を付けた呼び出しをする際にはこれが必須になります。引数を必要としないものの呼び出しではこの属性付与はおそらく必要ありませんが、公式ではつけるようにおすすめされているので引数が必要でない場合でもつけるようにしておきましょう）

<details><summary>[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]について</summary>

この部分では共有変数の同期方法について設定しています
 `BehaviourSyncMode.Manual`を任意のものに変更することで同期方法を変更することができます

設定可能な値は以下のとおりです

| モード | 意味  |
| --- | --- |
| None | 同期を行わない |
| NoVariableSync | 動作に共有変数が強制されない...らしいです（よくわかりません） |
| Continuous | 一定周期ごとに自動的に変数の同期を行う |
| Manual | 手動で変数の同期を行う |

Continuousは自動的に変数の同期を行うが、Manualだと手動で設定したタイミングでしか同期が行われないので、ネットワーク的にManualを使うことがおすすめ、とされています（多分）
 しかし、Manualモードの弊害として、`VRC ObjectSync`コンポーネントが使用できなくなります。

ちなみにNoneにすると全く同期が行われないらしく、この状態では`SendCustomNetworkEvent`は使用できなくなります。
 NoVariableSyncについては同期を行わないという点ではNoneと共通していますが、唯一違う点として、`SendCustomNetworkEvent`が使用可能です

同期をオフにしたいけれど`SendCustomNetworkEvent`を使いたい！というときにはNoVariableSyncを使いましょう

ちなみに`[UdonBehaviourSyncMode(BehaviourSyncMode.~~~)]`のようなものを書かない場合、このようにインスペクターから同期方法を設定することができます

![imagepng](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3962799/568a1704-caae-4dbc-8018-2a05d33feb99.png)

しかし、`[UdonBehaviourSyncMode(BehaviourSyncMode.~~~)]`を書くと
このように設定項目がグレースケールになり、インスペクター上から変更ができなくなります
（ちなみに下の例はManualモードで固定しています）

![imagepng](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3962799/b396f3d8-7e45-4921-b3c3-ad6e72f273eb.png)

コード側から指定したほうが安全性も増すと思うのでできるだけ`[UdonBehaviourSyncMode(BehaviourSyncMode.~~~)]`という書き方を使ったほうが良いと思います

もっと詳しくこの同期方法について知りたい方は[シン・U# 入門 ② - ハツェの真時代傾向璋](https://hatuxes.hatenablog.jp/entry/2021/05/24/171942#%E5%A4%89%E6%95%B0%E3%81%AE%E5%90%8C%E6%9C%9F%E6%96%B9%E6%B3%95)を見ることをおすすめします！

</details>

## 別のクラスのメソッドを呼び出すには

SendCustomNetworkEventでは別のクラスのメソッドを呼び出すことができます！
（これができるようになると、「特定の物にインタラクトしたときに、別の物の位置を変える」というようなことができるようになります。）

以下はWhiteCubeにインタラクトしたときに、RedCubeの位置を少し上にするというサンプルです
WhiteCubeにはWhiteCube.csが、RedCubeにはRedCube.csがそれぞれアタッチされています
（見やすくなるようにサンプルコードではusingが省略されていますが、RedCube.csではちゃんと`using VRC.SDK3;`を追加しましょう！）

```csharp:WhiteCube.cs
/*
    呼び出す側のUdonです
*/
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class WhiteCube : UdonSharpBehaviour
{
    [SerializeField]
    private UdonBehaviour redCubeUdon;// RedCubeのUdonBehaviourをここでセット

    public override void Interact()
    {
        redCubeUdon.SendCustomNetworkEvent(NetworkEventTarget.All, "MoveUp", 1);
    }
}
```

```csharp:RedCube.cs
/*
    呼び出される側のUdonです
*/
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class RedCube : UdonSharpBehaviour
{
    [NetworkCallable]
    public void MoveUp(int value)
    {
        transform.position += new Vector3(0, value, 0);
    }
}
```

別のクラスのメソッドを呼び出すには、そのクラスのインスタンス（UdonBehaviour）を取得することがまず必要です
この例では[SerializeField]属性を付与しているのでredCubeUdonはインスペクター上で指定できます
下の写真で青く囲まれている部分にRedCubeを指定しましょう

![imagepng](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3962799/081250d2-8853-4031-86ca-6a68e984bd3c.png)

これで、WhiteCubeに対してインタラクトするとRedCubeが、`SendCustomNetworkEvent`で指定した数値だけ上昇するようになりました！
（SendCustomNetworkEventでインスタンス内のプレイヤー全員にこのメッセージを送信しているため他のプレイヤーからも同じように見えるようになっています）

![Recording 2025-05-27 063709.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3962799/61b3bf80-02df-4606-86ee-6d3b6b83a79b.gif)


このようにすることで他のクラスのメソッドについても`SendCustomNetworkEvent`で呼び出せます

## 特定のプレイヤーにのみSendCustomNetworkEventを実行させる

`SendCustomNetworkEvent`には残念ながら特定のプレイヤーにのみ送信する、といったものは用意されていません
しかし、引数にプレイヤーIDを与え、それをもとに「特定のプレイヤー」かどうか判定するといった手法を取ることでそれを回避することができます

```csharp:WhiteCube.cs
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
using VRC.SDK3.UdonNetworkCalling;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class WhiteCube : UdonSharpBehaviour
{
    public override void Interact()
    {
        VRCPlayerApi localPlayer = Networking.LocalPlayer;
        Debug.Log("あなたのプレイヤーID: " + localPlayer.playerId);
        SendCustomNetworkEvent(NetworkEventTarget.All, "Greet", "hoge", 2);
    }


    [NetworkCallable]
    public void Greet(string name, int playerId)
    {
        VRCPlayerApi localPlayer = Networking.LocalPlayer;
        if (localPlayer.playerId == playerId)
        {
            Debug.Log("こんにちは " + name + "さん! (Player ID: " + playerId + ")");
        }
        else
        {
            Debug.Log("プレイヤーIDが一致しません。");
        }
    }
}
```

上の例では`SendCustomNetworkEvent`の引数として 2 を指定しています
実際に使うときはこの部分を、その実行させたいプレイヤーのIDに変更すれば良いと思います
Greetメソッドではif文を使って、与えられたプレイヤーIDと自分のプレイヤーIDが同じかどうか判定しています

このとき注意してほしいことが一つあって、**呼び出される側のメソッドを宣言するときにはデフォルト引数を使用することができません！**

```scharp:WhiteCube.cs
[NetworkCallable]
public void Greet(string name, int playerId = -1)
{
    //何らかの処理
}
```

このように`int playerId = -1`として宣言することはできません
（ちなみにこれはコンパイル時にエラーになります）

# 4. 新しいSDKで追加された便利なもの

新しいSDKでは`SendCustomNetworkEvent`を呼び出したプレイヤーのVRCPlayerApiを取得できるようになりました！

具体的には`NetworkCalling.CallingPlayer`プロパティを使用します
このプロパティの値としてはネットワーク呼び出しが行われた場合には、その呼び出しを行ったプレイヤーのApiを、ネットワーク呼び出し出ない場合にはNoneを取得できます

以下はCallingPlayerプロパティを使用してSendCustomNetworkEventを呼び出したプレイヤーのプレイヤーIDを取得する例です

```csharp:WhiteCube.cs
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
using VRC.SDK3.UdonNetworkCalling;

[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class WhiteCube : UdonSharpBehaviour
{
    void Start()
    {
        SendCustomNetworkEvent(NetworkEventTarget.All, "Greet", "hogehoge");
    }

    [NetworkCallable]
    public void Greet(string name)
    {
        VRCPlayerApi caller = NetworkCalling.CallingPlayer;
        Debug.Log(name + "さん、こんにちは！" + "プレイヤーIDは " + caller.playerId + " です。");
    }
}
```

```ruby:出力
hogehogeさん、こんにちは！プレイヤーIDは1です。
```

## もうちょっと細かい仕様説明

一部今までで説明した内容と被る部分もありますが、`SendCustomNetworkEvent`を使うときにいくつか注意しないといけないことがあります

- 引数を使った呼び出しを使う際は`[NetworkCallable]`の付与が必須
  - 以前までの互換性を保つために引数を使わない場合には付与しなくても良くなっている
- 呼び出し先のメソッドではオーバーロードが使用不可
- アクセス修飾子`public`の付与が必須
- 呼び出し先のメソッドではデフォルト引数の設定ができない
```csharp:動かない例
public void Greet(string name, int playerId = -1)
```
- 一度に送れるデータの上限値は16KBまで
  - **1KBを超えるデータは複数に分割されて送信される**
- 同期モードをNoneにした場合は`NetworkEventTarget.Self`にしても動作させることはできない
```csharp:動かない例
[UdonBehaviourSyncMode(BehaviourSyncMode.None)]//←同期モードがNoneになっている！
public class WhiteCube : UdonSharpBehaviour
{
    public override void Interact()
    {
        SendCustomNetworkEvent(NetworkEventTarget.Self, "Greet");
    }

    [NetworkCallable]
    public void Greet()
    {
        Debug.Log("Hello from WhiteCube!");
    }
}
```