ディフェンダーに似たオンライン対戦ゲームをつくる(2)の続きです。今回はクライアントとサーバー間およびサーバーからクライアント間の通信を可能にするためのHubクラスを定義します。
1 2 3 4 5 6 7 8 |
using Microsoft.AspNetCore.SignalR; namespace Defender { public class GameHub : Hub { } } |
以降は名前空間部分を省略して書きます。
1 2 3 |
public class GameHub : Hub { } |
サーバーに接続時の処理
ユーザーがサーバーに接続したときに呼び出される処理を示します。
辞書にContext.ConnectionIdを追加し、クライアントサイドに接続に成功した旨を通知します。また初回のみタイマーの初期化とGameオブジェクトにイベントハンドラを追加する処理をおこないます。またタイマーが停止している場合はスタートさせます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class GameHub : Hub { static Game _game = new Game(); static Dictionary<string, IClientProxy> _clientProxies = new Dictionary<string, IClientProxy>(); static System.Timers.Timer _timer = new System.Timers.Timer(); static bool _isFirst = true; public override async Task OnConnectedAsync() { await base.OnConnectedAsync(); // 辞書にContext.ConnectionIdを追加 _clientProxies.Add(Context.ConnectionId, Clients.Caller); // 初回のみ // タイマーの初期化、イベントハンドラの追加 if (_isFirst) { _isFirst = false; _timer.Interval = 1000 / 60; _timer.Elapsed += Timer_Elapsed; _game.HitEnemy += HitEnemy; _game.PlayerDead += PlayerDead; _game.PlayerGameOvered += PlayerGameOvered; } // タイマーが停止している場合はスタートさせる if(!_timer.Enabled) _timer.Start(); // クライアントサイドに接続に成功した旨を通知する await Clients.Caller.SendAsync("SendToClientConnectionSuccessful", Context.ConnectionId); } } |
通信切断時の処理
接続が切断されたときに呼び出される処理を示します。
辞書からContext.ConnectionIdを削除し、プレイ中の場合は対応するPlayerオブジェクトのプロパティを変更しなければならないのでGame.UninitPlayerメソッドを呼び出します。接続数が0になったらタイマーを停止させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class GameHub : Hub { public override async Task OnDisconnectedAsync(Exception? exception) { await base.OnDisconnectedAsync(exception); // 辞書からContext.ConnectionIdを削除 _clientProxies.Remove(Context.ConnectionId); _game.UninitPlayer(Context.ConnectionId); // 接続数が0になったらタイマーを停止 if (_clientProxies.Count == 0) _timer.Stop(); } } |
ゲーム開始時の処理
ゲーム開始時の処理を示します。クライアントサイドからプレイヤー名が送られてくるので、これをPlayerオブジェクトのプロパティにセットするためにGame.InitPlayerメソッドを呼び出します。処理が成功した場合は”SendToClientGameStartSuccessful”を、失敗した場合は”SendToClientGameStartFailure”をクライアントサイドに送信します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class GameHub : Hub { public void GameStart(string name) { // 長大なデータが送りつけられるかもしれないので対策 if (name.Length > 32) return; if(_game.InitPlayer(name, Context.ConnectionId)) _ = Clients.Caller.SendAsync("SendToClientGameStartSuccessful"); else _ = Clients.Caller.SendAsync("SendToClientGameStartFailure"); } } |
自機操作時の処理
ユーザーが自機を操作しようとしたときにおこなわれる処理を示します。左右に移動しようとしたときはSetVelocityXメソッドが、加速減速をしようとしたときはAddVelocityYメソッドが、弾丸を発射しようとしたときはShotメソッドが呼び出されます。速度の絶対値が増加した場合はクライアントサイドに”SendToClientSpeedUp”を、弾丸発射の処理が行なわれたときは”SendToClientShot”をクライアントサイドに送信します(効果音を鳴らしたい)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class GameHub : Hub { public void SetVelocityX(string direct) { _game.PlayerSetVelocityX(Context.ConnectionId, direct); } public void AddVelocityY(string direct) { if (_game.PlayerAddVelocityY(Context.ConnectionId, direct)) _ = Clients.Caller.SendAsync("SendToClientSpeedUp"); } public void Shot() { if (_game.PlayerShot(Context.ConnectionId)) _ = Clients.Caller.SendAsync("SendToClientShot"); } } |
更新処理
更新とクライアントサイドに描画処理用のデータを送信する処理を示します。
Game.TryUpdateメソッドを実行したあと、Game.TryGetSendDataメソッドで取得した文字列をクライアントサイドに送信します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class GameHub : Hub { private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { try { _game.TryUpdate(); string str = _game.TryGetSendData(); if (str == "") return; foreach (IClientProxy client in _clientProxies.Values) _ = client.SendAsync("Update", str); } catch { } } } |
イベントの送信
クライアントサイドに敵を撃ち落としたとき、敵に撃ち落とされたとき、ゲームオーバーになったときにイベントを送信する処理を示します(効果音を鳴らしたい)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class GameHub : Hub { private void HitEnemy(object? sender, EventArgs e) { string connectionId = ((Game.MyEventArgs)e).ConnectionId; if (_clientProxies.ContainsKey(connectionId)) _ = _clientProxies[connectionId].SendAsync("SendToClientHitEnemy"); } private void PlayerDead(object? sender, EventArgs e) { string connectionId = ((Game.MyEventArgs)e).ConnectionId; if (_clientProxies.ContainsKey(connectionId)) _ = _clientProxies[connectionId].SendAsync("SendToClientPlayerDead"); } private void PlayerGameOvered(object? sender, EventArgs e) { string connectionId = ((Game.MyEventArgs)e).ConnectionId; if (_clientProxies.ContainsKey(connectionId)) _ = _clientProxies[connectionId].SendAsync("SendToClientPlayerGameOvered"); } } |