Unity のロビー、マッチング、ランキング
PusherClient for .NET で、プレゼンスのロビー、「マッチング成立」、キャッシュしたランキング。
ゲームのメッセージすべてにゲームサーバーが要るわけではありません。ロビー、「マッチング成立」、フレンドのオンライン、チャット、ランキング、ライブ運営のお知らせはイベントです。小さく、ときどき、気にしているプレイヤーにだけ届けばいい。Hibira はこれらを WebSocket で運び、クライアントはオープンソースの .NET ライブラリ PusherClient。バックエンドは HTTP 呼び出し 1 回で配信でき、自前のソケットサーバーは要りません。毎フレーム変わる状態は、これまでどおりゲーム用のネットワーク(Netcode for GameObjects、Photon、Mirror、自作など)に任せてください。
プレイヤーにとっての価値
- ロビーに誰がいて誰が準備完了か、更新しなくても見えます。
- マッチメイカーが決めた瞬間に「マッチング成立」。ポーリングのくるくるを待つ必要はありません。
- ランキングとイベントのカウントダウンは、画面を開いた瞬間から最新です。
使う機能
- Unity で使う PusherClient(pusher-websocket-dotnet)。
- ロビーやパーティーのためのプレゼンスチャンネル(
presence-)。 - プレイヤー同士の「準備完了」などの小さな合図に使うクライアントイベント(
client-*)。 - 1 人のプレイヤーに届けるプライベートチャンネル(
private-)。 - 最新のランキングを開いたプレイヤー全員に渡すキャッシュチャンネル(
cache-)。
ステップ 1:接続は 1 回、イベントはメインスレッドで
NuGet から PusherClient パッケージを追加し(たとえば NuGetForUnity で)、このコンポーネントを最初のシーンの GameObject に付けます。
using System;
using System.Collections.Concurrent;
using System.Net.Http.Headers;
using PusherClient;
using UnityEngine;
public class Realtime : MonoBehaviour
{
public static Realtime Instance { get; private set; }
public Pusher Client { get; private set; }
// PusherClient raises events on a background thread; Unity's API only works on the main thread.
readonly ConcurrentQueue<Action> mainThread = new ConcurrentQueue<Action>();
public void Post(Action action) => mainThread.Enqueue(action);
async void Awake()
{
Instance = this;
DontDestroyOnLoad(gameObject);
Client = new Pusher("YOUR_APP_KEY", new PusherOptions
{
Host = "ws.hibira.io",
Encrypted = true,
ChannelAuthorizer = new HttpChannelAuthorizer("https://api.example.com/pusher/auth")
{
AuthenticationHeader = new AuthenticationHeaderValue("Bearer", Session.Token),
},
});
Client.Error += (sender, error) => Debug.LogWarning(error);
await Client.ConnectAsync();
}
void Update()
{
while (mainThread.TryDequeue(out var action)) action();
}
async void OnApplicationQuit() => await Client.DisconnectAsync();
}Cluster ではなく Host を設定してください。cluster を設定するとライブラリが pusher.com に向き直ります。アプリのシークレットはバックエンドに置き、認可エンドポイントでプライベート/プレゼンスチャンネルに署名します。
ステップ 2:プレゼンスと「準備完了」のあるロビー
[]
public class Player { public string name; public int level; }
var rt = Realtime.Instance;
var lobby = await rt.Client.SubscribePresenceAsync<Player>("presence-lobby-" + lobbyId);
foreach (var m in lobby.GetMembers()) rt.Post(() => LobbyUI.Add(m.Key, m.Value));
lobby.MemberAdded += (sender, m) => rt.Post(() => LobbyUI.Add(m.Key, m.Value));
lobby.MemberRemoved += (sender, m) => rt.Post(() => LobbyUI.Remove(m.Key));
// "Ready" goes straight to the other players: turn on Allow client events in the app settings
lobby.Bind("client-ready", (PusherEvent e) => rt.Post(() => LobbyUI.SetReady(e.UserId, true)));
readyButton.onClick.AddListener(() => lobby.Trigger("client-ready", new { ready = true }));誰がロビーに入れるかは認可エンドポイントが決め、各プレイヤーの user_id と user_info(ここでは名前とレベル)を返します。これはメンバー全員に届きます。
ステップ 3:マッチング成立とランキングをバックエンドから
// When the matchmaker pairs players
await pusher.trigger(players.map((p) => 'private-player-' + p.id), 'match-found', { matchId, server: address });
// The latest standings, kept for players who open the screen later
await pusher.trigger('cache-leaderboard-weekly', 'update', { top: await leaderboard.top(20) });var rt = Realtime.Instance;
var me = await rt.Client.SubscribeAsync("private-player-" + Session.PlayerId);
me.Bind("match-found", (PusherEvent e) => rt.Post(() => Matchmaking.Join(JsonUtility.FromJson<Match>(e.Data))));
// Listen before subscribing, so the cached standings sent right after the subscription are not missed
rt.Client.BindAll((string eventName, PusherEvent e) =>
{
if (e.ChannelName == "cache-leaderboard-weekly" && eventName == "update")
rt.Post(() => LeaderboardUI.Render(JsonUtility.FromJson<Standings>(e.Data)));
});
await rt.Client.SubscribeAsync("cache-leaderboard-weekly");1 回の trigger で最大 100 チャンネルに届き、何人が購読していてもチャンネルごとに 1 メッセージとして数えます。バックエンド側の書き方は Node.js ガイドと .NET ガイドにあります。
ヒント
- WebGL ビルドでは C# からソケットを開けません。その場合はページで pusher-js を読み込み、同じホストとチャンネルで
.jslibプラグインから呼び出します。 - モバイルではゲームが一時停止したら(
OnApplicationPause)切断し、再開時に接続し直します。ゲームを閉じている間のメッセージにはプッシュ通知を使いましょう。 - プレゼンスチャンネルのメンバーは最大 100 人で、ロビーやパーティーに向いています。ゲーム全体の「オンライン人数」は大規模な視聴者を参照してください。
- クライアントイベントは接続ごとにレート制限があり、1 件ごとに 1 メッセージとして数えます。毎フレームではなく、状態が変わったときに送りましょう。