なぜ “戻り値つき RPC” が要るのか
2025 年現在、Netcode for GameObjects(以下 UNGO)の公式 RPC API は 戻り値をサポートしていません。
旧 UNet 時代に存在した RpcResponse<T>
系 API は 完全に廃止され、代替案として「ServerRpc
→ ClientRpc
の 2 段構えで値を送り返す」パターンを推奨すると明言されています。(Unity Multiplayer)
しかし実際の開発では――
- サーバーに権限確認(例 : アイテムを拾って良い?)
- サーバーだけが知る一時的な値取得(例 : マッチメイキングで割り当てられたチーム番号)
といった “1 回こっきりで値が欲しい” ケースが日常的に現れます。毎回 ServerRpc
/ClientRpc
を手書きし、UniTaskCompletionSource
と Request ID を管理するのは 型安全でも生産的でもありません。
NetcodeGameObjectRpcAsync とは
そこで作成したのが NetcodeGameObjectRpcAsync
https://github.com/harayuu9/NetcodeGameObjectRpcAsync
キーアイデア
- Source Generator で 呼び側 も RPC ラッパ も自動生成
- UniTask<T> で async/await フレンドリー
- サーバー実行時は RPC を迂回 – 同期呼び出しでオーバーヘッドゼロ
- Request ID と Task 管理を共通基盤に集約 – 重複コード撲滅
コンパイル順序的にも安全です。Roslyn Source Generator は C# コンパイルの 前段、UNGO の ILPostProcessor は 後段 で実行されるため、相互干渉しません。
使い方 ─ たった 3 ステップ
前提:Netcode v2.2 以降 / Unity 6000.1 LTS 以降 / UniTask
-
パッケージを導入
"com.github.harayuu9.netcodegameobjectrpcasync": "https://github.com/harayuu9/NetcodeGameObjectRpcAsync.git"
-
NetworkBehaviour に属性を付与
[RpcAsyncGenerator] public partial class PlayerController : NetworkBehaviour { … }
-
“Core” メソッドを書く
[RpcAwaitable] private bool TryPickupItemCore(int itemId) { … }
あとは await TryPickupItemAsync(itemId)
を呼ぶだけ。自動生成コードは先頭に示した通りです。
フルサンプルコードはこちらをご覧ください
自動生成されるコードのポイント
処理 | 生成コードでの実装 |
---|---|
サーバー直呼び最適化 | if (IsServer) { … return UniTask.FromResult(result); } |
Request ID 生成 | RpcManager.GetNextRequestId() |
キャンセル対応 |
cancellationToken.Register(() => …) で pending Task を安全に破棄 |
単一クライアントへのレスポンス | RpcTarget.Single(senderClientId, RpcTargetUse.Temp) |
例外伝搬 | サーバー側 catch → TrySetException
|
生成されたメソッド名先頭の __
は「コード生成物」を示す慣習的プレフィクスで、手書きコードとの衝突を避けています。
サポート範囲と設計上の制約
項目 | 詳細 |
---|---|
戻り値型 |
bool , int , float , FixedString* , INetworkSerializable 実装構造体など UNGO が送れる型。ジェネリック T も同条件で可。 |
引数 | 同上。可変長配列 / List 等は未サポート(Netcode の伝送制限に準拠)。 |
実装場所 |
NetworkBehaviour 内の private/protected メソッドのみ。UNGO の RPC 制限に合わせ、外部 static メソッドや拡張メソッドはラップ不可。 |
並列リクエスト | 1 メソッドにつき 同一クライアントから複数同時発行可。RpcManager が requestIndex で多重対応。 |
パフォーマンスとネットワーク負荷
-
成功ケース 1 往復:
ServerRpc
→ClientRpc
のみ - 失敗ケースも 1 往復:例外情報は boolean flag で送信、スタックトレースはローカル log に残す方針
- Direct Call:Host モードでは RPC を完全スキップ。メモリ確保なし、GC0 世代圧縮なし。
-
ID 管理:
ulong
インクリメントで 2^64 – 1 コールまで衝突なし(≒枯渇不可)。
導入上の Tips
シナリオ | 推奨アプローチ |
---|---|
権限昇格のトグル(例 : Admin UI) |
await SetAdminAsync(targetPlayerId, true) で即返、NetworkVariable に二重書きしない |
ロード中のみ必要なデータ |
await GetLobbySeedAsync() で受け取ったらメモリにキャッシュし、RPC 経路は破棄 |
頻繁ポーリング | 毎フレーム呼ぶ必要がある場合は NetworkVariable か NetworkList へ移行推奨(帯域節約) |
まとめ
「RPC に戻り値がない」という UNGO の設計制約を、ソースコード 3 行で超える。
-
生産性:ボイラープレートを書かずに
await
可能。 - 安全性:型安全・例外転送・キャンセル対応。
- 効率:サーバー直呼び最適化+1 往復のみの軽量通信。
「ゲームプレイロジックを書きたいだけなのに戻り値問題でネットワーク層と格闘している」――そんな煩わしさを解決するものです。
リポジトリ:https://github.com/harayuu9/NetcodeGameObjectRpcAsync (Star 歓迎!)