UNETメモ

次の記事
[UNETのNetworkBehaviourクラスのコールバックとコンテキストフラグの罠](http://qiita.com/su10/items/f5b017e0990593cfa0ae)

# はじめに
Unityのマルチプレイヤー関連の機能（通称UNET）についての備忘録。


# ネットワークシステムの概念
[ネットワークシステムの概念](https://docs.unity3d.com/jp/540/Manual/UNetConcepts.html)（公式のマニュアル）

## クライアント
* リモートクライアント
* ローカルクライアント

の２種類があってホストとして動作してる場合はローカル、それ以外はリモート。どっちなのかはほぼ気にする必要なし。

自分に権限がある場合は`[Command]`つきメソッドの呼び出しでサーバに処理を実行させることができる。

## サーバ
オブジェクトの同期の要。

* `NetworkManager.Spawn()`で全クライアントで同期するオブジェクトの生成
* `[SyncVar]`の値を各クライアントに同期
* `[ClientRpc]`でクライアント側の処理呼び出し

クライアントに権限があるオブジェクトを操作するとクライアントに上書きされるらしい。

## ホスト
ローカルクライアントとサーバが同じ場所で動いている状態。クライアントとサーバの両方のコードが動作するので非常にデバッグしづらい。

# ネットワーク関連の属性（Attribute）
流れを理解するには公式の図が一番わかりやすい気がする。

![alt](https://docs.unity3d.com/jp/540/uploads/Main/UNetDirections.jpg)

## `[SyncVar]`
[SyncVars](https://docs.unity3d.com/jp/540/Manual/UNetStateSync.html)（公式のマニュアル）

* `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]`つきのメソッドを指定しても動かないので呼び出しを一回ラップする必要がある。

同様にクライアントで呼び出されるコールバックとして利用する時は関数をそのまま渡すとクライアントでの呼び出しとなってしまうので注意！

```csharp
[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]`と同じ。

以下のコードは[マニュアル](https://docs.unity3d.com/ScriptReference/Networking.TargetRpcAttribute.html)から一部修正して引用。

```csharp
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。

上２つは条件を満たしていてもそれぞれ

* `OnStartServer()`
* `OnStartClient()`

が呼ばれるまでは`false`になっていて（[参考リンク](https://forum.unity3d.com/threads/isserver-is-false-when-called-from-networkmanager.362642/)）、Hostの場合は両方`true`になる。

# `NetworkBehaviour`と`NetworkIdentity`
以下の制約がある。

* 両方ともSpawn可能なプレハブの<b>ルート</b>であるゲームオブジェクトにアタッチする必要がある。
* ルートオブジェクトに`NetworkIdentity`がないとNetworkManagerに登録できない。

データ同期の機能は`NetworkBehaviour`が持っているので、ルートでない`GameObject`で発火した同期が必要なイベントはルートまで通知をバブリングなりする必要がある（これで詰まった）。

それが嫌なら別でプレハブ（ルートに`NetworkBehaviour`と`NetworkIdentity`つき）を用意して、サーバ側で`NetworkServer.SpawnWithClientAuthority()`を呼び出して権限付きでスポーンさせる。

```csharp
// 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 について詳しく調べてみた](http://tips.hecomi.com/entry/2015/08/14/220030)
* [UNet Unity5.1からの新しいネットワークシステム](http://am1tanaka.hatenablog.com/entry/2015/07/28/002125)
* [マルチプレイヤーとネットワーキング](https://docs.unity3d.com/ja/540/Manual/UNet.html)（公式のマニュアル、長くて日本語が変なので読むの疲れる）
* [【Unity9】UNETでマルチプレイヤーなオンラインゲーム開発【UNET1】](http://hiyotama.hatenablog.com/entry/2015/07/06/153000)（手を動かしながら実際の使い方がわかる）
* [【Unity】Tanksデモのインターネット対戦対応版「TANKS! Networking Demo」で”遊ぶ”方法](http://tsubakit1.hateblo.jp/entry/2015/10/19/230819)（「TANKS!」はUNET使ったソース見れるので参考に）
* [UNET Unofficial Documentation (Errors, Workarounds, Best Practices)](http://qiita.com/neon-izm/items/8b98d8fbf590b31c72ef)
* []()


# 感想
前触った時はモバイルとブラウザで同じサーバで遊べなかったけどどうなったんだろうか。

次の記事
[UNETのNetworkBehaviourクラスのコールバックとコンテキストフラグの罠](http://qiita.com/su10/items/f5b017e0990593cfa0ae)