橋本翼(ツバサムス)

メタバースプログラマー。
UnityとUnreal Engineを専門的に扱う。
メタバース系スタートアップ企業に所属。

詳細はこちら

【Unity】「PUN2」の「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()
    {
        //適切なy座標を取得する
        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)
    {
        //Textを取得し、設定する
        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()
    {
        //適切なy座標を取得する
        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()
    {
        //Textを取得し、設定する
        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. 「Player」(ネットワークオブジェクト)の生成
  4. 生成した「Player」(ネットワークオブジェクト)の初期設定

を行っている。

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をコピーしました