マルチユーザーブラウザー戦略をゼロから作業ドラフトまで記述した経験をhabrasocietyと共有したいと思います。 プログラミング自体の観点から、アーキテクチャと遭遇した問題。 これはゲームを作成する私の最初の経験であり、多くの欠陥やミスに気付くか、賢明な何かをアドバイスするかもしれません。 しかし、ポイントは重要ではありません-私が問題を作業ドラフトに持ち込んだ主なことは、多くの人が詳細を知ることに興味があることは確かです。
ゲームはどのようなものですか? どうやら最短の説明は「文明のクローン」=)になります。 しかし、これは私が自分の何かを思い付く想像力を持っていなかったことを意味しません。 文明を作るだけが私の夢でした。 別のゲームを書くことでそれほど満足することはないでしょう。 それどころか、文明のファンは逆に、私のゲームは見た目を除いて文明とはまったく違うと考えています。 たぶんそれは最高のためです。
このゲームはThe Fate of Nationと呼ばれていますhttp://fatenation.com
アプリケーションのアーキテクチャとロジックを無限にペイントすることは可能です。そのため、興味がある場合はおそらく記事をいくつかの部分に分割する必要があります。 さらに、自分が書いたものをどの言語やプラットフォームでも実装できるため、大量のコードを持ち込むことにはあまり意味がありません。
ゲームを作成するために、サーバーでphpとMySQLを、クライアントでhtmlとjavascriptを使用しました。 フラッシュは使用されません。 html5のうち、サイト上のビデオと、ゲーム自体のキャンバスを含むいくつかの領域のみがあります-マップの表面とミニマップを含みます。 クライアント側のコードはサーバー側よりも数倍大きいため、主にクライアント開発について説明しますが、サーバーから始めましょう。
一般的なアーキテクチャ
アプリケーションの全体的なアーキテクチャは、完全に非同期のJavaScript Webアプリケーションのように見えます。 ページのリロードはありません。 AjaxおよびJSONのみを介したサーバーとの通信。 JSONでは、htmlコードなしでデータのみが送信されます。 Htmlマークアップは、アプリケーションの読み込みの開始時に個別にダウンロードされ、データがサーバーからダウンロードされるときに、クライアントテンプレートエンジンを介してデータで処理されます。
サーバーでフレームワークを使用しませんでした-Zend Frameworkを使用して記述を開始しましたが、後で不要になったため削除しました。 代わりに、彼はZendFrameworkのコントローラーとアクションを漠然と連想させるシンプルなアーキテクチャを作成しました。
この図からわかるように、サーバーには1つのエントリポイントがあります-index.phpファイルです。 サーバーを再生するプロセスでは、次のようなリクエストが行われます:/ Unit / Move。 パラメータ付きのJSONが送信されます。この場合は、ユニットのIDと移動の座標です。 サーバーはこの要求をindex.phpにリダイレクトします。index.phpはデータベースに順次接続し、現在のユーザーを確認し、クエリ文字列を解析してコントローラー(ユニット)とアクション(移動)を決定します。 コントローラーが指定されていない場合、サーバーはクライアントアプリケーションをビルドするためのコードを含むインデックスページを発行しますが、それについては後で詳しく説明します。 コントローラーが設定されている場合、このコントローラーのファイルが検索され、そのコードが接続され、このコントローラーの要求処理が開始され、必要なアクションが検索され、入力データがチェックインされ、ビジネスロジックが調整されます。
データベースを操作するために、ビジネスロジックとコントローラーからのすべてのデータベースクエリ、データシールド、その他の小さな便利な機能を通過させる特別なデータベース抽象化クラスが作成されます。 実際、サーバー上では、アーキテクチャがすべて非常に単純であり、サーバーの責任の範囲は、入力データの確認とデータベースからの情報の発行のみです。 他のすべてはクライアントによって行われます。
ゲーム自体について少し説明します。
地図
最初に行われたのは、ほとんどすべてのゲームアクションが行われるマップでした。都市の建設、改善(作物、道路)、ユニットの移動、マップの探索です。 データベース内の個々のエントリについて、マップサイズは1000セルあたり1000でした。 マップが無限に作成され、セルに関するアクションが実行されたときにセルに関するレコードが動的に挿入されるゲームを見ました。 しかし、このアプローチはその予測不可能性のために私を少し怖がらせました。 固定カードを持っていることが確実にわかっていると、ゲームの計画がはるかに簡単になります。 プレイヤーの位置、人数、都市とユニットの数を計画し、おおよその負荷を見積もることができます。
合計で、カードのデータベースに1000 * 1000 = 1 000 000レコードが見つかりました。 それ以前は、それほど多くのレコードを扱っていなかったので、私は驚いた。 遅くなると思った。
私はMySqlを出し抜いて、より速く動作することを期待して、カードをそれぞれ100,000レコードの10個のテーブルに配置することにしました。 その結果、複数のテーブルからセルを一度に選択する愚かなロジックを作成する必要があり、測定ではパフォーマンスが低下するだけでした。 すべてを1番目のテーブルに戻しました。
- x、yはセルの座標です。
- 地形-領域のタイプ(草原、森林、山など)。
- リソース-セル上にある場合のリソース(粘土、馬)。
- wens9_code-フィールドの名前は西北北東に由来します... 1つのセルに512種類のアイコンがある領土のスプライト!))その後、私の脳は沸騰し始め、アイコンを選択した依存関係を分析し、hemoの大きさを実感しました。 =)それはただ一つのことです:スプライトが128 x 64ピクセルの菱形の形でハードエンドを持っていることです。 最終的に、半透明のエッジが互いに重なり合うpng24を使用し、Tsiv3で説明した例よりも10倍優れた多様なランドスケープを作成することにしました。 そして、隣接するセルに関係なく、アイコンをランダムに選択します。 これは画面で見ることができます-同じフィールドアイコンがどこにあるかすぐにわかりません。 彼らは端の周りの山々を洗うのを忘れており、明確な境界線を持っている-それは悪いように見える。
- starting_position-プレイヤーがこのセルに表示されることを意味します。
もちろん、これはフィールドのリスト全体ではありませんが、以下では簡単にするために、記事で説明したフィールドのみを引用します。
地域
クライアントは、サーバーから特定のセルを要求するのではなく、100個のバッチ(10 x 10)で要求するように記述されています。これをリージョンと呼びます。 つまり、各セルは特定の領域に属し、クライアントは特定のセルではなく領域を要求します。 プレーヤーがマップを移動して新しい領域が見えるようになるとすぐに、この領域の背後でそれに隣接するサーバーにリクエストを送信します。 ロードされた各領域のデータは、クライアントで30秒間キャッシュされます。 これにより、ブレーキやサーバーへの不必要なリクエストなしでマップを簡単にスクロールでき、新しいリージョンがマップに表示されるときの遅延を排除します-隣接するすべてのリージョンを事前にロードするためです。
これらの「地域」を作ったとき、どれだけ生産性が上がるか想像もしていませんでした。 地域フィールドでフィルタリングすることで100個のセルが選択されたことがわかりました;座標でフィルタリングするよりも何倍も高速であることがわかりました。 1つのフィールドの位置= 1000 * x + yでセルのx座標とy座標を組み合わせたという事実にもかかわらず。 これは主に利便性のために行いました-1つのセルを取得しやすくするためです。
次に、マップ上に位置し、対応する特定の座標を持つ各エンティティ(都市、ユニット、リソース)に領域をマークしました。これにより、サンプルのパフォーマンスが何百倍も向上しました。 100万の一意の値を持つキーでテーブル内の値を検索することと、10,000の値を持つキーを検索することです。
したがって、このようなシステムは判明しました。クライアントはリージョンを要求します-サーバーはデータベースとその上のすべてのエンティティからマップを抽出し、リージョンですばやくフィルタリングします-クライアントはキャンバス上のすべてのブラウザにそれを描画します。 各エンティティには、戦闘終了までの時間や次のセルに移動するまでの時間などのフィールドがあります。この場合、これらのタイムアウトの後、必要なものだけをローカルで更新します。 たとえば、マップを調べると、新しく開いたセルだけをロードし、それ以上ロードしません。 敵ユニットが移動した場合-その移動の次のポイントをロードします。
地図調査
しかし、別の質問が私を苦しめました。 私は必死に地図の研究をしたかったので、最初は探索されず、何かを見るために地図の上を歩かなければなりませんでした。
これは、ブラウザゲームでは見たことがない(実際、空中ではなくマップ上を移動するユニットのように)。 仕事に取り掛かりました。 プレーヤーの開始位置はリージョン内にあります。 つまり、プレーヤーの最大数はリージョンと同様に10,000です。 各プレイヤーはマップ全体を偵察できます。 合計10 000 * 1 000 000 = 100億のレコードをセルの許可のテーブルに入れることができます! マップテーブルは、このベイビートークの背景に見えます=)。 もちろん、この数値は高すぎます。 誰もがマップ全体を偵察できる可能性は低い-それは非常に大きい。 しかし、許可テーブルの数千万のエントリがゲームの終わりにあることは確かです。
また、このテーブルでは、地域を選択するたびに、カード自体を含むすべてのエンティティを結合する必要がありました。 この場合も、キーによって地域のフィールドに保存されたため、これらの結合をより高速に行うことができました。
負荷テストを実行して、サーバーの速度が低下し始める段階を判断することはできませんでした。 私が見た最大値は、アクセス許可テーブルに200万を少し超えるエントリです。
移動ユニット
ユニットの動きを作るには、ロジックを何度も考えて書き直さなければなりませんでした。
最初に必要なことは、新しいセルの開始時間を正確に追跡し、これらのデータに基づいてセル、ユニット、都市をフィルタリングできるようにすることです。 すぐにカードの許可テーブルを使用するように頼みますが、特別なフィールドがあります-つまり、このレコードがアクティブになる時間を意味します。 それで終わった。 クライアントは、ユニットIDと新しい位置座標を送信します。 サーバーは、ユニットの現在の位置、移動するセルの座標を計算し、ユニットや他のパラメーターなど、これらのセルの領域に応じて、このユニットが各セル内にある時間を計算します。 次に、ユニットのビューの半径に応じて、同じ方法で隣接セルが追加で計算されます。
これらはすべてセルの権限テーブルに挿入され、カードは時計のように機能します。 ユニットは、マップを移動するたびに、新しい許可データに従ってエンティティをフィルターする標準的な方法を使用して、マップを移動するたびにマップを歩き回ります。
次に、ユニットの移動を示す許可レコード、さらに2つのフィールドでマークします:ユニットIDとレコードのタイプ:「 概要セル 」または「 ユニットが進むセル 」。 最初のフィールドは、ユニットが停止または宛先が変更されたときに削除できるようにするために必要です。2番目のフィールドは、ユニットが選択されたときに場所の変更時刻を書き留めるために必要です。
それから職場の同僚は、もう一つのかなり明白な点を私に促しました:ユニットが与えられたセルを去った時間を示すフィールドに入ること。 out_timestampと呼びました。 これにより、すべてのユニットの現在の位置を簡単に選択でき、それに応じて、表示されているセルで敵ユニットをフィルタリングできます。
私の例はそのようなゲームで最も成功したアーキテクチャではないと確信していますが、それはうまくいくようです=)クライアント。
はい、ところで、多くの場合、ゲームに関するさまざまな投稿の後、人々はゲームプレイではなくグラフィックスを賞賛し始めます。 だから私はすぐに言います-私はそれを描きませんでした!!! これはすべて、アーティストデザイナーのマキシムクドリツキーです。
PS TheShock 、トピックを書く際の助けとサポートをありがとう! =)