徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

【Unity】僕もPhotonを使いたい #09 RPC() シリアライズ編

 さて、今回も前回に引き続きRPC()関数についてです。
 RPC()関数で呼ぶことが出来る引数の型には少し制限があるため、使う際には以下の内容に気を付けてください。


この記事にはUnity5.4.1f1及びPUN1.75を使用しています。

RPC()で呼ぶ引数について

 PhotonView.RPC()を使うことで関数をRPCとして呼ぶことができ、ルーム内の他のプレイヤーのゲームに同期させることが出来ます。
 前回PhotonTargetsを調べる際に呼んだ際には、引数のない関数を呼んでいました。

www.urablog.xyz

引数として渡せる数について

 では、引数のある関数をPhotonView.RPC()を使って呼ぶにはどうすればよいのでしょうか。
 それは第三引数に渡すパラメータがRPCで呼び出す関数の引数として使用されるのです。

using UnityEngine;

public class DemoRPC : MonoBehaviour
{
    public void SendMethod()
    {
        PhotonView  photonView  = GetComponent<PhotonView>();
        photonView.RPC( "MethodRPC", PhotonTargets.All, 10 );
    }

    [PunRPC]
    private void MethodRPC( int i_value )
    {
        Debug.LogFormat( "MethodRPC value={0}", i_value );
    }

} // class DemoRPC

 上記のphotonView.RPC( "MethodRPC", PhotonTargets.All, 10 );のように使うことでRPCで呼ぶ関数に引数を渡すことが出来ます。
 引数が複数個ある関数の場合は、第三引数以降に渡していけばOKです。

using UnityEngine;

public class DemoRPC : MonoBehaviour
{
    public void SendMethod()
    {
        PhotonView  photonView  = GetComponent<PhotonView>();
        photonView.RPC( "MethodRPC", PhotonTargets.All, 10, "Hello", 3.14f );
    }

    [PunRPC]
    private void MethodRPC( int i_valueA, string i_valueB, float i_valueC )
    {
        Debug.LogFormat( "MethodRPC A={0}, B={1}, C={2}", i_valueA, i_valueB, i_valueC );
    }

} // class DemoRPC

 ちなみに引数を間違えようものなら、容赦なくエラーが発生します。

using UnityEngine;

public class DemoRPC : MonoBehaviour
{
    public void SendMethod()
    {
        PhotonView  photonView  = GetComponent< PhotonView >();
        photonView.RPC( "MethodRPC", PhotonTargets.All, 10 );
    }

    [PunRPC]
    private void MethodRPC( int i_valueA, string i_valueB, float i_valueC )
    {
        Debug.LogFormat( "MethodRPC A={0}, B={1}, C={2}", i_valueA, i_valueB, i_valueC );
    }

} // class DemoRPC

f:id:urahimono:20160922002738p:plain

PhotonView with ID 1001 has no method "MethodRPC" that takes 1 argument(s): Int32

引数で使える型について

 では引数として渡せる型はどうなのでしょうか。なんでも使えるのでしょうか。
 試しにColor型の引数を渡してみましょう。

using UnityEngine;

public class DemoRPC : MonoBehaviour
{
    public void SendMethod()
    {
        PhotonView  photonView  = GetComponent< PhotonView >();
        photonView.RPC( "MethodRPC", PhotonTargets.All, Color.red );
    }

    [PunRPC]
    private void MethodRPC( Color i_color )
    {
        Debug.LogFormat( "MethodRPC value={0}", i_color );
    }

} // class DemoRPC

f:id:urahimono:20160922002749p:plain

Exception: cannot serialize(): UnityEngine.Color

 残念なことにエラーが出ました。

 引数として渡せる型は、デフォルトでは以下のドキュメントに記述されたものしか渡せません。

doc.photonengine.com

 それ以外の型はシリアライズの対応が行われていないため渡すことが出来ないのです。
 これはOnPhotonSerializeView()でも同様です。

www.urablog.xyz

 ただ、シリアライズは自作することが出来ます
 自分でシリアライズ、デシリアライズ関数を作成することで、どんな型でもRPC()関数の引数として使用することが出来ます。

 試しにColorのシリアライズを自作してみましょう。

ColorSerializer.cs

using UnityEngine;

public static class ColorSerializer
{
    public static void Register()
    {
        ExitGames.Client.Photon.PhotonPeer.RegisterType( typeof( Color ), (byte)'C', SerializeColor, DeserializeColor );
    }

    private static byte[] SerializeColor( object i_customobject )
    {
        Color color = (Color)i_customobject;

        var bytes   = new byte[ 4 * sizeof( float ) ];
        int index   = 0;
        ExitGames.Client.Photon.Protocol.Serialize( color.r, bytes, ref index );
        ExitGames.Client.Photon.Protocol.Serialize( color.g, bytes, ref index );
        ExitGames.Client.Photon.Protocol.Serialize( color.b, bytes, ref index );
        ExitGames.Client.Photon.Protocol.Serialize( color.a, bytes, ref index );

        return bytes;
    }

    private static object DeserializeColor( byte[] i_bytes )
    {
        var color   = new Color();
        int index   = 0;
        ExitGames.Client.Photon.Protocol.Deserialize( out color.r, i_bytes, ref index );
        ExitGames.Client.Photon.Protocol.Deserialize( out color.g, i_bytes, ref index );
        ExitGames.Client.Photon.Protocol.Deserialize( out color.b, i_bytes, ref index );
        ExitGames.Client.Photon.Protocol.Deserialize( out color.a, i_bytes, ref index );

        return color;
    }

} // class ColorSerializer

DemoRPC.cs

using UnityEngine;

public class DemoRPC : MonoBehaviour
{
    void Awake()
    {
        // 適当な箇所が無かったため、ここでColorのシリアライズの登録しています。
        // 本来はプロジェクトの初期化を行う箇所でシリアライズの登録をしたほうが望ましいです。
        // PhotonPeer.RegisterType()は、登録してあるものを再度登録してもエラーになることはないようです。
        ColorSerializer.Register();
    }

    void OnMouseDown()
    {
        SendMethod();
    }

    public void SendMethod()
    {
        PhotonView  photonView  = GetComponent< PhotonView >();

        var randColor   = new Color( Random.value, Random.value, Random.value, 1.0f );
        photonView.RPC( "MethodRPC", PhotonTargets.All, randColor );
    }

    [PunRPC]
    private void MethodRPC( Color i_color )
    {
        Renderer render         = GetComponent<Renderer>();
        render.material.color   = i_color;
    }

} // class DemoRPC

f:id:urahimono:20160922002904g:plain

 Colorを引数として渡すことが出来ました。
 Colorはサイズが固定の構造体なので比較的に簡単にシリアライズを実装出来ました。
 これがListなどを使った可変長のデータを持つクラスをシリアライズだとしたら自作はなかなか大変そうですね……。

おまけ 継承時

 [PunRPC]アトリビュートをつけた関数を持つクラスを以下のように継承するとします。
 そして継承したクラスでPhotonView.RPC()を使って継承元の関数を呼びます。

DemoRPCBase.cs

using UnityEngine;

public class DemoRPCBase : MonoBehaviour
{
    [PunRPC]
    private void MethodRPC( Color i_color )
    {
        Renderer render         = GetComponent<Renderer>();
        render.material.color   = i_color;
    }

} // class DemoBase

DemoRPC.cs

using UnityEngine;

public class DemoRPC : DemoRPCBase
{
    void Awake()
    {
        // 適当な箇所が無かったため、ここでColorのシリアライズの登録しています。
        // 本来はプロジェクトの初期化を行う箇所でシリアライズの登録をしたほうが望ましいです。
        // PhotonPeer.RegisterType()は、登録してあるものを再度登録してもエラーになることはないようです。
        ColorSerializer.Register();
    }

    void OnMouseDown()
    {
        SendMethod();
    }

    public void SendMethod()
    {
        PhotonView  photonView  = GetComponent< PhotonView >();

        var randColor   = new Color( Random.value, Random.value, Random.value, 1.0f );
        photonView.RPC( "MethodRPC", PhotonTargets.All, randColor );
    }

} // class DemoRPC

 上記のやり方はエラーになります。

f:id:urahimono:20160922002807p:plain

PhotonView with ID 1001 has no method "MethodRPC" marked with the PunRPC or @PunRPC(JS) property! Args: Color

 なぜかというと、呼ばれる関数MethodRPC()のアクセス修飾子がPrivateだからです。

 継承元のクラスのPrivate関数は呼べなくて当然なのですが、PhotonView.RPC()文字列で関数を呼んでいるためSendMessage()のような感覚に陥りがちです。
 呼べないので注意しましょう。
 上記のスクリプトはアクセス修飾子を変えることで呼び出すことが可能になります。

using UnityEngine;

public class DemoRPCBase : MonoBehaviour
{
    [PunRPC]
    protected void MethodRPC( Color i_color )
    {
        Renderer render         = GetComponent<Renderer>();
        render.material.color   = i_color;
    }

} // class DemoBase

 二回にわたってRPCを調べてきました。
 便利なものですが、使い方を間違えると思わぬエラーやバグが発生するので扱いには気を付ける必要がありますね。

 次回 www.urablog.xyz

 前回 www.urablog.xyz