Photonでは、ターン制ゲームにオンライン対戦を実装するのに役立つ"PunTurnManager"というものが用意されています。
これらを使わなくても、ターン性のオンラインゲームは作れないことはありませんが、
本記事では、PunTurnManagerが気になる!、という方の参考になればと思い、その使い方を書き留めておきます。
なお、対戦相手とマッチングするまでの
①サーバーにログイン
②ロビー上で対戦するルームを探し、入室
という流れは、Photon共通で同じ仕組みです。
ちなみに、Photonを使ってオンライン対戦を可能にするアセットはこちら↓
Pun2 Freeは無料アセットで、同時接続最大20人までの利用できるプランです。
プロジェクトへのインポート、サーバーへのログインからルーム入室までの実装は
オンラインFPSゲームの作り方の#1と#2を読めばわかるので、ご参考ください↓
1. Pun Turn Managerを使うと何がいいのか?
ターン制ゲームシステムが機能するようにコーディングされたスクリプトを利用するので、手番を参加しているプレイヤーに回したり、データの送受信の仕組みを作る手間が省くことができます。
また、通信量が不必要にかからないこと。
通常Photonでオンライン対戦を開発すると、
MasterClient(ルームホスト)のシーンを参加プレイヤー全員に同期するものですが、
Pun Turn Managerを使う場合は、シーンの同期は行いません。
アクションゲームなどは常に状況が変わるのでシーン同期が適していますが、
ターン制は毎フレームごとへの状況判断は必要なく、
じっくり頭脳を使うゲームなので、
相手が手番を行ったときや勝敗チェックされたときのみが通信を行えれば十分です。
Pun Turn Managerはそれらデータの送受信を管理し、
ゲームを進行する最低限の通信量に抑えやすくできるのです。
2. 仕組みを知り、実装してみよう
先ほど述べたように対戦ルームに入るまでは割愛し、対戦するゲームシーンでどのように実装するのか説明します。
前提として、オフラインで遊べるボードゲームやカードゲームを用意してください。
オフラインで動くスクリプトを参考に、オンライン対応したスクリプトを作成していくのが良いでしょう。
必要なスクリプトは、そのオンライン対応したゲーム管理スクリプト(OnlineGameManager.csと名付けたり)と、PunTurnManager.csです。
PunTurnManager.csは、
Photonフォルダ内にあるPhotonUnityNetworkingの配下、UtilityScripts内のTurnBasedフォルダの中にあります。
空のゲームオブジェクトにOnlineGameManager.csとPunTurnManager.csをアタッチします。
あとはOnlineGameManager.csから、Pun Turn Managerの機能を使って、ゲームを進行するようにします。
OnlineGamaManager.cs冒頭には、次のように書いて、Punの機能を引用できるようにしていきます。
using System.Collections; using System.Collections.Generic; using UnityEngine; using Photon.Pun; using Photon.Pun.UtilityScripts; using Photon.Realtime; public class GameControllerOnline : MonoBehaviourPunCallbacks, IPunTurnManagerCallbacks { PunTurnManager punTurnManager = default;
次に具体的に処理される関数を書いていきます。
まずは、Pun Turn Managerを使う場合、ルームホストのシーンを同期しないのでAutomaticallySyncSceneをfalseにします。
ルームに入ったら、Pun Turn Managerをセットアップするようにします。
また、TurnManagerListenerを、ゲームを管理するスクリプト(現在のOnlineGamaManager.cs)に設定し、Pun Turn Managerからのコールバックを受け取れるようにします。
void Awake() { PhotonNetwork.AutomaticallySyncScene = false; } void Start() { SetupTurnManager(); } void SetupTurnManager() { punTurnManager = GetComponent<PunTurnManager>(); punTurnManager.enabled = true; punTurnManager.TurnManagerListener = this; }
プレイヤーの入室を検出し、ゲームスタートを管理する関数
OnlineGameManagerは、MonoBahaviorPunCallbacksを継承しているので、
ルームへプレイヤーが入室した場合、OnPlayerEnterdRoom関数を走らせることができます。
その処理内容として、プレイヤーが既定の人数(例えば2人)に達したら、
ゲームをスタートできるようにします。
(自動でスタートさせたり、スタートボタンを表示させたり)
PunTurnManagerでターン制ゲームをスタートさせるときは、
punTurnManager.BeginTurn()を呼びます。
public override void OnPlayerEnteredRoom(Player newPlayer) { Debug.Log("OnPlayerEnteredRoom: " + newPlayer.NickName); if (PhotonNetwork.CurrentRoom.PlayerCount >= 2) { if (PhotonNetwork.IsMasterClient) { punTurnManager.BeginTurn(); } } }
Pun Turn Managerと連携してターンを管理する関数
OnlineGameManager.csはコールバックを受け取るようになっていますが、
コールバックの内容も同スクリプト内で設定できます。
void IPunTurnManagerCallbacks.~で始まる決められた関数があり、
それらの内容に、プレイヤー全員に処理させたいことを書いていきます。
★チェックポイント★
Pun Turn Managerで数えられる1ターンとは、全プレイヤーの手番が含めたターンを意味します。
全プレイヤーが手番を終えてはじめて、2ターン目に移ります。
それでは各コールバックの関数や書くべき内容を紹介します。
IPunTurnManagerCallbacks.OnTurnBegins(int turn)
void IPunTurnManagerCallbacks.OnTurnBegins(int turn) { if (PhotonNetwork.IsMasterClient) { this.BeginMyTurn(); } }
OnTurnBegins()は、毎ターンの開始時に呼ばれる関数です。
前述のBeginTurn()でターン制ゲームが始まるので、当然この関数が、まず読まれます。
オリジナルでBeginMyTurn()を作っていますが、先手の操作が有効になるようにboolを切り替えるといった処理を行わせると良いでしょう。
void IPunTurnManagerCallbacks.OnPlayerMove(Photon.Realtime.Player player, int turn, object move)
void IPunTurnManagerCallbacks.OnPlayerMove(Photon.Realtime.Player player, int turn, object move) { this.ShareMove(move); }
この関数はプレイヤーが何か操作し、それをゲームの場に反映させるときに読まれるモノです。
つまり、プレイの同期処理と言えます。
OnlineGameManager内で
_punTurnManager.SendMove(move, false);といった
SendMove関数を呼ぶことで、このOnPlayerMove関数が行われます。
このとき、SendMoveの引数が重要で、
第1引数はプレイヤーの操作で反映させたい情報object型を、
第2引数は現在のプレイヤーの手番が終わりかどうかのbool型」となっています。
これがfalseで送信されるときは、プレイヤーの手番はまだ終わりません。
プレイヤーの手番を終わらせる場合、SendMove(null, true)の状態で呼ぶようにしましょう*。
SendMove(move, true)というように、何かしらの情報(move)と共に、trueを返すのは、初心の内は避けるのが無難です。相手に手番を回したり、操作できる権限を渡すコードを
どのように入れるかによりますが、
誤ったタイミングで手番を終えてしまうと、
情報が反映されなかったり、手番が飛んでしまったりします^^;
ShareMoveはオリジナルに設けた関数で、この関数にプレイヤー全体に反映させたい内容を書きます。
void IPunTurnManagerCallbacks.OnPlayerFinished(Photon.Realtime.Player player, int turn, object move)
void IPunTurnManagerCallbacks.OnPlayerFinished(Photon.Realtime.Player player, int turn, object move) { if (!PhotonNetwork.IsMasterClient && PhotonNetwork.LocalPlayer.ActorNumber == player.ActorNumber + 1) { this.BeginMyTurn(); } }
OnPlayerFinished関数はプレイヤーの手番が終わった時に呼ばれます。
上のif文のように、ルームホスト(先手)でない、
なおかつ、この関数を呼んだプレイヤーの次の人が、
BeginMyTurn()を読むことができ、自分がプレイできるようにすると良いでしょう。
void IPunTurnManagerCallbacks.OnTurnCompleted(int turn)
void IPunTurnManagerCallbacks.OnTurnCompleted(int turn) { punTurnManager.BeginTurn(); }
OnTurnCompleted関数は全プレイヤーの手番が終わったら、呼ばれる関数です。
ここでPunTurnManagerのBeginTurn()を呼ぶことで、新たなターンが始まります。
最初のコールバックOnTurnBegins関数から再びゲームが進行していくことになります。
void IPunTurnManagerCallbacks.OnTurnTimeEnds(int turn)
void IPunTurnManagerCallbacks.OnTurnTimeEnds(int turn) { punTurnManager.BeginTurn(); }
ターンには時間の制約があります。
それはPunTurnManagerのInspector上から、Turn Durationとして値を入力し、設定できます。
ターンの時間が終わると、新たなターンが先手から始まります。
このままでは先手が時間をかけた分だけ、次の後手の人に残された時間が少なくなり不公平な感じになってしまいます。
別途自分で手番に与えられる時間を設け、
それが0になると、自動的にSendMove(null, true)で手番終了にさせる処理を追加したほうがよいでしょう。
ゲームの勝敗
上記の決まったコールバックを使って、ターンを回していきますが、
ゲームの決着は、自分で実装します。
例えば、プレイヤーの操作および手番が終わると、
OnPlayerFinished()が呼ばれます。
この中身は、次の手番の人にBeginMyturn()を読ませるようになっています。
ここで読ませる前に、
「勝敗がついていなかったら」という条件をつけるのです。
適当なのは、bool関数です。
たとえば
if(!gameSet){ }といいうif文を
OnPlayerFinished()内に書き、
OnlineGameManager内にbool gameSet()という戻り値のある関数を作ります。
gameSetというbool関数には、
勝敗がついているかどうかを調べる処理を書きます。
勝敗がついたなら、gameSetへの戻り値をtrueにして、
if文を読ませないようにします。
次のプレイヤーに手番は移らず、ターンゲームは止まる仕組みです。
また、
gameSet()で勝敗がついたら、
ゲームが終了する処理(「〜の勝ち!」を表示するなど)を走らせましょう!
というわけで
PunTurnManagerを使って、オンライン対戦ゲームを作る方法でした。
実際の「対戦相手のマッチングからゲーム終了」までの
一連の実装方法はこちらで解説してます↓