コードのさまざまな側面に影響を与えるいくつかの段落の形で記事を作成することにしました。 明らかなことはしたくないので、興味深い点だけに触れてみます。 これらには、私がインターネットで会った議論のトピックが含まれます。
一般原則
プロジェクトのアーキテクチャーの説明から始めましょう。 コードは、サーバーとクライアントの2つの主要部分に分けることができます。 すべてがJavaScriptで記述されているため、コードの一部はクライアントとサーバーの両方で使用されます。 ただし、クライアントクラスとサーバークラスのロジックは通常異なるため、これを大きな利点とは呼びません。
基本的に、クライアントの役割は、ユーザー入力を処理し、ゲームプロセスを表示することです。 それ以外はすべてサーバー上で発生します。 戦車の動き、弾丸の飛行、衝突の処理、およびゲームのロジックに関連する一般的なすべての計算は、クライアントにそのようなことを信頼する価値がないため、サーバー上で行われます。 また、クライアントにはゲームに影響しないアニメーションが与えられます。 たとえば、サーバーでは、タンクオブジェクトには、タンクが現在装甲されているか、タンクがボーナスを与えるというプロパティのみがあります。 そして、各スプライトを描画する必要がある計算は、クライアントによって実行されます。 また、たとえば、タンクの爆発は、対応するフレームのシーケンスからクライアント上でアニメーションを開始しますが、サーバー上では、爆発時にタンクが単純に削除されます。 このような組織により、外部からの干渉からゲームデータを保護できます。 また、不要なアクションでサーバーをロードしないでください。
キャンバス
競技場の表示は非常に原始的です。 ゲームの開始時に、setInterval()を使用して、フィールド全体を再描画する関数が50ミリ秒ごとに呼び出されます。 競技場の変化する部分のみを再描画することは可能だと思いますが、これまでのところ、これらは将来の計画に過ぎません。 各フレームには独自の番号があり、毎回1ずつ増加します。 この数に基づいて、表示されるオブジェクトは描画されるスプライトのリストを構成します。 たとえば、装甲戦車は2つのスプライト、戦車自体、およびちらつきのある輪郭で「構成」されます。 この機能は、複雑なアニメーションを順番に簡素化します。
地図
おそらく、サーバーとクライアントの両方で使用される主なコードは、競技場のコードです。 地図にオブジェクトを保存する方法について頭を悩ませる必要がありました。 インデックスが座標になる2次元配列を取ることができます。 したがって、マップを記述するには、サイズが26 * 26の配列で十分です。 また、このような方法を使用すると、オブジェクトの交差点を簡単に検索できます。 しかし、これは一見しただけです。 戦車と弾丸がよりスムーズに移動するには、アレイのサイズを大幅に増やす必要があります。 さらに、タンクは氷の上に乗ることも、弾丸が水の上を飛ぶこともできます。つまり、3次元ではなく3次元の配列がすでに必要です。 一般に、一連の質問とその解決策はまったく魅力的ではありません。 このオプションは、メモリにデータを保存するのには適していませんが、ファイルにカードを保存するには非常に便利です。
私がやった2番目の方法は、地図上に存在するオブジェクトのリストを保存することです。 各オブジェクトには、幅と高さだけでなく、オブジェクトの交点を検索するために使用される座標があります。 この方法の欠点は、交差点を検索するために、マップ上に存在するすべてのオブジェクトを並べ替える必要があり、各マップ上のこれらのオブジェクト(壁、木、水、タンク、弾丸など)が約1000になることが判明していることです。つまり、移動するオブジェクトごとに交差()関数を1000回実行し、2つの長方形の交差を確認します。
現在開発中の3番目の方法は、マップをツリー形式で表示することです。 ルートがマップ全体である場合、最初の2つのブランチは左半分と右半分などです。 木の葉は実際にはゲームオブジェクトです(タンク、弾丸、壁など)。 このアプローチは、マップの特定のセクターの充足度に応じて、オブジェクトとの交差点の検索を1000操作から20-100に減らします。
同期する
続けましょう。 クライアントとサーバー間のデータは、クライアントが常にゲームの現在の状態を保持できるように、何らかの方法で同期する必要があります。 このために、次のオプションを選択しました。サーバー上のすべての変更が記録され、要求に応じてクライアントに送信されます。 各クライアントには最後の同期の時間があります。したがって、同期ごとに、クライアントは最後の数十ミリ秒の変更を受け取ります。 この方法はかなり多くのプロセッサー時間を消費しますが、別の簡単なオプションは思いつきませんでした。 もちろん、一般にすべてのサーバーデータをユーザーに転送することは意味がありません。 ユーザーは、他のプレーヤーからのゲームデータを必要としません。 したがって、いくつかのログがサーバー上で維持されます。すべてのオンラインユーザー、すべてのオンラインゲーム、現在のゲームのユーザー、現在のゲームのマップ上のオブジェクトなど。
ログは次のように形成されます。クライアントに送信されるデータが変更されるたびに、変更されたオブジェクトへのリンクと変更の時刻がログに記録されます。 このオブジェクトに関するログにすでにレコードがある場合、それは削除され、新しいレコードが記録されます。 つまり、変更されたオブジェクトへのリンクはログに1つしかなく、このリンクには最後の変更の時刻がマークされます。 クライアントが同期のためにデータを要求すると、サーバーは目的の期間オブジェクトを選択し、それらをシリアル化してクライアントに送信します。 オブジェクトが1つの座標のみを変更した場合、クライアントには引き続きシリアル化されたオブジェクト全体が送信されるため、アクティビティ用のフィールドがまだあります。 ただし、この方法には大きな利点が1つあります。 ユーザーはいつでもサーバーに接続して、ゲームの現在の状態、チャット、プレーヤーのリストを取得できます。 プレーヤーとして既に進行中のゲームに接続することはできませんが、すでに進行中のゲームを表示する機能を追加する予定です。
データ転送用にsocket.ioライブラリが選択されました。これは、これが私が知っているサーバー上のWebSocketサポートの唯一のバージョンであるためです。 Socket.ioは、WebSocketだけでなくフラッシュソケットなどのいくつかの方法でもデータ転送をサポートするため、多少冗長であるため、理想的なオプションではありません。 ただし、WebSocketを使用する場合にのみ、ゲームは適切な速度で進みます。 また、安全な接続に切り替えることも計画されています。これにより、何らかの理由でWebSocketトラフィックを送信したくないプロキシを介して接続を確立できます。
性能
今ではパフォーマンスが悪いです。 Intel Pentium 4 3.00GHz上のサーバーは、一度に2〜3ゲームしかプルできません。 プロファイリングの結果は2つの問題を示しました。これはサーバー上のオブジェクトの交差とロギングの変更の検索です。 彼らが言うように、仕事の2番目の90%は残った。
著作権
ゲームのスプライトが見つかり、インターネットからダウンロードされました。 これがいかに合法であるかを教えてください。 つまり、ゲームのリメイクを作成したい場合、元のゲームのグラフィックを使用できますか?
第二部、最適化について。