//tips
//PUN2 ルーム作成パターン
プレイヤー同士のマッチング方法は、ルームの数や種類が固定されたものや自由にルームを作成したり、作成されたルームに参加したりするものがある。
Photonのロビー機能を使い、ルーム選択型のマッチングを考えていく。
マスターサーバーへの接続が成功した後にPhotonNetwork.JoinLobby()でロビーに参加すると、MonoBehaviourPunCallbacksを継承しているスクリプトは、ルームリストの更新をコールバックで受け取ることができるようになる。
コールバックの引数には、変更(追加・更新・削除)があったルームの情報が渡されるので、それを元にUIへ反映させる。
// マスターサーバーへの接続が成功したら、ロビーに参加する
public override void OnConnectedToMaster() {
PhotonNetwork.JoinLobby();
}
ルーム選択リスト(RoomListView)とそのリスト要素(RoomListEntry)を実装する。
ルーム選択リストはスクロールビューで作成する。canvas下に作成したスクロールビューに下記のスクリプトをアタッチ。
using System.Collections.Generic;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(ScrollRect))]
public class RoomListView : MonoBehaviourPunCallbacks
{
[SerializeField]
private RoomListEntry roomListEntryPrefab = default; // RoomListEntryのPrefabの参照
private ScrollRect scrollRect;
private Dictionary<string, RoomListEntry> activeEntries = new Dictionary<string, RoomListEntry>();
private Stack<RoomListEntry> inactiveEntries = new Stack<RoomListEntry>();
private void Awake()
{
scrollRect = GetComponent<ScrollRect>();
}
// ルームリストが更新された時に呼ばれるコールバック
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
foreach (var info in roomList)
{
RoomListEntry entry;
if (activeEntries.TryGetValue(info.Name, out entry))
{
if (!info.RemovedFromList)
{
// リスト要素を更新する
entry.Activate(info);
}
else
{
// リスト要素を削除する
activeEntries.Remove(info.Name);
entry.Deactivate();
inactiveEntries.Push(entry);
}
}
else if (!info.RemovedFromList)
{
// リスト要素を追加する
entry = (inactiveEntries.Count > 0)
? inactiveEntries.Pop().SetAsLastSibling()
: Instantiate(roomListEntryPrefab, scrollRect.content);
entry.Activate(info);
activeEntries.Add(info.Name, entry);
}
}
}
}
そして、別途ボタンを作成し、その下に3つのtext mesh pro uguiを追加する。
ボタンには下記のスクリプトをアタッチする。
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(Button))]
public class RoomListEntry : MonoBehaviour
{
[SerializeField]
private TextMeshProUGUI nameLabel = default;
[SerializeField]
private TextMeshProUGUI messageLabel = default;
[SerializeField]
private TextMeshProUGUI playerCounter = default;
private RectTransform rectTransform;
private Button button;
private string roomName;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
button = GetComponent<Button>();
}
private void Start()
{
// リスト要素がクリックされたら、対応したルーム名のルームに参加する
button.onClick.AddListener(() => PhotonNetwork.JoinRoom(roomName));
}
public void Activate(RoomInfo info)
{
roomName = info.Name;
nameLabel.text = (string)info.CustomProperties["DisplayName"];
messageLabel.text = (string)info.CustomProperties["Message"];
playerCounter.SetText("{0}/{1}", info.PlayerCount, info.MaxPlayers);
// ルームの参加人数が満員でない時だけ、クリックできるようにする
button.interactable = (info.PlayerCount < info.MaxPlayers);
gameObject.SetActive(true);
}
public void Deactivate()
{
gameObject.SetActive(false);
}
public RoomListEntry SetAsLastSibling()
{
rectTransform.SetAsLastSibling();
return this;
}
}
クリックされたらPhotonNetwork.JoinRoom()でルーム選択リストから渡されたルーム名のルームへの参加を試みる処理を行なっている。
作成したLIst要素にLayout Elementコンポーネントを追加し、繰り返し表示されるオブジェクトの最低・最大幅などを設定している。
作成したRoomListEntryはprefab化してヒエラルキーから消去。
繰り返し表示したいオブジェクトをContentの子要素に追加することで一覧表示していく。
Contentの子要素に入れたオブジェクトが自動整列されるよう、Contentにコンポーネントを追加する。追加された子要素を縦方向に整列していくVertical Layout Group コンポーネント、追加された子要素の数に応じて、Contentのサイズを自動調整するContent Size Fitter コンポーネントを加える。
Vertical Layout Groupのpaddingで表示余白を微調整する。
このようにロビーのマッチング場所を作成することができる。
現在はリストの内容をクリックしても部屋に入れないので、リスト項目をクリックしたらルームに移行できるようにしたいので、まずはルームの作成方法を見ていく。
今まではひとつのルームの中の活動しか見てこなかったので、並列して複数のルームが生成されている状態を考えていく。
まずはボタンを押すとルームを作成する仕組みを考える。
下記を参考にし、作成。
using ExitGames.Client.Photon;
using Photon.Pun;
using Photon.Realtime;
using System.Collections.Generic;
using UnityEngine;
public class NetworkManager : MonoBehaviourPunCallbacks
{
/////////////////////////////////////////////////////////////////////////////////////
// Field ////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
[Header("DefaultRoomSettings")]
// 最大人数
[SerializeField] private int maxPlayers = 4;
// 公開・非公開
[SerializeField] private bool isVisible = true;
// 入室の可否
[SerializeField] private bool isOpen = true;
// 部屋名
[SerializeField] private string roomName = "Naki's Room";
// ステージ
[SerializeField] private string stageName = "Stage1";
// 難易度
[SerializeField] private string stageDifficulty = "Easy";
/////////////////////////////////////////////////////////////////////////////////////
// Awake & Start ////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Awake
private void Awake()
{
// シーンの自動同期: 無効
PhotonNetwork.AutomaticallySyncScene = false;
}
// Start is called before the first frame update
private void Start()
{
// Photonに接続
Connect("1.0");
}
/////////////////////////////////////////////////////////////////////////////////////
// Connect //////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Photonに接続する
private void Connect(string gameVersion)
{
if (PhotonNetwork.IsConnected == false)
{
PhotonNetwork.GameVersion = gameVersion;
PhotonNetwork.ConnectUsingSettings();
}
}
// ニックネームを付ける
private void SetMyNickName(string nickName)
{
if (PhotonNetwork.IsConnected)
{
PhotonNetwork.LocalPlayer.NickName = nickName;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// Join Lobby ///////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// ロビーに入る
private void JoinLobby()
{
if (PhotonNetwork.IsConnected)
{
PhotonNetwork.JoinLobby();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// Join Room ////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// 1. 部屋を作成して入室する
public void CreateAndJoinRoom()
{
// ルームオプションの基本設定
RoomOptions roomOptions = new RoomOptions
{
// 部屋の最大人数
MaxPlayers = (byte)maxPlayers,
// 公開
IsVisible = isVisible,
// 入室可
IsOpen = isOpen
};
// ルームオプションにカスタムプロパティを設定
ExitGames.Client.Photon.Hashtable customRoomProperties = new ExitGames.Client.Photon.Hashtable
{
{ "Stage", stageName },
{ "Difficulty", stageDifficulty }
};
roomOptions.CustomRoomProperties = customRoomProperties;
// ロビーに公開するカスタムプロパティを指定
roomOptions.CustomRoomPropertiesForLobby = new string[] { "Stage", "Difficulty" };
// 部屋を作成して入室する
if (PhotonNetwork.InLobby)
{
PhotonNetwork.CreateRoom(roomName, roomOptions);
}
}
// 2. 部屋に入室する (存在しなければ作成して入室する)
public void JoinOrCreateRoom()
{
// ルームオプションの基本設定
RoomOptions roomOptions = new RoomOptions
{
// 部屋の最大人数
MaxPlayers = (byte)maxPlayers,
// 公開
IsVisible = isVisible,
// 入室可
IsOpen = isOpen
};
// ルームオプションにカスタムプロパティを設定
ExitGames.Client.Photon.Hashtable customRoomProperties = new ExitGames.Client.Photon.Hashtable
{
{ "Stage", stageName },
{ "Difficulty", stageDifficulty }
};
roomOptions.CustomRoomProperties = customRoomProperties;
// ロビーに公開するカスタムプロパティを指定
roomOptions.CustomRoomPropertiesForLobby = new string[] { "Stage", "Difficulty" };
// 入室 (存在しなければ部屋を作成して入室する)
if (PhotonNetwork.InLobby)
{
PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, TypedLobby.Default);
}
}
// 3. 特定の部屋に入室する
public void JoinRoom(string targetRoomName)
{
if (PhotonNetwork.InLobby)
{
PhotonNetwork.JoinRoom(targetRoomName);
}
}
// 4. ランダムな部屋に入室する
public void JoinRandomRoom()
{
if (PhotonNetwork.InLobby)
{
PhotonNetwork.JoinRandomRoom();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// Leave Room ///////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// 部屋から退室する
public void LeaveRoom()
{
if (PhotonNetwork.InRoom)
{
// 退室
PhotonNetwork.LeaveRoom();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// Pun Callbacks ////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
// Photonに接続した時
public override void OnConnected()
{
Debug.Log("OnConnected");
// ニックネームを付ける
SetMyNickName("Naki");
}
// Photonから切断された時
public override void OnDisconnected(DisconnectCause cause)
{
Debug.Log("OnDisconnected");
}
// マスターサーバーに接続した時
public override void OnConnectedToMaster()
{
Debug.Log("OnConnectedToMaster");
// ロビーに入る
JoinLobby();
}
// ロビーに入った時
public override void OnJoinedLobby()
{
Debug.Log("OnJoinedLobby");
}
// ロビーから出た時
public override void OnLeftLobby()
{
Debug.Log("OnLeftLobby");
}
// 部屋を作成した時
public override void OnCreatedRoom()
{
Debug.Log("OnCreatedRoom");
}
// 部屋の作成に失敗した時
public override void OnCreateRoomFailed(short returnCode, string message)
{
Debug.Log("OnCreateRoomFailed");
}
// 部屋に入室した時
public override void OnJoinedRoom()
{
Debug.Log("OnJoinedRoom");
// 部屋の情報を表示
if (PhotonNetwork.InRoom)
{
Debug.Log("RoomName: " + PhotonNetwork.CurrentRoom.Name);
Debug.Log("HostName: " + PhotonNetwork.MasterClient.NickName);
Debug.Log("Stage: " + PhotonNetwork.CurrentRoom.CustomProperties["Stage"] as string);
Debug.Log("Difficulty: " + PhotonNetwork.CurrentRoom.CustomProperties["Difficulty"] as string);
Debug.Log("Slots: " + PhotonNetwork.CurrentRoom.PlayerCount + " / " + PhotonNetwork.CurrentRoom.MaxPlayers);
}
}
// 特定の部屋への入室に失敗した時
public override void OnJoinRoomFailed(short returnCode, string message)
{
Debug.Log("OnJoinRoomFailed");
}
// ランダムな部屋への入室に失敗した時
public override void OnJoinRandomFailed(short returnCode, string message)
{
Debug.Log("OnJoinRandomFailed");
}
// 部屋から退室した時
public override void OnLeftRoom()
{
Debug.Log("OnLeftRoom");
}
// 他のプレイヤーが入室してきた時
public override void OnPlayerEnteredRoom(Player newPlayer)
{
Debug.Log("OnPlayerEnteredRoom");
}
// 他のプレイヤーが退室した時
public override void OnPlayerLeftRoom(Player otherPlayer)
{
Debug.Log("OnPlayerLeftRoom");
}
// マスタークライアントが変わった時
public override void OnMasterClientSwitched(Player newMasterClient)
{
Debug.Log("OnMasterClientSwitched");
}
// ロビーに更新があった時
public override void OnLobbyStatisticsUpdate(List<TypedLobbyInfo> lobbyStatistics)
{
Debug.Log("OnLobbyStatisticsUpdate");
}
// ルームリストに更新があった時
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
Debug.Log("OnRoomListUpdate");
}
// ルームプロパティが更新された時
public override void OnRoomPropertiesUpdate(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
{
Debug.Log("OnRoomPropertiesUpdate");
}
// プレイヤープロパティが更新された時
public override void OnPlayerPropertiesUpdate(Player target, ExitGames.Client.Photon.Hashtable changedProps)
{
Debug.Log("OnPlayerPropertiesUpdate");
}
// フレンドリストに更新があった時
public override void OnFriendListUpdate(List<FriendInfo> friendList)
{
Debug.Log("OnFriendListUpdate");
}
// 地域リストを受け取った時
public override void OnRegionListReceived(RegionHandler regionHandler)
{
Debug.Log("OnRegionListReceived");
}
// WebRpcのレスポンスがあった時
public override void OnWebRpcResponse(OperationResponse response)
{
Debug.Log("OnWebRpcResponse");
}
// カスタム認証のレスポンスがあった時
public override void OnCustomAuthenticationResponse(Dictionary<string, object> data)
{
Debug.Log("OnCustomAuthenticationResponse");
}
// カスタム認証が失敗した時
public override void OnCustomAuthenticationFailed(string debugMessage)
{
Debug.Log("OnCustomAuthenticationFailed");
}
}
この仕組みをスクロールビューにも組み込んでいく。
まずはクリックしたルームに入れるようにし、その後、スクロールビューにAddボタンを追加し、押したら新規ルームが作成できるようにする。
現在roomlistentryクラスでroomへの参加は下記コードで表されているが、そのルーム名のルームがないのが問題。
private void Start()
{
// リスト要素がクリックされたら、対応したルーム名のルームに参加する
button.onClick.AddListener(() => PhotonNetwork.JoinRoom(roomName));
}
ロビーにRoomの名前が記載されている時点でルーム作成されている必要があるので、roomlistviewの初期段階でひとつサンプルroomを作成するようにする。
ここでロビーからルームへの移行の仕方がわからず検証中。コンソールからはルームに接続できていることは確認できているので、どう状態を移行させるかが問題。