クソゲーに魂を!プロジェクト 残念ながらプログラミング以前の問題です!の続きです。

ゲームの仕様

まずゲームの仕様は以下のとおりです。

キー操作またはマウスで自機を操作することができる。餌を食べると体長が長くなる。

発射ボタン(PCであればSpaceキーまたはマウスクリック)を押すと自機の前後から弾丸が発射される。弾丸を発射すると自分の体長が 1 短くなるが、敵に命中させた場合は敵の体長を 8 短くすることができる。

自分よりも短い敵に体当たりをしたらその敵を倒すことができる。 ただし長い敵に体当たりをしたときは逆に自分が死ぬ。 同じ長さの場合は乱数で勝敗を決める。

フィールドは半径800の円とし、時間の経過とともに狭くなっていく(最小値は120)。ユーザーは任意のタイミングでゲームに参加できる。そのときの体長は各プレイヤーの長さの平均値とする。しかしミスをしたステージで再参戦するときは最小の長さ(=8)とする。倒されたNPCは同一ステージ内では復活せず、最後まで生き残ったプレイヤーをそのステージの優勝者とする。

餌を食べたときは10点加算。敵を倒したら自分の長さ×10点です。各ステージで最後まで生き残った場合は勝者となり、そのステージで獲得した点数×2がボーナスポイントとして加算される。

高速化の工夫

60fpsだとどうしてもときどきカクつきがおきます。Microsoft.AspNetCore.SignalRを使う以上仕方がないことなのかと思ったのですが、そうでもありません。

高頻度でメッセージを送信するアプリケーション (リアルタイム ゲーム アプリケーションなど) であっても、ほとんどのアプリケーションは 1 秒間に数個以上のメッセージを送信する必要はありません。

SignalR パフォーマンス

たしかにそのとおりでフィールド上にあるすべてのオブジェクトの座標を送信しようとするからデータ送信量が増えるのであって工夫すれば「1 秒間に数個」程度に減らすことができます。

プレイヤーの進行速度は同じなので進行方向が変化したことを送信すればクライアント側でもサーバー側で管理されている座標と同じ位置に描画をすることができるはずです。ただ通信障害でうまくいかないこともあるのでときどきオブジェクトの座標を送信してズレを修正することにします。

では、はじめましょう。名前空間は FireSnake とします。

定数の定義

Constantクラスを以下のように定義します。

Circleクラスの定義

プレイヤーの身体を構成する円の座標を管理するためにCircleクラスを定義します。Circleオブジェクトにはオブジェクトが生成されたときのプレイヤーの状態も記録します。ひとつ前に生成されたオブジェクトに記録されている値と比較して異なっている場合はプレイヤーの状態が変化したことを意味しています。

Bulletクラスの定義

弾丸を操作するためのBulletクラスを定義します。弾丸には一意のIDであるIDプロパティとどのプレイヤーが発射したものかわかるようにPlayerIDプロパティを定義します。また弾丸はなにかに当たるまで永久に飛び続けるのではなく約1秒で消滅させます(画面外から突然飛んできて避けられないのはあまりに理不尽仕様だと指摘されたため)。

Foodクラスの定義

餌を操作するためのFoodクラスを定義します。餌も一意のIDであるIDプロパティを定義します。餌はフィールドの端に当たったら内部にむけて跳ね返るようにします。なので他のプレイヤーに食べられるまで存在しつづけることになります。