Photon Unity Networking 2でオブジェクトのオーナー権限を別ユーザが奪う方法

# [Photon Unity Networking 2(PUN2)](https://www.photonengine.com/ja/PUN) とは
Unity でマルチプレイが簡単にできるようになるアセット。
最近バージョン2.6が出て、以前の PUN とはだいぶ勝手が違うものになっていたので、勉強がてら記事に残しておきます。

# 動作環境
- __Unity2017.4.19f1__
- __[PUN 2 - FREE](https://assetstore.unity.com/packages/tools/network/pun-2-free-119922)__

それぞれダウンロードしてインポートしておいてください。
また PUN2 のセットアップも完了させておいてください。

# シーンのセットアップ
ゲームオブジェクトのオーナーが誰にあるのか一目でわかるシーンを作りましょう。
今回は赤い Plane の上に白い Cube を1個だけ置き、これを操作できるか否かでオーナー権限の確認とします。
また MainCamera はこれらの直上に配置して Cube が動いているか確認しやすくします。
シーン完成予想図はこんな感じです。
![スクリーンショット (283).png](https://qiita-image-store.s3.amazonaws.com/0/213883/218325cb-bfdc-9c36-b247-d719990494fa.png)

Game ウィンドウには Cube の現在のオーナーID、前回のオーナーID、自分のID、そしてオーナー権限を取得するボタンを配置します。
Game ウィンドウ完成予想図はこんな感じです。
![スクリーンショット (284).png](https://qiita-image-store.s3.amazonaws.com/0/213883/a9de479f-9b41-2a06-3050-255fd4593394.png)

# Cube を操作できるようにする
簡単なスクリプトを Cube につけて移動できるようにします。
今回は Cube に Rigidbody をアタッチして、その Velocity を変えることで移動しているように見せます。

```KeyToMove.cs
public class KeyToMove : MonoBehaviour
{
    [SerializeField] private Rigidbody _rigidbody;
    [SerializeField] private int _speed = 100;

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKey(KeyCode.RightArrow))
        {
            _rigidbody.velocity = Vector3.right * _speed * Time.deltaTime;
        }
        else if (Input.GetKey(KeyCode.LeftArrow))
        {
            _rigidbody.velocity = Vector3.left * _speed * Time.deltaTime;
        }
        else if (Input.GetKey(KeyCode.UpArrow))
        {
            _rigidbody.velocity = Vector3.forward * _speed * Time.deltaTime;
        }
        else if (Input.GetKey(KeyCode.DownArrow))
        {
            _rigidbody.velocity = Vector3.back * _speed * Time.deltaTime;
        }
    }
}
```

このスクリプトを Cube にアタッチすれば、矢印キーで操作できるようになります。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">キューブがコロコロ <a href="https://t.co/1eMayiWgM1">pic.twitter.com/1eMayiWgM1</a></p>&mdash; karukaru👑@白魔導技士 (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/1090925067075121154?ref_src=twsrc%5Etfw">2019年1月31日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# PUN2 に接続して同期する
PUN2 のネットワークに接続して Cube の位置や回転、速度などを同期しましょう。
まず Cube に PhotonView 、 PhotonTransformView 、 PhotonRigidbodyView をアタッチします。
そして PhotonView の ObservedComponents に PhotonTransformView と PhotonRigidbodyView を追加します。

次に Cube のオーナー権限を要求されたときにどのような挙動をとるか指定します。
これは PhotonView の Owner にあるプルダウンから選択できます。
今回の場合は、要求を受けたら即オーナー権限を渡して通知を受け取りたいので、Takeover を指定します。

現在 Cube にアタッチされているスクリプトはこんな感じになっているかと思います。
![スクリーンショット (287).png](https://qiita-image-store.s3.amazonaws.com/0/213883/20e8ca9e-8dda-f2cb-a57a-2b4231894ace.png)

これで Cube の同期設定は完了しましたが、肝心のPUN2に接続するスクリプトをまだ書いていません。
[PUN2 の公式ドキュメント](https://doc.photonengine.com/ja-jp/pun/v2/getting-started/pun-intro)などを見ながらさらっと書いてしまいましょう。
ちなみにですが、[PUN2 で新しくなったAPIリファレンス](https://doc-api.photonengine.com/en/pun/v2/index.html)も存在するので活用しましょう。

```PUN2LoginEvent.cs
public class PUN2LoginEvent : MonoBehaviourPunCallbacks
{
    [SerializeField] private Text _myId;

    // Use this for initialization
    void Start()
    {
        _myId.text = "";
        PhotonNetwork.ConnectUsingSettings();
    }

    public override void OnConnectedToMaster()
    {
        Debug.Log("Master Connected!");
        PhotonNetwork.JoinOrCreateRoom("ROOM1", null, null);
    }

    public override void OnCreatedRoom()
    {
        Debug.Log("Create Room!");
    }

    public override void OnJoinedRoom()
    {
        Debug.Log("Join Room!");

        //プレイヤーナンバー設定
        //オーナー譲渡したときにPlayer情報がすべてnullになってしまって判別が困難なのを防ぐため
        int id = PhotonNetwork.CountOfPlayers;
        PhotonNetwork.LocalPlayer.SetPlayerNumber(id);
        _myId.text = id.ToString();
    }
}
```

このスクリプトを適当なオブジェクトにアタッチすることで PUN2 のネットワークに接続することができます。

# オーナー権限を要求されたときの処理を書く
次に Cube のオーナー権限を要求されたときの処理を書きます。
ここが旧バージョンの PUN と大きく違っている点で、PUN2 ではコールバックがすべてインターフェイスで宣言されています。
そのためコールバックを受け取りたいクラスでは、対象のコールバックが宣言されているインターフェイスを継承しつつクラス内で実装する必要があります。
新しく実装されたコールバックインターフェイスの一覧は先ほどの[公式ドキュメント](https://doc.photonengine.com/ja-jp/pun/v2/getting-started/dotnet-callbacks)に載っています。

今回はオブジェクトのオーナー権限を要求したときのコールバックが欲しい為、 __`IPunOwnershipCallbacks`__ を継承します。
このインターフェイスでは __`void OnOwnershipRequest(PhotonView targetView, Player requestingPlayer)`__ と __`void OnOwnershipTransfered(PhotonView targetView, Player previousOwner)`__ の実装を要求しているため、空でもいいのでとにかく実装してあげます。

```PUN2OwnershipEvent.cs
public class PUN2OwnershipEvent : MonoBehaviourPunCallbacks, IPunOwnershipCallbacks
{
    [SerializeField] private PhotonView _photonView;
    [SerializeField] private Text _currentOwnerText;
    [SerializeField] private Text _previousOwnerText;

    private bool _initializedText = false;

    // Use this for initialization
    void Start()
    {
        _currentOwnerText.text = "";
        _previousOwnerText.text = "";
    }

    // Update is called once per frame
    void Update()
    {
        if (!_initializedText && PhotonNetwork.InRoom)
        {
            _currentOwnerText.text = _photonView.Owner.GetPlayerNumber().ToString();
            _initializedText = true;
        }
    }

    //PhotonView.OwnershipTransfer == OwnershipOption.Request の時に呼ばれる
    public void OnOwnershipRequest(PhotonView targetView, Player requestingPlayer)
    {
    }

    //PhotonView.OwnershipTransfer == OwnershipOption.Takeover の時に呼ばれる
    public void OnOwnershipTransfered(PhotonView targetView, Player previousOwner)
    {
        _currentOwnerText.text = _photonView.Owner.GetPlayerNumber().ToString();
        _previousOwnerText.text = previousOwner.GetPlayerNumber().ToString();
    }
}
```

これでオーナー権限を要求されたときの処理が書けました。

# オーナー権限を要求する処理を書く
最後に Cube のオーナー権限を要求する処理を書きましょう。
といってもこれは従来の方法と何ら変わっておらず、対象オブジェクトの PhotonView.RequestOwnership() を叩くだけです。
先ほどのスクリプトの最後あたりに追加しておきましょう。

```PUN2OwnershipEvent.cs
    public void GetOwnershipButtonEvent()
    {
        _photonView.RequestOwnership();
    }
```

この関数を UIButton の OnClick イベントに追加しておけば発火されるようになります。
![スクリーンショット (286).png](https://qiita-image-store.s3.amazonaws.com/0/213883/34bf0fc3-b270-468d-039a-ba03a935e57b.png)

# 動作確認
ここまで設定出来たらビルドして動作確認しましょう。多重起動して疑似マルチプレイの状態を作り出します。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">キューブがコロコロ*2 <a href="https://t.co/v8aiqnAV6b">pic.twitter.com/v8aiqnAV6b</a></p>&mdash; karukaru👑@白魔導技士 (@_karukaru_) <a href="https://twitter.com/_karukaru_/status/1090949527723433985?ref_src=twsrc%5Etfw">2019年1月31日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このようにボタンを押したらオーナー権限が移動するようになっているかと思います。
オーナー権限が移動する前に Cube を動かせるようになってしまっているので、実用の際は対象オブジェクトのオーナー権限が自分にあるかのチェックや、同期方法を変えて移動しないようにする、などの対策が必要かと思います。