Slither.io (スリザリオ)のようなオンラインゲームを作りたい(2)の続きです。Playerクラスを定義することでプレイヤーの動作処理が高速にできる方法を考えます。

当初考えていた方法はキューを使った方法です。以下の動画のようにスネークに身体を更生する円の位置は一度確定したら移動することはなく、移動処理は頭の部分を追加、尻尾よりも後ろになる部分は削除するという処理の繰り返しにするというものです。

ところが実際のSlither.ioの動きはこうはなっていません。

これを見ると移動することで回転でできた輪っかが移動によって小さくなっていることがわかります。こうならないと相手を囲んだとき内部でグルグル回転されるといつまで経っても倒せないという問題がおきます。そこでこのような動きを実現するために先頭からn番目の円の中心をn-1番目の円の中心とn+1番目の円の中心の中点に移動させることにします。

また先頭に追加、最後尾から削除という処理を繰り返すのでLinkedListを使います。これだと先頭に追加、最後尾から削除という処理がO(1)でできます(LinkedListはランダムアクセスが苦手なので全体の処理速度は通常のListと似たような結果になるかもしれないが・・・)。

フィールド変数とプロパティ

プレイヤー(NPCを含む)を操作するためにPlayerのクラスを定義します。

まずフィールド変数とプロパティを示します。

コンストラクタと初期化

コンストラクタを示します。第一引数はASP.NET SignalRで使われる一意の接続ID、第二引数はプレイヤーの名前、第三引数は当たり判定用のCirclesMapオブジェクト(同一ゲームでは同じオブジェクトを使う)です。

プレイヤーに一意の通し番号をつけます。そのあと渡されたプレイヤー名をPlayerNameにセットします。このときに名前に不適切な文字(改行文字やタブ文字)が含まれていたら除去します。また長い文字列が渡されたときは16文字以内にします。

文字列を半角16~17文字以下にする処理を示します。文字コードが128未満なら半角文字、それ以外は全角文字ということにして(厳密にはぜんぜん違うのだが…)16文字数えています。

プレイヤーの初期化

プレイヤーの初期化(死亡時からの復活)の処理を示します。

ここでやることは以下のとおりです。

プレイヤーの身体を構成するCircleオブジェクトのリストのクリア
次に追加するCiecleオブジェクトの番号(HeadNumber)をリセット
体長、太さ、向き、倒した敵の数のリセット
各フラグ(移動方向など)のクリア
PlayerNameが空文字列ならNPCなのでPlayerIDをつかって名前をつける
最初の頭の座標の設定と頭部のCircleオブジェクトの生成とLinkedListとCirclesMapへの追加

引数は頭の座標です。

ユーザーがページにアクセスしただけの状態では上記のInitメソッドではなくDemoメソッドを実行します。プレイヤーでもNPCでもない状態で当たり判定がない非表示のPlayerオブジェクトとして初期化されます。これは中心付近に存在する他のプレイヤーやNPCの動きを表示させるためのものです。

フラグのセットとクリア

以下はユーザーが回頭やダッシュを開始または終了したときに各フラグをセットまたはクリアするための処理です。

移動処理

スネークを移動させる処理を示します。

頭の向きから次の頭の座標を求めてCircleオブジェクトを生成して追加します。また体長よりもリストのほうが長い場合は最後尾を削除します。

また冒頭に述べた回転でできた輪っかを小さくする処理を行います。ここでは輪っかになっているかどうか無関係に、先頭からn番目の円の中心をn-1番目の円の中心とn+1番目の円の中心の中点に移動させています。

更新処理

更新処理を示します。フラグの状態から移動方向を変更します。そのあとMoveメソッドを呼び出してスネークを移動させます。ダッシュ時はMoveメソッドを2回余分に呼び出します。

NPCの場合は 6回更新に1回の割合で 0.06radだけ右に回転させますが、ときどき変化をもたせるため別の動きもさせます。ただしNpcTurn◯◯Countが0でない場合はその方向に回頭処理をおこないます。