【Unity】PUN2(Photon Unity Networking 2)のRPCを用いた同期方法と引数の重要性

Unity

RPCと引数

RPCとは

RPC(Remote Procedure Call)とは直訳すると「遠く離れた手続きの呼び出し」である。

要するに他のプレイヤーと「同時」に「同じ」メソッドを実行するという事である。

基本的な使用方法などに関しては o8que 様のこちらの記事を読むと良いだろう。

サンプルゲーム概要

2人のプレイヤーがオンラインでゲームに参加し、画面に各プレイヤーの名前を表示する。

1人目は「Taro」で、2人目は「Hanako」である。

コード

PhotonController.cs

using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

public class PhotonController : MonoBehaviourPunCallbacks
{
    private bool isConnecting;

    private void Awake()
    {
        //マスタークライアントと同じシーンを読み込む
        PhotonNetwork.AutomaticallySyncScene = true;
    }

    private void Start()
    {
        if (PhotonNetwork.IsConnected)
        {
            PhotonNetwork.JoinRandomRoom();
        }
        else
        {
            isConnecting = PhotonNetwork.ConnectUsingSettings();
        }
    }

    //マスターサーバーに接続した際に呼び出される
    public override void OnConnectedToMaster()
    {
        if (isConnecting)
        {
            //「room」に参加する
            PhotonNetwork.JoinOrCreateRoom("room", new RoomOptions(), TypedLobby.Default);

            //マスターサーバーに接続していない状態に切り替える(ゲームサーバーに接続するため)
            isConnecting = false;
        }
    }

    //ゲームサーバーに接続した際に呼び出される
    public override void OnJoinedRoom()
    {
        //ネットワークオブジェクトを生成し、それの初期設定を行う
        PhotonNetwork.Instantiate("Player", Vector3.zero, Quaternion.identity).GetComponent<PlayerController>().SetUp();
    }
}

PlayerController.cs

RPCメソッドの引数を利用した場合
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(PhotonView), typeof(PhotonTransformView))]
public class PlayerController : MonoBehaviourPunCallbacks
{
    private string myName;

    public void SetUp()
    {
        float posY = PhotonNetwork.LocalPlayer.IsMasterClient ? 3f : -1f;

        transform.position = new(0f, posY, 0f);

        Camera.main.transform.SetParent(transform);

        myName = PhotonNetwork.LocalPlayer.IsMasterClient ? "Taro" : "Hanako";

        //RPCメソッドを呼び出す
        photonView.RPC(nameof(RpcDisplayPlayerName), RpcTarget.All, myName);
    }

    [PunRPC]
    private void RpcDisplayPlayerName(string name)
    {
        transform.GetChild(0).GetChild(0).GetComponent<Text>().text = name;
    }

    //他のプレイヤーが同じルームに参加した際に呼び出される
    public override void OnPlayerEnteredRoom(Player newPlayer)
    {
        //RPCメソッドを呼び出す
        photonView.RPC(nameof(RpcDisplayPlayerName), RpcTarget.All, myName);
    }
}
RPCメソッドの引数を利用しない場合
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(PhotonView), typeof(PhotonTransformView))]
public class PlayerController : MonoBehaviourPunCallbacks
{
    private string myName;

    public void SetUp()
    {
        float posY = PhotonNetwork.LocalPlayer.IsMasterClient ? 3f : -1f;

        transform.position = new(0f, posY, 0f);

        Camera.main.transform.SetParent(transform);

        myName = PhotonNetwork.LocalPlayer.IsMasterClient ? "Taro" : "Hanako";

        //RPCメソッドを呼び出す
        photonView.RPC(nameof(RpcDisplayPlayerName), RpcTarget.All);
    }

    [PunRPC]
    private void RpcDisplayPlayerName()
    {
        transform.GetChild(0).GetChild(0).GetComponent<Text>().text = myName;
    }

    //他のプレイヤーが同じルームに参加した際に呼び出される
    public override void OnPlayerEnteredRoom(Player newPlayer)
    {
        //RPCメソッドを呼び出す
        photonView.RPC(nameof(RpcDisplayPlayerName), RpcTarget.All);
    }
}

結果

RPCメソッドの引数を利用した場合

Taro 視点(1人目)
Hanako 視点(2人目)

RPCメソッドの引数を利用しない場合

Taro 視点(1人目)
Hanako 視点(2人目)

説明

スクリプト解説

PhotonController.cs
  1. マスターサーバーへの接続
  2. ゲームサーバーへの接続
  3. ネットワークオブジェクト生成
  4. 生成したネットワークオブジェクト初期設定

を行っている。

PlayerController.cs
  1. 自身の座標の設定
  2. カメラの親の設定
  3. 自身の名前の取得(「マスタークライアントがどうか」の条件分岐によって、1人目は「Taro」、2人目は「Hanako」になる)
  4. 自身が生成されたタイミング他のプレイヤーが参加したタイミングでプレイヤー名を表示

を行っており、「RPCメソッドで引数を利用するか、利用しないか」という違いで2つのスクリプトを作成してある。

結果の違い

前述したように「PlayerController.cs」の2つのスクリプトの違いは「RPCメソッドで引数を利用するか、利用しないか」という事だが、結果は大きく異なる。

引数を利用した方は2人とも正常に各プレイヤー名が表示されているが、引数を利用していない方はそれぞれ自分の名前しか表示されていない

原因

自分は Photon の開発チームのメンバーではないため、正確な原因や理由は分からないが、RPCによって実行されるメソッド内の変数は引数を利用して渡してあげない限りは空になるのではないだろうか。

今回の場合の変数は myName 変数で string 型は Null 許容型ではないため、「null」ではなく「string.Empty」が格納されていたのだと思われる。

結論

RPCを用いて同期を行う際は「引数を利用するかしないか」で大きく結果が変わるため、「RPCで呼び出しているはずなのに、なぜか上手くいかない…」と悩んでしまったら、引数を利用する事も考えると良いだろう。

ちなみにRPCの引数に使える型は

  • 値型(byte/int/float等)
  • 値型の配列(byte[]/int[]/float[]等)
  • 文字列(string/string[])
  • その他(Vecter3/Quaternion等)

である。

その他

参考記事

お問い合わせ

    タイトルとURLをコピーしました