💭

PUN2でプレイヤーの情報の同期を行う。

2022/11/03に公開

概要

PUN2を使って2名の対戦者間でトランプ情報の共有をおこないます。ハロウィンだったので、トランプ画像の代わりにおばけの情報を表示しています。その後、麻雀などを想定して4名の情報の同期方法を考えます。

環境

Unity 2020.3.35f1
OS : windows10 pro
PUN2 ver 2.41 photon lib 4.1.6.17

結果


いまは1秒おきに同期処理を動かしているため、しばらくたつと次のようになります。

コード

githubを参照してください。

ポイント

ポイントとなる部分を下記に記述します。
なお、今回はプレイヤーが2人なので、MasterClientかそれ以外かで分けていますが、人数が増えるともう一工夫が必要になります。

シリアライズ

PunRPCの場合、引数に使える型は原則として基本形のみです。それ以外は自分でシリアライズ処理を記述する必要があります。RPCSyncCard(Card val, int mp) のようにCardクラス型を使うならば、PhotonPeer.RegisterType()でシリアライズ/デシリアライズ関数を登録して実装する必要があります。下の例では1要素ずつ記述していますがJSONに変換して文字列として送信するとコードがすっきりします。

    void SyncCard(Card val, int mp)    {
        photonView.RPC("RPCSyncCard", RpcTarget.All, val, mp);
    }
    [PunRPC]
    private void RPCSyncCard(Card val, int mp)    {
        m_card =val;
        this.mp = mp;
    }
       ExitGames.Client.Photon.PhotonPeer.RegisterType(typeof(Deck), 0x41, Deck.Serialize, Deck.Deserialize);
        ExitGames.Client.Photon.PhotonPeer.RegisterType(typeof(Card), 0x42, Card.Serialize, Card.Deserialize);

    public static byte[] Serialize(object i_customobject)
    {
        Card card = (Card)i_customobject;
        //card.m_share = new Share();
        var bytes = new byte[ 3*sizeof(int)];
        int index = 0;
        ExitGames.Client.Photon.Protocol.Serialize(card.fixid, bytes, ref index);
        ExitGames.Client.Photon.Protocol.Serialize(card.atk_plus, bytes, ref index);
        ExitGames.Client.Photon.Protocol.Serialize(card.visibleIcon, bytes, ref index);
        return bytes;
    }

    public static object Deserialize(byte[] i_bytes)
    {
        Card card = new Card();
        int index = 0;
        ExitGames.Client.Photon.Protocol.Deserialize(out card.fixid, i_bytes, ref index);
        ExitGames.Client.Photon.Protocol.Deserialize(out card.atk_plus, i_bytes, ref index);
        ExitGames.Client.Photon.Protocol.Deserialize(out card.visibleIcon, i_bytes, ref index);
        return card;
    }

2アプリ間で情報の共有

2アプリ間で情報の共有をするため、OS上のプロセスが2個必要になります。プロセスAとプロセスBが存在して、先に起動した方がマスタークライアントになります。このとき4つの場合分けをして考える必要があります。PhotonNetwork.isMasterClientとphotonView.IsMineを使います。

コードでは次のように場合分けをしています。

            if (PhotonNetwork.IsMasterClient) {
                field_name = (photonView.IsMine) ? "Field_M" : "Field_C";
            } else{
                field_name = (photonView.IsMine) ? "Field_C" : "Field_M";
            }

同期

相手の情報は相手のプロセスから送信されてきます。相手のプロセス視点だと自分の情報を送信しています。そのため、自分の情報のみを相手に送信するように記述します。ちょっとややこしいですね。

    public void DoSync(){
        if (this.photonView.IsMine) {
            SyncCard(this.m_card, this.mp);
            SyncData(m_deck);
        }
    }

麻雀のように4人で同期したい場合

この場合、if (PhotonNetwork.IsMasterClient) を使うと破綻するため、4人分のチケットを発行して、チケットを取得できた場合のみゲーム参加するような処理が必要になります。複数の方法で実現はできますが、ネットワークオブジェクトを発行して所有権をとりにいく方式で実装しました。

	//チケットの発行
            for (int i = 0; i< EntryTicket.ticket_max; i++) {
                var ticketobj = PhotonNetwork.Instantiate("EntryTicket", Vector3.zero, Quaternion.identity);
                var ticket = ticketobj.GetComponent<EntryTicket>();
                ticket.SetNo(i+1);
            }	    

ネットワークオブジェクトに"Ticket"タグをつけておき、FindGameObjectsWithTagしたものを保存していおく。ここではUpdateで行っていますが、OnPlayerEnteredRoom()で更新すると効率的です。

    void Update()
    {
        if(m_ticketArray ==null){

            var objs = GameObject.FindGameObjectsWithTag("Ticket");

            m_ticketArray = new EntryTicket[objs.Length];

            int i = 0;
            foreach (var obj in objs) {
                m_ticketArray[i++] = obj.GetComponent<EntryTicket>();
                //m_list.Add(obj.GetComponent<EntryTicket>());
            }
        }
    }	

備考

.NET Standard 2.0だとスタンドアロン実行でJSONシリアライズ系が失敗する事があるようです。

Discussion