Photon使ってUnityでカーゲームが作りたい

Unityでオンラインゲーム作れるようになりたい！！！
UnityではPhoton Realtime SDKを使えば無料でできると聞いたので、制作過程を記します。使用したUnityのバージョンは2018.3.6です。

#1. photonアカウントとアプリの準備
まずは[photon](https://www.photonengine.com/ja/photon)でアカウントを作成します。
新しいアプリを作成するを選んでください。
<img width="1468" alt="スクリーンショット 2019-06-15 18.50.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/8306442e-c929-4907-a896-491a72d5da83.png">

こんな感じで今回作るゲームの情報を入れておきます。今回使うのはPhoton Realtimeです。
<img width="1480" alt="スクリーンショット 2019-06-15 18.55.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/6bba9024-ac0c-5e05-bda0-ec1d872b226e.png">

作成するを押して、以下の画面になります。詳細を確認しましょう
<img width="1466" alt="スクリーンショット 2019-06-15 18.55.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/890b5707-9cde-bdbc-7358-864cf37f17c2.png">

このアプリケーションIDはあとで使うので、コピーしておきましょう！
<img width="1435" alt="スクリーンショット 2019-06-15 18.58.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/10149b48-773c-7e92-af9f-ee1a0ecd96ba.png">

#2. Unity側の準備
Unityでオンライン化したいプロジェクトに対し、AssetStoreから**Photon Unity Networking Classic-Free**をImportします。Photonは似たようなAseetが沢山あるので注意してください。
<img width="1248" alt="スクリーンショット 2019-06-22 18.19.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/eed61243-1c7c-c636-45e6-e9dc5a4b49d9.png">

Unityのプロジェクトをphotonと繋げるところからやっていきましょう。

Importするとすぐ、AppIDを打ち込む画面が現れます。先ほどコピーしたアプリケーションIDをペーストしてください。
<img width="412" alt="スクリーンショット 2019-06-15 18.59.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/68273d5d-f0ce-1c08-4ead-28a90cc676f9.png">

入れ損ねた場合や間違えた場合はWindow > Photon Unity NetWorking > HighLight Server Setting から、PhotonServerSettingのInspectorから編集できます。
<img width="1680" alt="スクリーンショット 2019-06-20 12.24.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/c8fae7ec-04e3-1abb-aba9-8ec366201f4f.png">
さっきPhotonのマイページで作ったアプリケーションIDをAppidに入力してください。下の画像の青いところです。日本なのでRegionをJpにしておきます。Auto-join Lobbyにチェックをつけて、ゲーム開始時に自動でロビーに入るようにします。
<img width="516" alt="スクリーンショット 2019-06-15 19.00.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/a244b116-ec35-ce04-cacb-63ce736bcb4b.png">

Photonに接続するスクリプトを書きます。
Hierarchy Viewに空のオブジェクト(PhotonManager)を作って、下のスクリプト(PhotonManager.cs)をアタッチしてください。

```c#:PhotonManager.cs
using UnityEngine;
using System.Collections;
 
public class PhotonManager : MonoBehaviour {
 
    void Start() {
        // Photonに接続する(引数でゲームのバージョンを指定できる)
        PhotonNetwork.ConnectUsingSettings(null);
    }
 
    // ロビーに入ると自動的に呼ばれる
    void OnJoinedLobby() {
        Debug.Log("ロビーに入りました");
 
        // ルームに入室する
        PhotonNetwork.JoinRandomRoom();
    }
 
    // ルームに入室すると自動的に呼ばれる
    void OnJoinedRoom() {
        Debug.Log("ルームへ入室しました");
    }
 
    // ルームの入室に失敗すると自動的に呼ばれる
    void OnPhotonRandomJoinFailed() {
        Debug.Log("ルームの入室に失敗しました");
 
        // ルームがないと入室に失敗するため、その時は自分で作る
        // 引数でルーム名を指定できる
        PhotonNetwork.CreateRoom("myRoomName");
    }
}
```
実行してConsoleを確認してみましょう。こんな感じになっていたら成功です！
<img width="397" alt="スクリーンショット 2019-06-22 17.58.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/0cc924ce-8aab-eccd-0c34-e7c12c2a19b0.png">

ちなみにphotonに繋げないWi-fiもあるみたいです。ポート番号が解放されていないとそうなるみたいです。Wi-fiを変えたらうまくいくということがありました。

# 3. プレイヤーを生成する
今のままでは1人プレイしかできないので、各プレイヤーが操作できる車を生成できるようにします。とりあえず2人プレイできるようにしましょう。オブジェクトの位置や角度を同期させるには、**PhotonNetwork**を用います。

### 接続中の人数
> int PhotonNetwork.countOfPlayers

現在このアプリケーションをプレイしているユーザー数（マスターサーバーで5秒間隔で取得可能）を取得できます。(by [Photon Network Class Reference](https://doc-api.photonengine.com/ja-jp/pun/current/class_photon_network.html))

### ネットワーク越しのPrefabの生成
> static GameObject PhotonNetwork.Instantiate(string prefabName, Vector3 position, Quaternion 	rotation, int group)

ネットワーク越しにプレハブからインスタンス作成します。このプレハブは**"Resources"フォルダー直下**に配置されている必要があります。Resourcesフォルダーのプレハブを使う代わりに、手動でInstantiateしてPhotonViewを割り当てることもできます。(by [Photon Network Class Reference](https://doc-api.photonengine.com/ja-jp/pun/current/class_photon_network.html))


PhotonManagerのOnJoinedRoomの中を書き加えていきます。

```c#:PhotonManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PhotonManager : MonoBehaviour
{
    void Start()
    {
        // Photonに接続する(引数でゲームのバージョンを指定できる)
        PhotonNetwork.ConnectUsingSettings(null);
    }

    // ロビーに入ると呼ばれる
    void OnJoinedLobby()
    {
        Debug.Log("ロビーに入りました。");

        // ルームに入室する
        PhotonNetwork.JoinRandomRoom();
    }

    // ルームに入室すると呼ばれる
    void OnJoinedRoom()
    {
        Debug.Log("ルームへ"+ PhotonNetwork.countOfPlayers +"人入室しました。");
        //CarPrefabを生成する
        transform.rotation = Quaternion.Euler(0, 180, 0); //Carの向きが逆になってになってしまう問題を解決
        
        if (PhotonNetwork.countOfPlayers < 2)
        { 
            transform.position = new Vector3(66, 0, 0);
            GameObject car1 = PhotonNetwork.Instantiate("CarPrefab", transform.position, transform.rotation, 0);
            car1.name = "car1";
        }
        if (PhotonNetwork.countOfPlayers == 2)
        {
            transform.position = new Vector3(60, 0, 0);
            Debug.Log("2台目生成できたよ");
            GameObject car2 = PhotonNetwork.Instantiate("CarPrefab", transform.position, transform.rotation, 0);
        }

    }

    // ルームの入室に失敗すると呼ばれる
    void OnPhotonRandomJoinFailed()
    {
        Debug.Log("ルームの入室に失敗しました。");

        // ルームがないと入室に失敗するため、その時は自分で作る
        // 引数でルーム名を指定できる
        PhotonNetwork.CreateRoom("myRoomName");
    }

}
```



### Resourcesフォルダ
PhotonはResourcesというフォルダからPrefabを生成するというシステムらしいです。

Carの名前をCarPrefab、
Prefabsフォルダの名前をResourcesに変えるか、新たにResourcesというフォルダを作るかしましょう。

### Photon Viewコンポーネント
同期するオブジェクトにつけるコンポーネントです。
CarPrefabのInspectorを開き、**Photon View**と**Photon Transform View**をつけます。PositionとRotationにチェックを入れましょう。Photon Transform ViewをPhoton ViewのObserved Componentsにドラッグ＆ドロップします。

このようにクライアント間で通信を行いたい際にオブジェクトにPhotonViewコンポーネントを追加し、Obaserved(監視対象)を指定すると、そのオブジェクトが同期されるようになります。
CarPrefabのInspevtorはこんなかんじになります。
<img width="273" alt="スクリーンショット 2019-06-22 21.33.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/a90241c1-c191-3a83-da7d-acfe077b3bd0.png">


逆に、**同期したくないオブジェクトはPhotonNetwork越しに生成すると厄介なことになります。**
カメラとか、、、、、、、、、むやみに同期させない方が良いということを学びました。ネットワーク負荷の軽減のためにも、同期するオブジェクトは必要最低限にすることを心がけましょう。

CarPrefabの子オブジェクトのMainCameraを消してください。カメラはあとでプレイヤー別になるようにしますが、今ついていると2人以上になったとき同期しておかしなことになります。適当な位置にに固定カメラを設置しておきましょう。
CarPrefabの準備が済んだら、CarPrefabをResourcesにいれてPrefab化してください。Hierarchyに残ったCarPrefabは消しちゃって大丈夫です。

<img width="1792" alt="スクリーンショット 2019-06-22 21.33.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/448fd5e1-28a0-9f4b-39d4-f1c9e242ce48.png">

実行して、Scene Viewにcar1が生成されたのを確認できたら成功です！
ちゃんと2台生成したCarが同期しているか、ビルドして確認しましょう。

## ☆. 同期の確認方法
Photonは沢山Buildします。Buildしないと1つのパソコンで2人分の動作確認ができないからです。
手始めに今どんな挙動を見せるのか確認してみます。PC向けAppをBuildしてください。
動作確認をする際は、まずAppを起動し、次にUnityの再生ボタンを押すようにしてください。
<img width="594" alt="スクリーンショット 2019-06-22 21.52.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/64656444-a132-c2a0-6afb-724346e52242.png">
Windowedにチェックを入れておくと楽です。チェックを入れないと全画面で起動します。
<img width="1675" alt="スクリーンショット 2019-06-22 21.54.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/250946/b8783b20-eada-90c5-162a-334b98b79854.png">

2台出たら成功です。
しかしこのままでは片方が移動するともう片方も移動してしまっています。次の章で直していきましょう。

# 4. プレイヤーをユーザーが各自で操作できるようにする
> bool PhotonView.isMine

PhotonViewの所有者が「自分」で、このクライアントからコントロール可能の場合trueです。
PUNは所有権という概念があります。それぞれのPhotonViewをコントロールしたり破棄できる主体のことです。 所有者がローカルPhotonPlayerの場合trueです。 シーンのPhotonViewで、Master client上で動作しているならtrueです。(by [Photon Network Class Reference](https://doc-api.photonengine.com/ja-jp/pun/current/class_photon_network.html))

プレイヤーを操作するスクリプトの部分に、isMineの条件を足します。プレイヤーを操作しているCarUserControl.csを書き換えていきましょう。

```c#:CarUserControl.cs
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityStandardAssets.Vehicles.Car;

public class CarUserControl : MonoBehaviour
{
    private PhotonView myPhotonView;
    private CarController m_Car;
    float h; //horizontal
    float v; //Vertical

    private void Start()
    {
        this.myPhotonView = GetComponent<PhotonView>();
        m_Car = GetComponent<CarController>();
    }
    private void FixedUpdate()
    {
        
        if (myPhotonView.isMine) //自分のPhotonViewだったら
        {
            if (RaceManager.instance.isRacing == true)
            {
                h = CrossPlatformInputManager.GetAxis("Horizontal");
                v = CrossPlatformInputManager.GetAxis("Vertical");
            }
            
            m_Car.Move(h, v, v, 0);
        }
    }
}
```

Builidして、それぞれのCarを動かすことができていれば成功です！


# 5. 各プレイヤーを追従するカメラを作る

### 失敗例
その①
PhotonViewのついたCameraを子オブジェクトに持つCarPrefabをPhotonNetworkから生成した場合
→ カメラが同期してちゃんと自分の車につかない

その②
PhotonViewのないCameraを子オブジェクトに持つCarPrefabをPhotonNetworkから生成した場合
→ 操作とカメラが逆転する謎現象が起きた

その③
PhotonViewのついたCameraをPhotonNetworkから生成し、CarPrefabを追従するようにした場合
→ これもカメラが同期して自分の車につかない

全部Buildしないと気づかない失敗です。同期したくないオブジェクトはPhotonNetwork越しに生成すると厄介なことになります。ネットワーク負荷の軽減のためにも、同期するオブジェクトは必要最低限にすることを心がけましょう。

### 成功例
PhotonViewのついてないCameraを**PhotonNetworkを介さずに**生成した場合

まずはCameraをCameraPrefabに改名し、CameraScriptをつけてください。CarUserControl.csとCameraScript.csに書き加えていきます。
CarUserControlのAwakeでCameraをCarの子オブジェクトとして**PhotonNetworkを介さずに**生成します。CameraPrefabをResourcesに入れてください。

```c#:CarUserControl.cs
using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
using UnityStandardAssets.Vehicles.Car;

public class CarUserControl : Photon.PunBehaviour
{
    private PhotonView myPhotonView;
    private CarController m_Car;
    float h; //horizontal
    float v; //Vertical

    private void Awake()
    {
        this.myPhotonView = GetComponent<PhotonView>();
        m_Car = GetComponent<CarController>();

        if (myPhotonView.isMine) //Carが自分の車である場合
        {
            GameObject camPrefab = (GameObject)Resources.Load("CameraPrefab"); //ResourcesフォルダからCameraPrefabをとってくる
            Vector3 CameraPosition = new Vector3(0, 3, 5); //カメラの相対座標
            GameObject cam = Instantiate(camPrefab, transform.position + CameraPosition, transform.rotation); //CameraPrefabを生成する
            cam.transform.parent = gameObject.transform; //Cameraの親オブジェクトはCarとする
        }
    }
    private void FixedUpdate()
    {
        
        if (myPhotonView.isMine)
        {
            if (RaceManager.instance.isRacing == true)
            {
                h = CrossPlatformInputManager.GetAxis("Horizontal");
                v = CrossPlatformInputManager.GetAxis("Vertical");
            }
            
            m_Car.Move(h, v, v, 0f);
        }
    }
}
```


再生してカメラがついてくることを確認したら、Buildして動作確認しましょう。
[完成品の動画](https://youtu.be/hpx0KVG7_zE)

順位つけたりもしたいですね。

お疲れ様でした！

#参考資料
[基本説明 - Photon Unity Networking](https://doc-api.photonengine.com/ja-jp/pun/current/general.html)
[UnityでPUNを使ったオンラインマルチプレイの実装-準備編-](https://tech.pjin.jp/blog/2016/07/20/unity_pun_1/)
[Unity猫本のサンプルゲームをPhotonでオンライン対戦ゲーム化してみた](https://qiita.com/iriya0624/items/898e78158ded1fa09c5e)
[Unityカーレース](https://game.handmade.jp/2018/02/14/unitycarrace/)