Unity 4.3 x Photon Cloud 2Dオンラインアプリチュートリアル(後編)| アドカレ2013 : SP #24

2013.12.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回はアプリの土台とゲームサーバーへの接続させるところまで作りました。今回はプレイヤーをネットワーク上で同期させる仕組みを追加していきたいと思います。

ネットワーク上でオブジェクトを同期させるには大きく以下の3つの手順を行います。

  1. 同期させるオブジェクトに対してPhotonViewを追加する
  2. PhotonNetwork.Instantinate()を使ってインスタンス化する
  3. PhotonViewのObserveに同期対象を指定する

作りながら順を追って説明します。

プレイヤーにPhotonViewを追加する

PhotonViewはオブジェクトを監視してネットワーク上にデータ送信するためのコンポーネントです。また今回は利用しませんがRPC(リモートプロシージャコール)でネットワーク上にあるオブジェクトのメソッドを呼び出すことも出来ます。今回はプレイヤーの動きを同期させたいのでPlayerPrefabに対してPhotonViewを付与してみましょう。

unity-photon2-01

プレイヤーをネットワーク上でインスタンス化する

続いてNetworkManager.csに以下の処理を追加して、ゲームサーバーへ接続成功時にプレイヤーをネットワーク上でインスタンス化します。インスタンス化はPhotonView.Inistantinate()を使って行います。この時、マニュアルでインスタンス化する場合を除いて、インタンス化の対象となるPrefabはResourcesフォルダの直下に格納されている必要があります。

NetworkManager.cs

    //ルーム参加成功時のコールバック
    void OnJoinedRoom() {
        Debug.Log("ルームへの参加に成功しました");
        //プレイヤーをインスタンス化
        Vector3 spawnPosition = new Vector3(0,2,0); //生成位置
        PhotonNetwork.Instantiate("PlayerPrefab", spawnPosition, Quaternion.identity, 0);
    }

実行するとゲームサーバーへの接続後、画面上へプレイヤーが出現しました。ここでWebPlayer用にビルドし、ブラウザで複数同時起動させることで、クライアント間での動作を確認することが出来ます。

unity-photon2-03

自プレイヤーと他プレイヤーを区別して制御する

ブラウザを使って2つのクライアントを同時に起動すると画面上には2つプレイヤーが出現しています。しかし自プレイヤーを操作してみると、他プレイヤーまで同時に動いてしまいました(同じ見た目なのでわかりにくいですが・・)これはPlayerControllerが自プレイヤーと他プレイヤーの区別がついていないため起こっています。まずはこの問題を解決しましょう。

PlayerController.csを開いてUpdate()内の処理を次のように修正します。

PlayerController.cs

    void Update () {
        //自プレイヤーであるか評価
        if(photonView.isMine){ 
            //移動
            float inputX = Input.GetAxis("Horizontal");
            float inputY = Input.GetAxis("Vertical");
            Vector2 force = new Vector2(inputX, inputY) * movePower;
            rigidbody2D.AddForce(force);

            //ジャンプ
            if(Input.GetButtonDown("Jump")) {
            rigidbody2D.AddForce(Vector2.up * jumpPower);
            }
        }
    }

photonView.isMineはこのスクリプトを実行したオブジェクトが自分であるかどうかを判断するプロパティです。 修正したら先ほどと同じように実行して確認してみましょう。自プレイヤーだけを操作出来ていればOKです。

プレイヤーの動作をクライアント間で同期させる

複数のプレイヤーを同時に操作出来てしまう問題は解決しました。しかしもう一つ問題があります。別のクライアントへその動作が伝わっていません。操作によって変化したプレイヤーの情報をクライアント間で送受信する仕組みを追加しましょう。

プレイヤーのTransformをPhotonViewの監視対象(observe)へ直接指定することも出来ますが、今回は送受信するデータを自前で定義したいのでスクリプトを使って制御していきます。

まずPhoton.MonoBehaviourを継承したクラスを作成し次のような処理を記述します。ここでは「Synchronizer.cs」という名前にしました。これをPlayerPrefabへ付与し、さらにそれをPhotonViewのObserveへドラッグ&ドロップして監視対象として指定します。

Synchronizer.cs

using UnityEngine;

public class Synchronizer : Photon.MonoBehaviour {

    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
        if (stream.isWriting) {
            //データの送信
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
            stream.SendNext(rigidbody2D.velocity);
        } else {
            //データの受信
            transform.position = (Vector3)stream.ReceiveNext();
            transform.rotation = (Quaternion)stream.ReceiveNext();
            rigidbody2D.velocity = (Vector2)stream.ReceiveNext();
        }
    }
}

unity-photon2-05

OnPhotonSerializeView()はPhotonNetorkクラスのsendRateとsendRateOnSerializeプロパティに従って呼び出されます。ここでビットストリームの状態(stream.isWriting)から判断してデータの送信と受信を行っています。今回はプレイヤーの持つ位置と回転、剛体の速度のそれぞれ受け渡せるようにしました。

実行するとクライアント間で各プレイヤーの動きが同期されていることが確認出来ると思います。

・・が、何だか時々カクカクとぎこちない動きをしています。

オブジェクトをスムーズに同期させる

オブジェクトがカクカクする原因は、先ほど少し触れたsendRateに関係しています。sendRateは初期値で毎秒15回の送信を行うように設定されています。情報の送受信回数が描画フレームレートに比べて低いためオブジェクトがワープしているように見えてしまいます。

単純に考えるとsendRateを高く設定すれば改善できそうですが、同時にネットワークに掛かる負荷も大きくなってしまうため、今回は別の方法で何とかしたいと思います。

Synchronizer.csを次のように修正しました。

Synchronizer.cs

using UnityEngine;

public class Synchronizer : Photon.MonoBehaviour {
    //受信データ
    private Vector3 receivePosition = Vector3.zero;
    private Quaternion receiveRotation = Quaternion.identity;
    private Vector2 receiveVelocity = Vector2.zero;

    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
        if (stream.isWriting) {
            //データの送信
            stream.SendNext(transform.position);
            stream.SendNext(transform.rotation);
            stream.SendNext(rigidbody2D.velocity);
        } else {
            //データの受信(変数へ格納)
            receivePosition = (Vector3)stream.ReceiveNext();
            receiveRotation = (Quaternion)stream.ReceiveNext();
            receiveVelocity = (Vector2)stream.ReceiveNext();
        }
    }

    void Update() {
        //自分以外のプレイヤーの補正
        if(!photonView.isMine){
            transform.position = Vector3.Lerp(transform.position, receivePosition, Time.deltaTime * 10);
            transform.rotation = Quaternion.Lerp(transform.rotation, receiveRotation, Time.deltaTime * 10);
            rigidbody2D.velocity = Vector2.Lerp(rigidbody2D.velocity, receiveVelocity, Time.deltaTime * 10);
        }
    }
}

簡単に説明すると、受信した他プレイヤーのデータを一旦変数へ格納し、Update()で現在のデータと受信したデータの中間補正を行うように処理を変更しています。足りない情報をクライアント側で補ってそれらしく見せている、といった感じです。剛体の速度も補正していますが、見た目の問題だけであればTransform情報の補正で十分かもしれません。

実行すると先ほどと比べて他プレイヤーの動きがスムーズになっていることが確認出来ると思います。 ちなみにsendRateを変更したい場合は、PhotonNetwork.sendRateとPhotonNetwork.sendRateOnSerializeに任意の整数値をセットします。極端な例ですが、NetworkManager.csに次のような処理を追加して実行すると、同期タイミングと中間補正がどのように動いているか実行時にわかりやすいと思います。毎秒1回にレートを落としています。

NetworkManager.cs

    void Awake() {
        //秒間送信レートを設定(初期値15)
        PhotonNetwork.sendRate = 1;
        PhotonNetwork.sendRateOnSerialize = 1;
        //マスターサーバー(ロビーサーバー)へ接続
        PhotonNetwork.ConnectUsingSettings("v0.1");
    }

動作サンプルとソースコード

さてそろそろ最後ですが、下記の動作サンプルはこれまでに作ってきたものをベースに、周囲を壁で囲み、カメラが自プレイヤーを追従するようにし、超高速回転するトラップを闇雲に設置したものです。これで豆を操作してマルチプレイで戯れる・・という何かが完成しました。操作方法はカーソルキー又はWASDキーで移動、スペースキーでジャンプというか急上昇します。ちなみに20クライアントまでしか同時接続出来ないのでそこは宜しくお願いします。

動作サンプル
ソースコード

まとめ

前編、後編と駆け足で進めてきましたが、UnityとPhoton Cloudを使って簡単にサーバーへの接続からオブジェクトを同期させる仕組みを作れるということがわかりました。Photon Cloudはまだ日本語の情報が少ないのですが、公式サイトにドキュメント(英語)が一通り揃っていますので、より詳しく調べたい方はそちらを参考にして頂ければと思います。

Unityも日々進化していますし、これからはインディーでしか出せないようなユニークなオンラインゲームが次々と登場してくるかもしれませんね。楽しみです。それではメリークリスマス。

山本

関連記事

Unity 4.3 x Photon Cloud 2Dオンラインアプリチュートリアル(前編)| アドカレ2013 : SP #3