次の記事
UNETのNetworkBehaviourクラスのコールバックとコンテキストフラグの罠
はじめに
Unityのマルチプレイヤー関連の機能(通称UNET)についての備忘録。
ネットワークシステムの概念
ネットワークシステムの概念(公式のマニュアル)
クライアント
- リモートクライアント
- ローカルクライアント
の2種類があってホストとして動作してる場合はローカル、それ以外はリモート。どっちなのかはほぼ気にする必要なし。
自分に権限がある場合は[Command]
つきメソッドの呼び出しでサーバに処理を実行させることができる。
サーバ
オブジェクトの同期の要。
-
NetworkManager.Spawn()
で全クライアントで同期するオブジェクトの生成 -
[SyncVar]
の値を各クライアントに同期 -
[ClientRpc]
でクライアント側の処理呼び出し
クライアントに権限があるオブジェクトを操作するとクライアントに上書きされるらしい。
ホスト
ローカルクライアントとサーバが同じ場所で動いている状態。クライアントとサーバの両方のコードが動作するので非常にデバッグしづらい。
ネットワーク関連の属性(Attribute)
流れを理解するには公式の図が一番わかりやすい気がする。
[SyncVar]
SyncVars(公式のマニュアル)
-
NetworkBehaviour
コンポーネントのメンバー変数につける - サーバーから各クライアントへ値が同期される
- (逆をやりたい場合は[Command]を使ってクライアントからサーバに送る)
-
int
,string
,float
,Vector3
構造体などで使用可 - セッター内で変更しようとすると無限ループになるから同期が機能しないようになるっぽい
- リストバージョンの
SyncList
もある。こちらは[SyncVar]
不要。- SyncListString
- SyncListFloat
- SyncListInt
- SyncListUInt
- SyncListBool
[Client]
[Server]
メソッドにつけるとそれぞれの環境でしか動作しなくなる。具体的には[Client]
が付与されたメソッドはサーバ上では空実装になる(呼び出し時にWarningが表示される)。
[ClientCallback]
[ServerCallback]
基本的に[Client]
[Server]
と同じだがWarningを発生しない。Start()
とかUpdate()
とかUnityに自動で呼ばれるメソッドにつける。
[Command]
クライアント側で呼び出し、サーバ側で実行したいメソッドにつける。メソッド名にはCmd
のプレフィックスが必要。
以下の場合に使う。
- クライアントからサーバへ値の送信
- プレイヤーの操作をサーバへ送信
- etc..
自分に権限がない場合は呼び出してもWarningを吐くだけ。staticメソッドにはつけられない(コンパイル時にエディタがエラーを吐く)。
uGUIのボタンのOnClick
コールバックに直接[Command]
つきのメソッドを指定しても動かないので呼び出しを一回ラップする必要がある。
同様にクライアントで呼び出されるコールバックとして利用する時は関数をそのまま渡すとクライアントでの呼び出しとなってしまうので注意!
[ClientRpc]
public void RpcEnableControl()
{
if (this.isLocalPlayer == false)
{
return;
}
ChatManager.OnSubmitAsObservable()
// .Subscribe(CmdSpeak) <- 「Rpcがクライアントから呼び出された」と怒られる
.Subscribe(body => CmdSpeak(body)) // 引数つきで呼び出す
.AddTo(this);
}
[Command]
private void CmdSpeak(string body)
{
ChatManager.Instance.RpcSpeak(this.name, body);
}
[ClientRpc]
サーバ側で呼び出し、クライアント側で実行したいメソッドにつける。メソッド名にはRpc
のプレフィックスが必要。強い権限で実行されるのでセキュリティとか関係なし。全てのクライアントで実行される。
制限時間の経過などサーバからクライアントへの通知に使う。
こちらもstaticメソッドにはつけられない(コンパイル時にエディタがエラーを吐く)。
[TargetRpc]
5.4からのAPIらしい。特定のクライントのみで実行される以外は[ClientRpc]
と同じ。
以下のコードはマニュアルから一部修正して引用。
using UnityEngine;
using UnityEngine.Networking;
public class Example : NetworkBehaviour
{
[TargetRpc]
public void TargetDoMagic(NetworkConnection target, int extra)
{
Debug.Log("Magic = " + (123 + extra));
}
[Command]
void CmdTest()
{
TargetDoMagic(connectionToClient, 55);
}
}
権限
NetworkIdentifier
で誰に権限があるか指定、NetworkManager.Spawn()
でスポーン(全クライアント上でInstantiate()
され、以後同期)。自分に権限がないNetworkBehaviour
は操作できない。
プレイヤーの場合は権限つきというだけでなく特別な意味を持ち、NetworkManager.Spawn()
ではなくNetworkManager.AddPlayerForConnection()
でスポーンさせる。
ネットワークコンテキストプロパティー
NetworkBehaviour
クラスにあるネットワークコンテキストを判断するためのプロパティー。Inspectorにあるオブジェクトのプレビューウィンドウで値を確認できる。
-
isServer
- オブジェクトがサーバー上で実行されていて、かつ、サーバー上で生成された場合は true。 -
isClient
- クライアント上で実行されるオブジェクトの場合は true。 -
hasAuthority
- ①クライアント && ②自分に操作権限があるオブジェクト なら true。 -
isLocalPlayer
- ①hasAuthority
&& ③プレイヤーオブジェクト なら true。
上2つは条件を満たしていてもそれぞれ
OnStartServer()
OnStartClient()
が呼ばれるまではfalse
になっていて(参考リンク)、Hostの場合は両方true
になる。
NetworkBehaviour
とNetworkIdentity
以下の制約がある。
- 両方ともSpawn可能なプレハブのルートであるゲームオブジェクトにアタッチする必要がある。
- ルートオブジェクトに
NetworkIdentity
がないとNetworkManagerに登録できない。
データ同期の機能はNetworkBehaviour
が持っているので、ルートでないGameObject
で発火した同期が必要なイベントはルートまで通知をバブリングなりする必要がある(これで詰まった)。
それが嫌なら別でプレハブ(ルートにNetworkBehaviour
とNetworkIdentity
つき)を用意して、サーバ側でNetworkServer.SpawnWithClientAuthority()
を呼び出して権限付きでスポーンさせる。
// NOTE: NetworkBehaviourを継承しているクラスにおける実装を想定
[Command]
void CmdSpawn()
{
var go = (GameObject)Instantiate(
otherPrefab,
transform.position + new Vector3(0,1,0),
Quaternion.identity);
NetworkServer.SpawnWithClientAuthority(go, connectionToClient);
}
NetworkManager
HLAPIをより簡単に使えるようにいろいろ提供している一つの実装にすぎない。使い方がわかったらこのクラスを継承して自前のクラスを作るのが良さそう。
適当にまとめたのに長い
一旦ここまで。
参考資料
- Unity 5.1 から導入された新しいネットワーク機能の UNET について詳しく調べてみた
- UNet Unity5.1からの新しいネットワークシステム
- マルチプレイヤーとネットワーキング(公式のマニュアル、長くて日本語が変なので読むの疲れる)
- 【Unity9】UNETでマルチプレイヤーなオンラインゲーム開発【UNET1】(手を動かしながら実際の使い方がわかる)
- 【Unity】Tanksデモのインターネット対戦対応版「TANKS! Networking Demo」で”遊ぶ”方法(「TANKS!」はUNET使ったソース見れるので参考に)
- UNET Unofficial Documentation (Errors, Workarounds, Best Practices)
感想
前触った時はモバイルとブラウザで同じサーバで遊べなかったけどどうなったんだろうか。