Edited at

Unity + PhotonRealtime でオンラインゲームを作る


概要

Unity のオンラインゲームで使われるクラウドエンジンである PhotonRealtime を使ってみる。

ちょっと使ってみた感じは結構良さそうな印象を受けた。

詳しくは公式ドキュメントを参照。

ただ、公式はかなりふわふわしてるので QiitaやTwitter、英語ブログ等を中心に検索すると良さそう。

Photon でモデルを共有する際、クライアント実行ファイルに予め該当のモデルが存在しないと表示されないので、アバターアップロード等の機能を実装するときは外部からアバターをダウンロードしてくる処理を実装する必要がありそう。


Photonファミリー

名称
機能

PhotonRealtime
MO とか MMO 向け (VRなら多分これ)

PhotonTurnbased
ターン制ゲームの特化版

PhotonChat
チャット機能に特化していて、料金も安い

PhotonVoice
ボイスチャット機能を提供してくれる


サンプル

https://github.com/kawashi/PhotonExample

サンプルgif

https://github.com/kawashi/PhotonAnimatorSyncExample

Image from Gyazo


開発の流れ



  1. PhotonRealtime の登録

  2. SDK ( Photon Unity Networking, 2じゃない方 ) を AssetStore から導入 (Demoはチェックを外す)

  3. SDK のセットアップ

  4. AppID の入力

  5. リージョンの設定 (JP で良い)

  6. Auto-Join Lobby と Enable Lobby Stats のチェックを入れる

  7. 同期する GameObject に Photon Transform View と Photon View のコンポーネント導入

  8. Photon Transform View を設定

  9. Photon View の Observed Components に Photon Transform View をセット

  10. 同期するオブジェクトをプレハブにする

  11. サーバやルームへ接続するスクリプトを記述して、Empty GameObject に追加

基本的には上記の流れになる。

SDK の設定は Photon Unity Networking/Resources/PhotonServerSettings.asset から出来る。

同期するオブジェクトの扱いについてはもうちょっと実験が必要そう。

PhotonRealtime は多分 GameObject の座標や回転をオンラインで共有出来るってことだと思う。


サーバやルームへ接続する流れについて

ソースコード を眺めながら詳しく見ていく。

基本的に PhotonNetwork クラスに必要なメソッドが静的に定義されている。

ゲーム時のフローは多分下記の通り。


  1. サーバへ接続

  2. ロビーへ入出

  3. ルームへ入出 (ルームが無ければ作成して入出)

  4. 同期オブジェクトの読み込み (同期オブジェクトの操作?)

1 - 3 までのフローは共通で、4 のフローが重要になりそう。

各フローの詳細はソースコード参照。


アニメーションを同期させるには

Photon でのアニメーション同期は Photon Animator View という Component で実装可能。

Photon では Animator の Parameter を共有出来る仕組みを用意してくれている。

書籍にはコードで実装する方法があるが、こっちの方がコードを Component で隠蔽してて楽。

使い方は下記の通り。


  1. 予め Animator を設定しておく

  2. 対象の GameObject に Photon Animator View を追加

  3. Photon View の Observed Component に Photon Animator View を D&D

  4. Photon Animator View の Synchronize Parameters を設定する

また、対象の GameObject には下記のコードを Update メソッドの最初に追加しておくこと。

これを行うことで自分のキャラクターのみ Update() の内容を反映出来る。

また、これを行わないとアニメーションが同期されないことがあるので注意。

void Update() {

if (!(photonView.isMine)) return;
...
}

アニメーションに関するその他の情報は下記リンクや書籍を参照。


ボイスチャットを実装するには

サンプル: https://github.com/kawashi/PhotonVoiceExample

これ 通りにやればすぐに出来る。

手順は下記の通り。


  1. PhotonVoiceアプリケーションの登録

  2. PUN と Photon Voice の導入

  3. 音を出す GameObject に Photon Voice Recoder と Photon View を追加


クライアントにメッセージを送信する

PhotonView を持っている GameObject にメッセージを送信する時は RPC というものを使うと良い。

例えば、当たり判定等はラグで位置情報だけだと上手く同期出来ないので、RPC を使って当たったことを通知する。

RPC は PhotonView を持っている全ての GameObject のスクリプトで送受信可能。

RPC を扱う際、ViewID という物を使うと PhotonView を持っている GameObject を一意に識別出来る。

( ViewID は PhotonNetwork.Instantiate で生成された GameObject に一意に割り振られる。 )

例:

void OnCollisionEnter(Collision other) {

photonView.RPC("OnCollisionRPCFunction", PhotonTargets.All, photonView.viewID);
}

[PunRPC]
void OnCollisionRPCFunction(int viewID) {
if ( photoView.viewID == viewID ) {
Debug.Log("自分と同一のviewIDからRPCメソッドが呼ばれた!");
}
}

PhotonTargets は Enums.cs で下記のように定義されてる。


Enum.cs

/// <summary>Enum of "target" options for RPCs. These define which remote clients get your RPC call. </summary>

/// \ingroup publicApi
public enum PhotonTargets
{
/// <summary>Sends the RPC to everyone else and executes it immediately on this client. Player who join later will not execute this RPC.</summary>
All,
/// <summary>Sends the RPC to everyone else. This client does not execute the RPC. Player who join later will not execute this RPC.</summary>
Others,
/// <summary>Sends the RPC to MasterClient only. Careful: The MasterClient might disconnect before it executes the RPC and that might cause dropped RPCs.</summary>
MasterClient,
/// <summary>Sends the RPC to everyone else and executes it immediately on this client. New players get the RPC when they join as it's buffered (until this client leaves).</summary>
AllBuffered,
/// <summary>Sends the RPC to everyone. This client does not execute the RPC. New players get the RPC when they join as it's buffered (until this client leaves).</summary>
OthersBuffered,
/// <summary>Sends the RPC to everyone (including this client) through the server.</summary>
/// <remarks>
/// This client executes the RPC like any other when it received it from the server.
/// Benefit: The server's order of sending the RPCs is the same on all clients.
/// </remarks>
AllViaServer,
/// <summary>Sends the RPC to everyone (including this client) through the server and buffers it for players joining later.</summary>
/// <remarks>
/// This client executes the RPC like any other when it received it from the server.
/// Benefit: The server's order of sending the RPCs is the same on all clients.
/// </remarks>
AllBufferedViaServer
}

余談だが、恐らく cluster ではこのメッセージ同期に MQTT を利用していると考えられる。

参考URL

- RPCとRaiseEvent | Photon Engine

- Unityでゲームを作っている方は必見!クラスターのUnityエンジニアが語る「クラスターの魅力とVR×Unityのお仕事」 | cluster's Blog


プロパティを共有する

Photon では全クライアントでプロパティを共有することが出来る。

プロパティの設定は下記の通り。

var properties = new ExitGames.Client.Photon.Hashtable();

properties.Add ("プロパティ名", );
PhotonNetwork.room.SetCustomProperties (properties);

また、プロパティの取得は下記の通り。

(プロパティが存在しない時は NullReferenceException が発生する)

(型名)PhotonNetwork.room.CustomProperties ["プロパティ名"]

参考URL: https://qiita.com/mo4_9/items/f43606875c8b198fb145


他プレイヤーの新規接続を検知する

下記メソッドを定義すると、新規プレイヤーを検知出来る。

void OnPhotonPlayerConnected(PhotonPlayer player) {

// ...
}


注意点


  • ログイン直後は全 Object が初期位置に一瞬配置される為、当たり判定が発火する可能性がある

  • 相手が生成した Object のスクリプトも動いてるのを忘れてバグを生む可能性がある

  • ラグで当たり判定がズレてバグることがある、当たり判定での処理はRPCを使ったほうが良い

  • ログイン直後は相手の Object が存在していないので、Object を参照したい時は Invoke で数秒待つ

  • マスタークライアントという概念を取り入れる場合、マスターが切断した時の引き継ぎ処理が必要 (公式で仕組みが用意されているっぽい...?)


  • PhotonNetwork.Instantiate はキャッシュが残り、PhotonNetwork.Destroy で削除しないとキャッシュが残る可能性がある ( ただし、削除は所有者が行う必要がある、MonoBehaviour.Destroy じゃ駄目 )


Photon関連URL