前回の キー操作とタイマーの処理 ランキングを偽装させないゲームをつくる準備(2) ではキー操作とタイマーをつかった処理の実験をしましたが、今回はキー操作とタイマー処理でキャラクタを移動させます。これができるようになるとASP.NET Coreをつかったゲームが作れるようになるのではないでしょうか?

以下のサンプルページにアクセスしてみると、方向キーをおすとキャラクタが移動しますが、交差点でないとキーを押しても方向転換することができないことがわかります。

Gameクラスを定義する

まずGameTest1という名前空間を定義してそこにGameクラスを定義します。

以下、記事内では名前空間は省略して書きます。理由はインデントが深くなるからです。

Direct列挙体

まずゲームを作ろうとしているのでキャラクタが移動する方向を管理するためにDirectという列挙体を定義します。

初期化の処理

ではGameクラスのフィールド変数とコンストラクタを示します。

キー操作にかんする処理

キーが押されたらIs~KeyDownフラグをセットして離されたらクリアします。

キャラクタの方向転換と移動

CanPlayerTurnAroundメソッドは方向転換できるかどうかを判定するためのものです。X座標とY座標が両方とも20の倍数になっているときだけ方向転換ができるものとします(いまは実験なのでテキトーです)。

タイマーイベントが発生したらキーが押されているかをチェックし、その位置で方向転換できるかを調べます。できる場合はPlayerDirectを変更します。

そのあとPlayerDirectに格納されている値によってX座標とY座標を変更します。またX座標とY座標が0より小さくなったり80よりも大きくなったら反対方向にワープさせます。

次にSignalRChatを使った処理を考えます。SignalRChat.Hubs名前空間のなかにMoveTest1クラスを定義します。

SignalRChat関連の処理

以降はインデントを減らすために名前空間は省略して書きます。

静的フィールド変数としてTimer、ClientProxyMap、TimerElapsedHandlers、Gamesを定義します。

接続に成功したらそれを伝えるためにクライアントサイドのReceiveMessage関数を呼び出します。SendAsyncメソッドで送るデータは「接続成功」という文字列とConnectionId、現在時刻です。

そのあとClientProxyMapにContext.ConnectionIdをキーにしてClients.Callerを追加します。Clients.Callerをフィールド変数として保存しても意味はありません。静的なフィールド変数として保存して辞書のなかから取得できるようにしておきます。

そして60ミリ秒ごとにイベントハンドラTimer_Elapsedを呼び出せるようにしておきます。切断されたときに追加したイベントハンドラTimer_Elapsedを削除できるようにしなければなりません。これも静的なフィールド変数として保存して辞書のなかから取得できるようにしておきます。最後にタイマーをスタートさせます。

接続時の処理

切断時の処理

切断されたときの処理を示します。

Context.ConnectionIdをキーにしてTimerElapsedHandlersのなかから削除すべきイベントハンドラを探します。そして削除します。そのあとTimerElapsedHandlersとClientProxyMapとGamesから値を削除します。

このときClientProxyMap.Count(TimerElapsedHandlers.CountとかGames.Countでもよいが)が0になった場合はタイマーは停止させてしまっても差し支えないことになります。この場合はサーバーに負荷をかけたくないので停止させてしまいます。

Elapsedイベント時の処理

タイマーでElapsedイベントが発生したときの処理ですが、今度はフィールド変数ConnectionIDに保存している文字列をつかってGamesから対応するGameオブジェクトを探します。この場合はContext.ConnectionIdではなくフィールド変数に保存されているものをつかわないとうまくいきません。

Gameオブジェクトが見つかったら上記で定義したGame.OnTimerメソッドを呼び出してキャラクタ移動の処理をおこなわせます。キャラクタの座標を求めることができたらSendAsyncメソッドで結果をクライアントサイドにおくります。

キー操作に対応する処理

キーが押されたとき、離されたときの処理を示します。この場合はContext.ConnectionIdをキーにしてGamesからGameオブジェクトを探します。そしてGame.OnKeyDownメソッドとGame.OnKeyUpメソッドを呼び出してフラグをセットさせます。

またクライアントサイドから変なデータが送られてきたことを想定して文字列の長さをチェックしています。

Program.csの編集

Program.csのapp.Run();と書かれている部分の直前に以下の1行を追加しておきます。

サーバーサイドの処理は以上です。次にクライアントサイドの処理を考えます。

クライアントサイドの処理

プロジェクトのフォルダにあるPagesフォルダのなかにMoveTest.cshtmlを作成して以下を書きます。

HTML部分

Canvasとキーが押されたたときの処理

次にJavaScript部分ですが

Pages\MoveTest.cshtmlのscriptタグ内

キーが押されたたときの処理を示します。キーが押しっぱなしになっているときにサーバーサイドに同じキーが押された情報が連続で送られないためにフラグで管理しています。

Pages\MoveTest.cshtmlのscriptタグ内

キーが離されたときの処理を示します。フラグをクリアしているだけです。

Pages\MoveTest.cshtmlのscriptタグ内

描画に関する処理

描画に関する処理を示します。サーバーサイドでタイマーイベントが発生するたびに座標を1だけ移動させていますが、それでは動きが小さいので移動幅を3倍にしています。それからキャラクタが移動する通路を1ピクセルの白い線で描画しています。

Pages\MoveTest.cshtmlのscriptタグ内

データを受信したときの処理

サーバサイドからデータを受信したときの処理を示します。

Pages\MoveTest.cshtmlのscriptタグ内

以下のサンプルページにアクセスしてみると、方向キーをおすとキャラクタが移動しますが、交差点でないとキーを押しても方向転換することができないことがわかります。