[Unity] NetworkViewを使ってネットワークでオブジェクトを同期させる


Unityにはリアルタイムに、ネットワークを介してオブジェクトの位置などを同期する仕組みが用意されています。
Hello Worldはとても簡単で、`NetworkView`コンポーネントを該当のオブジェクトにアタッチするだけです。

実際にはいくつかの分岐処理などを書かないとなりませんが、ネットワークを通してデータのやり取りをする部分などはほぼUnityが対応してくれます。これはとても便利な機能です。さすがUnity。
（ちなみに[こちらの記事](http://nn-hokuson.hatenablog.com/entry/2014/04/08/232228)を参考にさせていただきました）

## セットアップ手順

1. ネットワークを介して操作したいオブジェクトをPrefab化する
2. 該当オブジェクトに`NetworkView`コンポーネントをアタッチする
3. NetworkManager的なオブジェクトを作り、操作するためのスクリプトをアタッチする
4. `Network`クラスのメソッドを用いてセットアップする
	1. 具体的には`Network.Instanciate`、`Network.Destroy`、`Network.Connect`、`Network.InitializeServer`メソッドなどを用いる


具体的に見て行きましょう。

### Prefab化する

このあたりはネットワーク関係なく通常の手順ですね。
Prefab化することでスクリプトから動的に生成することが可能となります。
ただひとつ注意点として、通常は`Instantiate`を使ってインスタンス化しますが、`NetworkView`を介して操作するオブジェクトは`Network.Instantiate`メソッドを使ってインスタンス化します。
（基本的な引数は`Instantiate`と同様、第4引数にグループを`int`型で指定します）

```c#:Network.Instantiate
Network.Instantiate(objectPrefab, objectPrefab.transform.position, objectPrefab.transform.rotation, 1);
```


### `NetworkView`コンポーネントをアタッチする

通常通り、`Add Component`で`NetworkView`と検索すると出てくるコンポーネントをアタッチします。
基本的にはこれだけです。

![NetworkViewのアタッチ](https://qiita-image-store.s3.amazonaws.com/0/5616/c77b27b0-0ab4-60cb-0bd2-fbf2d1b12762.png "NetworkViewのアタッチ")


### NetworkManagerを作る

通常のPrefab同様、Prefab化してコンポーネントをアタッチしても画面にはなにも表示されません。
それを管理するオブジェクトがないからです。
なので、空のオブジェクトでもカメラでもなんでもいいですが、NetworkManagerの役割を担うオブジェクトに、NetworkViewをコントロールするスクリプトをアタッチします。

具体的には、Prefabをインスタンス化する処理と、サーバの起動、クライアントからの接続といった処理を実装します。

```c#:NetworkManager
using UnityEngine;
using System.Collections;

public class NetworkManager : MonoBehaviour {
	public GameObject objectPrefab;
	string ip      = "127.0.0.1";
	string port    = "1192";
	bool connected = false;
	
	private void CreatePlayer() {
		connected = true;
		Network.Instantiate(objectPrefab, objectPrefab.transform.position, objectPrefab.transform.rotation, 1);
	}
	
	public void OnConnectedToServer() {
		connected = true;
	}
	
	public void OnServerInitialized() {
		CreatePlayer();
	}
	
	public void OnGUI() {
		if (!connected) {
			if (GUI.Button(new Rect(10, 10, 90, 90), "Client")) {
				Network.Connect(ip, int.Parse(port));
			}
			if (GUI.Button(new Rect(10, 110, 90, 90), "Master")) {
				Network.InitializeServer(10, int.Parse(port), false);
			}
		}
	}
}
```


#### Network.Connectメソッド

こちらはクライアントサイドで実行するものです。`Connect`からも分かるように、サーバを起動したのちに実行することでサーバに接続します。
引数はIPアドレスとポート番号です。

```c#:Network.Connect
Network.Connect(ip, int.Parse(port));
```


#### Network.InitializedServerメソッド

こちらも見た通り、サーバサイドとして起動するためのメソッドです。
引数はコネクション数ととポート番号を指定します。第三引数にはNatを使うかのbool値を指定します。

```c#:Network.InitializeServer
Network.InitializeServer(10, int.Parse(port), false);
```



### RPC（Remote Procedure Call）

さて、以上で位置や回転などは自動的に同期されるようになります。
しかし、例えばキャラクターが移動してなにかしらのアクションを実行するためのメソッドを呼びだそうとするとします。
しかし普通に実装するとこれはうまく行きません。
ネットワーク越しでは直接関数が実行できないからです。
そこで利用されるのがRPC（Remote Procedure Call）と呼ばれる、リモートの関数呼び出しを可能にする技術です。

これはUnityは関係なく、一般的に用いられる方法です。
これをUnityでも利用してリモートでのメソッド呼び出しを可能にします。

具体的には以下のように定義、実行することで実現できます。

#### 定義

```c#:RPC-define
[RPC]
void AnyMetho(string name, int num) {
	// RPC経由で実行したいメソッド
}
```

`[RPC]`というAttributeを付けることでRPCで利用することを宣言できます。


#### 実行

```c#:RPC-perform
networkView.RPC("AnyMethod", RPCMode.All, "name", 10);
```

`networkView`の`RPC`メソッドを、上記のようにメソッド名やモード、RPCで呼び出したいメソッドの引数を指定して実行することでRPCに対応したメソッドを実行することができます。

※ ここでの`networkView`はGameObjectにアタッチされているNetworkViewコンポーネントです。NetworkViewコンポーネントがアタッチされていると`GetComponent`を実行しなくても参照可能です。