最新のUnity Gaming Serviceのロビー機能を使って、自身のゲーム内コミュニティをどう構築するかを学びたいですか?今回の記事では、独自のロビーを作成し、他のプレイヤーがそのロビーに参加するプロセスをステップバイステップで解説します。ゲームの体験をより深く、より社会的なものに変える方法を一緒に見ていきましょう。
今回の流れ
ロビー機能はたくさんの機能を有しています。今回はチュートリアルとしてロビーに参加するまでの手順をメインに進めます。エラーハンドリングや、更新に便利なコールバックなどに関しては別の機会に。
- ロビーの作成
- ロビーリストの表示
- ロビーへの参加
ロビーの作成
ロビーの作成では、作成するだけではなくその維持を行う処理も作成します。作成時にはオプションを指定することも可能です。今回の実装では比較的軽めの実装を行いたいと思います。
Lobbyパッケージを追加
ロビー機能を使うには、パッケージが必要になります。パッケージマネージャーを開き、Unity RegistryからLobbyパッケージをインストールしてください。
プロジェクトIDを連携する
Edit>Project SettingsからServiceを選択します。初期状態ではプロジェクトの紐づけが出来ていないと思いますので、ロビーを適応するプロジェクトを選択してください。
ダッシュボードからLobbyを有効にする
連携を行ったProject Settings>Serviceページにあるリンクからダッシュボードへ移動します。
ダッシュボードから左上のGaming Servicesに戻るから設定ができるページへ移動。
未設定の場合は左側のMultiplayer>Lobbyを選択して[Get Started]ボタンを押してください。その後は導入方法の説明をしてくれますが、読み飛ばしてもOK(後で見れます)。
これでLobby機能を利用することができるようになります。
Gaming Serivceのページではプロジェクト名があっていることも確認してくださいね
LobbyManagerクラス作成と初期化処理
今回はLobbyManagerクラスを主軸に作業を進めます。まずはプレイヤー名をランダムにした状態でUnityServiceの初期化を行い、匿名ログインが成功したらログを表示するものを作りましょう。
using UnityEngine;
using Unity.Services.Core;
using Unity.Services.Authentication;
public class LobbyManager : MonoBehaviour
{
private string playerName;
private async void Start()
{
playerName = "PlayerName" + Random.Range(0, 1000).ToString();
InitializationOptions initializationOptions = new InitializationOptions();
initializationOptions.SetProfile(playerName);
await UnityServices.InitializeAsync(initializationOptions);
AuthenticationService.Instance.SignedIn += () =>
{
Debug.Log("Signed in:" + AuthenticationService.Instance.PlayerId);
};
await AuthenticationService.Instance.SignInAnonymouslyAsync();
}
}
スクリプト貼り付けて、ゲーム動かしてログが表示されたら成功!
プレイヤー名をランダムにしているのは、このあとのチェックのためです。実際にはプレイヤーごとのお名前を入れてくださいね。
ロビー作成のプログラム
ロビーを作成するメソッドを作成します。スクリプトはLobbyManagerに追記する形で更新を行ってください。
- ロビーの名前はTestLobby
- 参加できるプレイヤーの最大人数は4人
- プライベート(非公開)ではない
- 自分のプレイヤー情報を追加
- PlayerName:Startメソッドで作成したプレイヤー名を利用
- 作ったロビーはメンバー変数として保持
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
public class LobbyManager : MonoBehaviour
{
private Lobby hostLobby;
public async void CreateLobby()
{
try
{
string lobbyName = "TestLobby";
int maxPlayers = 4;
CreateLobbyOptions createLobbyOptions = new CreateLobbyOptions(){
IsPrivate = false,
Player = new Player(AuthenticationService.Instance.PlayerId){
Data = new Dictionary<string, PlayerDataObject>(){
{"PlayerName", new PlayerDataObject(PlayerDataObject.VisibilityOptions.Public,playerName)}
}
}
};
hostLobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers ,createLobbyOptions );
Debug.Log("Lobby created:" + hostLobby.LobbyCode);
}
catch (LobbyServiceException e)
{
Debug.Log("Exception:" + e.Message);
}
}
}
実際に作ってみる
UpdateメソッドでCキーを押したらロビーを作成します。次の処理はLobbyManagerクラスの中に作成してください。
private void Update()
{
if(Input.GetKeyDown(KeyCode.C))
{
if(hostLobby == null){
CreateLobby();
}
}
}
スクリプトの更新が出来たら確認してみましょう。
- ゲームを動かす
- サインインのログを確認(成功)
- Cキーを押す
- Lobby createdのログが表示されるのを確認
これでロビーの作成成功です。
ロビーの維持
これで作成自体は完了なのですが、ロビーは作ってから一定時間何もしないと消滅します。ここでは一定時間ごとにハートビートを送ることでロビーの維持を行います。
Updateメソッド内、今回の追加分しか記載していません。ロビーを作成する処理は残しつつ、追記を行ってください。
public class LobbyManager : MonoBehaviour
{
private float heartbeatTimer;
private float heatbeatInterval = 15f;
private void Update()
{
RefreshLobbyHeatbeat();
}
private async void RefreshLobbyHeatbeat(){
if (hostLobby != null)
{
heartbeatTimer += Time.deltaTime;
if (heartbeatTimer > heatbeatInterval)
{
heartbeatTimer = 0.0f;
await LobbyService.Instance.SendHeartbeatPingAsync(hostLobby.Id);
}
}
}
}
ロビーのリスト表示
いつまでもデバッグログ見ながら確認しているわけにもいかないので、UIでロビー状況を把握できるようにしましょう。
ロビーリストのUI作成
UIの詳細は割愛しますが、見た感じはこんな。主に次のパーツに別れます。
- UI全体を管理するウインド
- リスト更新用のボタン
- バナーを表示する部分
- ロビーを表示するバナー
- ロビーの名前
- 人数
- 参加するボタン
ロビーバナーはプレファブ化して、再利用できるようにしておきましょう。
LobbyBannerスクリプト
先にロビーパーツになる、LobbyBannerのスクリプトを作成します。LobbyBannerにさせたいことは
- ロビーのパラメータを渡して初期化する
- ロビーの名前
- 最大人数と現在の人数
- ボタンを押したらロビー参加の意思表示(イベントを発信)
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Unity.Services.Lobbies.Models;
using System;
public class LobbyBanner : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI lobbyNameText;
[SerializeField] private TextMeshProUGUI playerCountText;
[SerializeField] private Button joinButton;
public Lobby myLobby { get; private set; }
public static EventHandler<Lobby> OnJoinLobby;
public void Init(Lobby lobby){
myLobby = lobby;
lobbyNameText.text = lobby.Name;
playerCountText.text = lobby.Players.Count.ToString() + "/" + lobby.MaxPlayers.ToString();
joinButton.onClick.AddListener(() => {
OnJoinLobby?.Invoke(this, myLobby);
Debug.Log("JoinLobby LobbyID="+ myLobby.Id.ToString());
});
}
}
LobbyListViewスクリプト
バナーを並べるロビー一覧表示スクリプトを作りましょう。
- ロビーリストを受け取ってバナーを表示する
- すでにバナーがある場合は削除
- リスト分のバナーを複製
- 各要素をLobbyで初期化(LobbyBanner.Init)
- リフレッシュボタンでロビー一覧の更新の依頼をする
using System.Collections.Generic;
using Unity.Services.Lobbies.Models;
using UnityEngine;
using UnityEngine.UI;
using System;
public class LobbyListView : MonoBehaviour
{
[SerializeField] private GameObject lobbyBannerPrefab;
[SerializeField] private Transform lobbyListContent;
[SerializeField] private Button refreshButton;
public static EventHandler OnRefreshLobbyListRequest;
private void Start()
{
refreshButton.onClick.AddListener(() => {
OnRefreshLobbyListRequest?.Invoke(this, EventArgs.Empty);
});
}
public void Refresh(List<Lobby> lobbyList)
{
foreach (Transform child in lobbyListContent)
{
Destroy(child.gameObject);
}
foreach (var lobby in lobbyList)
{
GameObject lobbyBanner = Instantiate(lobbyBannerPrefab, lobbyListContent);
lobbyBanner.GetComponent<LobbyBanner>().Init(lobby);
}
}
}
LobbyManagerの修正
表示側のスクリプトが出来たので、LobbyManagerを修正して、各表示スクリプトに反映されるようにしましょう。自動的に取得する処理なんかも作れますが、今回は任意のタイミングで表示反映させます。
- メンバ変数にLobbyListViewをもたせる
- リストからリフレッシュの依頼が来たらリストを取得して再表示
public class LobbyManager : MonoBehaviour
{
[SerializeField] private LobbyListView lobbyListView;
private async void Start()
{
LobbyListView.OnRefreshLobbyListRequest += (sender, e) => {
RefreshLobbies();
};
}
private async void RefreshLobbies()
{
try
{
QueryResponse queryResponse = await Lobbies.Instance.QueryLobbiesAsync();
Debug.Log("Lobbies Count:" + queryResponse.Results.Count);
foreach (var lobby in queryResponse.Results)
{
Debug.Log($"Lobby:name {lobby.Name} id {lobby.Id} code {lobby.LobbyCode} maxPlayers {lobby.MaxPlayers}");
}
lobbyListView.Refresh(queryResponse.Results);
}
catch (LobbyServiceException e)
{
Debug.Log("Exception:" + e.Message);
}
}
}
CreateLobbyメソッド内で、ロビー作れた直後にLobbyListView.OnRefreshLobbyListRequestを呼び出してもいいですね。作成直後のロビー状況を自動で取得するように見えますよ。
動作確認
もろもろ設定を行って動作確認してみましょう。
- LobbyBannerをプレファブ化
- ロビー名
- プレイヤー数
- 参加用ボタン
- LobbyListViewのインスペクター設定
- バナーのプレファブ
- バナーを並べるルートのトランスフォーム
- リフレッシュボタン
- LobbyManager
- LobbyListViewをセット
ゲーム動かしたらCキーを押してロビーが作れたのをログで確認。そのあとリフレッシュボタンを押してロビーが一覧に表示されるのを確認してください。
ロビーへの参加
ここまで来たらお膳立ては万端です。ロビーへの参加を行いましょう。
JoinLobby処理
LobbyManagerにロビー参加処理を実装します。LobbyBannerからどのロビーへ参加するかのイベントを受け取れるようにして、いざ参加。
public class LobbyManager : MonoBehaviour
{
private async void Start()
{
LobbyBanner.OnJoinLobby += (sender, lobby) => {
Debug.Log("JoinLobby LobbyID="+ lobby.Id.ToString());
JoinLobby(lobby);
};
}
private async void JoinLobby(Lobby lobby)
{
try
{
JoinLobbyByIdOptions options = new JoinLobbyByIdOptions(){
Player = new Player(AuthenticationService.Instance.PlayerId){
Data = new Dictionary<string, PlayerDataObject>(){
{"PlayerName", new PlayerDataObject(PlayerDataObject.VisibilityOptions.Public,playerName)}
}
},
};
hostLobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobby.Id,options);
Debug.Log("Lobby joined:" + hostLobby.Id);
}
catch (LobbyServiceException e)
{
Debug.Log("Exception:" + e.Message);
}
}
}
動作確認
これでロビーへの参加が可能になります。以下の手順で確認してみてください。
- Build Setting > Build Runでクライアントアプリを起動
- この時Playerセッティングなど事前に行っておくとよい
- Run in background
- Windowed
- Unityエディタの方でもゲームを起動させる
- クライアント側をアクティブにしてCキーでロビー作成
- 作成後はRefreshボタンでロビーが作成されたことを確認
- この確認はクライアント・Unityエディタ両方で可能です
- Unityエディタ側のJoinボタンを押して参加
- 参加後にRefreshボタンを押して参加プレイヤーの人数が増えていることを確認。
この後やるべきこと
今回はかなり一方的にロビーで合流する機能を作成しました。ただしゲームとして利用するにはまだ機能が不足しています。
今後実装するべき機能など
実際にゲームで利用する場合、現在の機能では不十分です。今後追加するべき機能などを少し把握しておきましょう。
- ロビー内の情報表示の充実
- 他の参加者の名前
- ロビーへの情報付与とその表示など
- ロビー内の機能
- ロビーからの退出
- ロビーから他のプレイヤーをキック
- ロビーコードによるロビー参加
- Privateロビー
- ロビー検索のフィルタリング
などなど。一般的なロビーで使われる機能が必要でしょう。他の機能はまた別の機会に紹介したいと思います。
リレーとの連携
そもそもこのロビー機能を紹介することの発端ですが、Relayを使ったマルチプレイ機能を活用したゲームに参加するためのロビーが欲しい、というものでした。
と、言うことで。次回はこの簡易ロビーからリレーを使ったマルチプレイゲームの連携をしてみたいと思います。
コメント
コメント一覧 (4件)
[…] あわせて読みたい Lobby機能を使ってマルチプレイゲームへの参加準備をする[Unigy] 最新のUnity Gaming […]
プログラミングを勉強している中1です。
動画とサイトを拝見しながらマルチプレイのつくりかたについて勉強させていただいています。
動画内で教えていただいた範囲はうまくいっているのですが、
追加で以下のようにロビーから退出するコードを書いてみたところうまくいきません。
Clientで退出しようとするとjoinedlobby.IdでNullReflenceが出てしまいます。
なぜかHostだけは成功しています。
どうすればClient側で正常に退出できるのかをサイトなどで追加で解説していただけると助かります。よろしくお願いいたします。
public async void leavelobby()
{
try{
string playerid = AuthenticationService.Instance.PlayerId;
string lobbyid = joinedlobby.Id;
Debug.Log(playerid);
if(ishost){
await LobbyService.Instance.RemovePlayerAsync(lobbyid, playerid);
}else{
await LobbyService.Instance.RemovePlayerAsync(lobbyid,playerid);
}
joinedlobby = null;
}catch(LobbyServiceException e){
Debug.Log(e);
}
}
joinedlobby変数に、入っているロビーのインスタンスが代入されてないからだと思います。
おそらくメンバ変数にjoinedlobbyを準備していると思うのですが、JoinLobbyメソッドの引数をメンバ変数のjoinedlobbyに代入してあげると解決すると思います。
hostだけうまくいくのは、CreateLobby系の処理で、joinedlobbyにロビー作成時のインスタンスを代入しているからだと推察します。
RemovePlayerAsyncに必要なのはLobbyIDの文字列なので、ロビーに参加したときに参加中のロビーのIDだけ取得しておくだけでも構いません。
ありがとうございます。
参考にさせていただきました。
無事解決することができました。