//tips
//PUN2ルームの同期設定
カスタムプロパティはルームに設定することもでき、ルームに参加しているプレイヤー全員と、任意の値を同期させたい場合に活用する。
ルームのカスタムプロパティを取得・設定するクラス(GameRoomProperty)を作成。
using Photon.Realtime;
using Hashtable = ExitGames.Client.Photon.Hashtable;
public static class GameRoomProperty
{
private const string KeyDisplayName = "DisplayName"; // 表示用ルーム名のキーの文字列
private const string KeyStartTime = "StartTime"; // ゲーム開始時刻のキーの文字列
private static Hashtable hashtable = new Hashtable();
// ルームの初期設定オブジェクトを作成する
public static RoomOptions CreateRoomOptions(string displayName)
{
return new RoomOptions()
{
// カスタムプロパティの初期設定
CustomRoomProperties = new Hashtable() {
{ KeyDisplayName, displayName }
},
// ロビーからカスタムプロパティを取得できるようにする
CustomRoomPropertiesForLobby = new string[] {
KeyDisplayName
}
};
}
// 表示用ルーム名を取得する
public static string GetDisplayName(this Room room)
{
return (string)room.CustomProperties[KeyDisplayName];
}
// ゲーム開始時刻が設定されているか調べる
public static bool HasStartTime(this Room room)
{
return room.CustomProperties.ContainsKey(KeyStartTime);
}
// ゲーム開始時刻があれば取得する
public static bool TryGetStartTime(this Room room, out int timestamp)
{
if (room.CustomProperties[KeyStartTime] is int value)
{
timestamp = value;
return true;
}
timestamp = 0;
return false;
}
// ゲーム開始時刻を設定する
public static void SetStartTime(this Room room, int timestamp)
{
hashtable[KeyStartTime] = timestamp;
room.SetCustomProperties(hashtable);
hashtable.Clear();
}
}
シーンにテキストを追加して、ゲームの経過時間を表示できるようにしてみる。
GameRoomHUDを作成。
using Photon.Pun;
using TMPro;
using UnityEngine;
[RequireComponent(typeof(TextMeshProUGUI))]
public class GameRoomHUD : MonoBehaviour
{
private TextMeshProUGUI timeLabel;
private void Awake()
{
timeLabel = GetComponent<TextMeshProUGUI>();
}
private void Update()
{
// まだルームに参加していない時は更新しない
if (!PhotonNetwork.InRoom) { return; }
// まだゲーム開始時刻が設定されていない時は更新しない
if (!PhotonNetwork.CurrentRoom.TryGetStartTime(out int timestamp)) { return; }
// ゲーム開始時刻からの経過時間を求めて、テキスト表示する
float elapsedTime = Mathf.Max(unchecked(PhotonNetwork.ServerTimestamp - timestamp) / 1000f);
timeLabel.text = elapsedTime.ToString("f2"); // 小数点以下2桁表示
}
}
これをcanvasの子要素に設定しているtext mesh pro UGUIを含むHUDにアタッチするもテキストに反映されなかった。
SampleSceneスクリプトのマッチングが成功した時に呼ばれるコールバックに下記のスクリプトを追加することで無事動いた。
// 現在のサーバー時刻を、ゲームの開始時刻に設定する
if (PhotonNetwork.IsMasterClient && !PhotonNetwork.CurrentRoom.HasStartTime()) {
PhotonNetwork.CurrentRoom.SetStartTime(PhotonNetwork.ServerTimestamp);
}
Photonがデフォルトでサポートしていないデータ型でも、データ型からバイト列へのシリアライズ処理とバイト列からデータ型へのデシリアライズ処理を実装して登録することで、通信コストを削減する工夫を行うことができる。
例えば、Color型をカスタムタイプとして登録すると、Color型のRGBA値はfloat型なので、そのままバイト列に書き込むためには、合計で16バイト必要となるが、Color32型のRGBA値はbyte型で合計で4バイトで済むため、型変換することによって通信量を削減できる。
オンラインゲームでは様々なタイミングでプレイヤーが途中参加したり途中退出したりする可能性があるので、同じルームに参加しているプレイヤーを適切に管理するための機能を確認する。
MonoBehaviourPunCallbacksを継承しているスクリプト(public class SampleScene : MonoBehaviourPunCallbacksが該当)は、自身が参加しているルームに他プレイヤーが参加・退出した時のコールバックを受け取ることができる。他プレイヤーの情報は、コールバックの引数として渡される。
ルームに参加しているプレイヤーの情報は、PhotonNetworkの配列にアクセスすることで取得できる。
Player[] allPlayers = PhotonNetwork.PlayerList; // プレイヤーの配列(自身を含む)
Player[] otherPlayers = PhotonNetwork.PlayerListOthers; // プレイヤーの配列(自身を含まない)
PhotonView[] photonViews = PhotonNetwork.PhotonViews; // ネットワークオブジェクトの配列
ただ、これらは頻繁にアクセスする際はパフォーマンス上の問題が発生する可能性があるので、ネットワークオブジェクトのリストを管理する独自クラスGamePlayerManagerを作成するのが推奨されている。
新たに空オブジェクトを作成し、そこにスクリプトをアタッチする。
using System.Collections.Generic;
using UnityEngine;
public class GamePlayerManager : MonoBehaviour
{
private List<GamePlayer> playerList = new List<GamePlayer>();
public GamePlayer this[int index] => playerList[index];
public int Count => playerList.Count;
private void OnTransformChildrenChanged()
{
// 子要素が変わったら、ネットワークオブジェクトのリストを更新する
playerList.Clear();
foreach (Transform child in transform)
{
playerList.Add(child.GetComponent<GamePlayer>());
}
}
}
その上で、各GamePlayerが、生成されたらGamePlayerManagerの子要素になるように設定する。GamePlayerスクリプトを一部編集した。
public Player Owner => photonView.Owner;
private void Awake()
{
projectileManager = GameObject.FindWithTag("ProjectileManager").GetComponent<ProjectileManager>();
spriteRenderer = GetComponent<Renderer>();
var gamePlayerManager = GameObject.FindWithTag("GamePlayerManager").GetComponent<GamePlayerManager>();
transform.SetParent(gamePlayerManager.transform);
}
GamePlayerManager側ではOnTransformChildrenChanged()が呼ばれるので、そこでネットワークオブジェクトリストの更新を行う。
ここまでは、同じルームに参加していることを前提に進めていたが、
・ルームの数や種類があらかじめ決まっている場合
この際には、PhotonNetwork.JoinOrCreateRoom()を使用すると便利で、これにより、指定した名前のルームが既に作成されていたら参加し、作成されていなかったら作成してから参加することができる。
PhotonNetwork.JoinOrCreateRoom("Room1", new RoomOptions() { MaxPlayers = 4 }, TypedLobby.Default);
・各プレイヤーが自由にルームを作成したり、作成されたルームに参加したりする場合
などのパターンがあるので、そちらも考慮していく。
//Text mesh proのボヤけ解消
Text mesh pro UGUIは普通のテキストよりボヤけにくいと言われているが初期のLiberationSansフォントの36サイズでscale-1のオブジェクトを作成すると若干ぼやけて見える。
これを解消するためにmaterial presetをdrop shadowに切り替えてみると黒いぼやけがとれ、くっきりと指定したvertex colorが現れる。