【Unity】 ネットワーク

# 初めに
  ・この記事は書籍「Unity5 オンラインゲーム開発講座」をまとめていく。

# オンラインゲームとは
## 通信の分類
#### リアルタイム同期
・キャラクターの移動や協力プレイなどリアルタイムな同期が必要
#### 非リアルタイム同期
・ランキングの結果や他ユーザのユニットをお供に連れていく場合など、特定のタイミングのみ通信を必要とするもの。

#### ターンベース制
・オセロや将棋とか、シャドウバースとか
#### 非ターンベース制
・RPGやアクションなど

#### MO
・数人～数十人が一つの空間に集まりゲームが展開される。
・マリカ
#### MMO (Massively Multiplayer Online)
・数百人～数千人が一つの空間に集まりゲームが展開される。
・FF14

## メリット・デメリット
#### メリット
・他ユーザとの交流（チャットや協力プレイなど）の楽しみが生まれる。
・他ユーザとの対戦：CPUでは得られない勝利の喜び
・シングルプレイモードを手抜きすることもできる。(CPUのAIやストーリーの作成など)

#### デメリット
・プレイヤ数が必要
・ユーザ対応が必要
・テストの工数の増加、テストパターンの複雑化。
・サーバ維持コストが必要

## 課金の種類
・有料販売　：　ネームバリューがないと難しい
・アプリ内課金　：　一番現実的
・広告収入
・リワード広告


# 開発環境
## 環境一覧
・Unity 2019.2.9f1
・Photon RealTime
・Photon Unity Network (Asset)

## Photon Realtime
### 概要
・Exit Games社が開発したネットワークエンジンと呼ばれるシステム
・Android, iOS, ブラウザなどのプラットフォーム向け。
・リアルタイム、マルチプレイヤー、マッチメイキングに対応している。
・クラウドシステム(SaaS)
・デフォルトの同時ユーザ接続数(CCU)は 20
・ロードバランシングAPIを利用するとプラットフォームを問わず通信できる。
### 環境準備
#### ① アカウントを登録する。
・リンク : https://www-jp.exitgames.com/ja/Realtime
![キャプチャ222.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/822745ed-3779-324c-d51c-a0358e36db16.png)
・無料プランはこちらのボタンを押下。
・メールを入力
・受信したメールからアカウントを登録する。
#### ② Photon Cloud アプリケーションを作成する。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/cefdcd24-039f-e79a-1a54-212fd28709b7.png)
・新しくアプリを作成する。を押下する。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/14b9cb2d-9672-0af0-c2de-916974702621.png)
・Photonの種別を「PUN」として作成するを押下する。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/cc26a24a-bfb3-c87c-224a-eff14130d97e.png)
・作成したものがアプリケーション一覧に表示されていればOK

#### ③ SDKをダウンロード
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/663678b8-f592-b2d8-2ab0-dab4e67f977a.png)
・UnityのAsset Storeから「Photon Unity Networking Free」をインポートする。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/f0e6db00-b480-97cd-4b45-4bc76cb1a6d6.png)
・アプリケーションIDの入力が求められるので、②で作成したもののIDをコピペする。

・Closeを押下する。

#### ④サーバ設定
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/c83a1bf2-6076-b8f7-0e02-7e26f59162e4.png)
・プロジェクトビューからPhoton Unity Networking -> Resources -> PhotonServerSettingsを選択しインスペクタからサーバの設定ができる。

|項目 | 説明|
|:--|:--|
| AppId  | アプリケーションID  |
| Regioin  | どのエリアのサーバを使用するか。  |

# Photonを使ったネット接続
## 全体の概要
・PUN(Photon Unity Network, Unity側で利用できるSDK)を用いて、クラウドのサーバへアクセスする。
・NameServer -> GameServer(Lobby) -> MasterServer(Room)の順に接続する。
・部屋の作成や入退出などの処理に対応したコールバック関数(On~~~)が用意されているため、それをオーバーライドする。
　（いちいち、〇〇のフラグが経つまでupdate()で監視するといった面倒な処理がないので便利）
・下図はあくまでイメージ（シーケンス図の記法は守られてないです。）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505426/6c032847-2a61-d9c9-9645-5b6f03caa1b3.png" width=50% />

### サーバの種類
・PUNは3種類のサーバを使用する。
#### NameServer
・指定リージョンのマスターサーバのIPアドレスを取得する。
・リージョンはコンフィグで設定したリージョンのこと。
#### MasterServer
・ロビーの役割を担うサーバ
・マッチメイキングに対応。(どのユーザ同士をマッチさせるかなど）
・スクリプトからは、ルームの一覧情報を取得できる。(ロビーにいる間有効）
#### GameServer
・ルームの役割を担うサーバ
・ルーム内のプレイヤにメッセージを伝達する。
・ルーム内のプレイヤ情報を取得する。（ルームにいる間有効）

### クライアントの種類
・クライアントの種類には「一般クライアント」と「マスタークライアント」がある。
#### マスタークライアントの条件
・そのルームに一番に入室しているプレイヤーがマスターに割り当てられる。
・マスタークライアントが退出すると、ルーム内の他のプレイヤー一人がマスタークライアントに切り替わる。
#### マスタークライアントの役割
・プレイヤーの強制退出
・自身のマスター権限の譲渡 (退出した場合は自動でこれが行われている？）
・スクリプト上でマスターかを判定して、分岐処理
　（例.ゲームのリザルトを他プレイヤに送信する。)

## コールバック関数
| メソッド | 説明 | トリガー | 備考 |
|:--|:--|:--|:--|
|OnConnectedToMaster   |  ロビーへ入室した | ConnectUsingSettings|コンフィグのautoJoinLobbyが=false  | |
|OnJoinedLobby|  ロビーへ入室した | ConnectUsingSettings|コンフィグのautoJoinLobbyが=true| |
|OnJoinedRoom   | ルームの入室に成功  | JoinRandomRoom  | |
|OnPhotonPlayerConnected | 自分以外がルームに入室  | JoinRandomRoom  | |
|OnPhotonRandomJoinFailed| ルームの入室に失敗  | JoinRandomRoom  | |
|OnCreatedRoom| ルームの作成に成功  | CreateRoom| |
|OnPhotonRandomCreateFailed| ルームの作成に失敗  | CreateRoom| |
|OnLeftRoom| ルームを退出した  | LeaveRoom| |
|OnApplicationPause(bool)| スリープ/スリープ解除した|スマホのスリープ|Unity起動時かサーバ接続時かにスリープ解除(false)扱いでコールバック関数が呼ばれることに注意|

## ステータス変数
PhotonNetworkクラスには便利なstaticフィールドがいっぱい。

| 変数名 | 説明 |
|:--|:--|
|connected   |  いずれかのサーバに接続できているかどうか。 |
|insideLobby　| ロビーにいるかどうか。  |
|inRoom   |入室中であるかどうか。 (ClientState.Joinedと同じ。)   |
|connectionStateDetailed   |PhotonNetwork.ClientStateのenum型。接続開始から、どのサーバに接続中か、ルームに入室した、退出したなど細かいステータス|
|isMasterClient   |マスタークライアントかどうか   |
|room   |入室中の部屋情報。入室人数など  |
|player, playerList, otherPlayers   |入室中のプレイヤの情報。プレイヤ名など   |

## データ連係
プレイヤー間でデータの連係をする方法には2種類ある。
### RPC (Remote Procedure Call)

```csharp:定義
    [PunRPC]
    public void SetGameState(int gameState) {
        this.gameState = (GameState)gameState;
    }
```
``` csharp:呼び出し
　　PhotonView　myView = GetComponent<PhotonView>();
　　myView.RPC("SetGameState", PhotonTargets.All, new object[] { (int)GameState.Judge });
```
#### 手順
・データの通信を行う「PhotonView」コンポーネントをアタッチする。
#### 説明
・メソッド呼び出し時のみ通信を行うため、負荷が小さい。          
・第1引数にはメソッド名、第2引数には伝搬対象、第3引数はメソッドの引数
・伝搬対象には以下がある。
　- All : 自分を含む全員
　- Others : 自分以外の全員
　- MasterClient
・第3引数はシリアライズ可能でないといけないっぽい。(そのままGameState(enum型)で渡すとパラメタ不整合のような内容のエラーとなる。)

### OnPhotonSerializedView
```csharp
public class ClickCounter : Photon.MonoBehaviour, IPointerClickHandler
{
    private int counter1;
    private int counter2;
    void Start() {
        counter1 = 0;
        counter2 = 100;
    }

    public void OnPointerClick(PointerEventData eventData) {
        counter1++;
        counter2--;
        t.text = counter1.ToString() + "/" + counter2;
    }

    // OnPhotonSerializeViewによりデータの送受信を行う。
    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
        // データを送信する。
        if (stream.isWriting)
        {
            // 送りたい分だけ記載する。
            stream.SendNext(counter1.ToString());
            stream.SendNext(counter2.ToString());
        }
        // データを受信する。
        else
        {
            counter1 = int.Parse(stream.ReceiveNext().ToString());
            counter2 = int.Parse(stream.ReceiveNext().ToString());
            t.text = counter1.ToString() + "/" + counter2.ToString();
        }
    }
}
```
#### 手順
・データの通信を行う「PhotonView」コンポーネントをアタッチする。
・PhotonViewのObserved Componentsにデータ連係したいコンポーネントを設定する。
#### 説明
・常時通信を行うため、負荷が大きい。
・マスタークライアントの場合、一定時間ごとにメソッドが呼ばれ、データ送信のみ行う。
・一般クライアントの場合、マスター側で値の変更があった場合のみメソッドが呼ばれ、データ受信のみ行う。
　逆に一般クライアントの変更をトリガーにデータ送信は行われなかった。
・伝搬対象は「PhotonTargets.Others」

### インスタンス連係
```
　PhotonNetwork.Instantiate(prefub.name, new Vector3(9.0f, 0f, 0f), Quaternion.identity, 0);
```
・プレハブから生成したインスタンスが他のクライアントにも生成される。
・プレハブは「Resources」配下に置く必要がある。

### カスタムプロパティ
・各クライアントまたはルームに対して保存できるハッシュテーブル。
・データはサーバに保存される。
・頻繁にRPCなどによるデータ伝搬を行わなくても済む。
・プレイヤの戦績情報など、変化しえない情報の格納に使えそう。

``` csharp
  // データの初期化
  ExitGames.Client.Photon.Hashtable playerHash;  
  playerHash = new ExitGames.Client.Photon.Hashtable();
  // データの追加
  playerHash.Add("test1", 30);
  // データの保存
  PhotonNetwork.player.SetCustomProperties(playerHash);
  // データの参照
  int value = (int) PhotonNetwork.player.CustomProperties["test1"];

  // ルームについては、PhotonNetwork.playerがPhotonNetwork.roomに置き換わるだけ。
```

## トラブルシューティング
PUNに関するエラーとその対処方法を以下にまとめる。
### AppId未設定
> The appId this client sent is unknown on the server (Cloud). Check settings. If using the Cloud, check account.

原因 : 正しいAppIdが設定されていない。
対処 : マイページのアプリケーションから正しいAppIdをコピペする。

### プレハブの作成タイミング
> Failed to Instantiate prefab: Cube. Client should be in a room. Current connectionStateDetailed: JoinedLobby　UnityEngine.Debug:LogError(Object)
> NullReferenceException: Object reference not set to an instance of an object

原因 : 共通するプレハブは"ルーム内"でしか使用できない。

### ルーム接続時エラー
> Debug.LogError("JoinRandomRoom failed. Client is not on Master Server or not yet ready to call operations. Wait for callback: OnJoinedLobby or OnConnectedToMaster.");
 
原因／対処 : ロビー接続前にルーム接続を行ってはいけない。(動作的にはエラーを吐くものの正常にルームには入れてしまう。)

# 残分
〇true : OnJoinedLobbyが呼ばれる, false OnConnectedToMasterが呼ばれる。
双方の違いについては、そもそもMasterが何かを学ぶ必要がある。
〇カスタムプロパティなRoomの作成とランク別マッチ
〇根本、PUNを使用しないUnityで利用できるネットワークの機能を理解していない。