EDT に直面してゲームの状態を管理するにはどうすればよいでしょうか?

StackOverflow https://stackoverflow.com/questions/981920

質問

Java プラットフォームでリアルタイム ストラテジー ゲームのクローンを開発していますが、どこに配置するか、またゲームの状態をどのように管理するかについて概念的な質問があります。ゲームはレンダリングとして Swing/Java2D を使用します。現在の開発段階では、シミュレーションや AI は存在せず、ゲームの状態を変更できるのはユーザーだけです (たとえば、建物の建設と解体、生産ラインの追加と削除、車両と機器の組み立て)。したがって、ゲーム状態の操作は、レンダリング ルックアップなしでイベント ディスパッチ スレッドで実行できます。ゲームの状態は、さまざまな集約情報をユーザーに表示するためにも使用されます。

ただし、シミュレーション (建物の進行状況、人口の変化、艦隊の移動、製造プロセスなど) を導入する必要があるため、タイマーや EDT でゲームの状態を変更すると、レンダリングが確実に遅くなります。

シミュレーション/AI 操作が 500 ミリ秒ごとに実行され、長さ約 250 ミリ秒の計算に SwingWorker を使用するとします。シミュレーションと可能なユーザー操作の間でゲーム状態の読み取りに関して競合状態が発生しないようにするにはどうすればよいですか?

シミュレーションの結果 (少量のデータ) は、SwingUtilities.invokeLater() 呼び出しを介して効率的に EDT に戻すことができることがわかっています。

ゲーム状態モデルは複雑すぎて、不変値クラスをあらゆる場所で使用するだけでは実行不可能であるようです。

この読み取り競合状態を排除するための比較的正しいアプローチはありますか?おそらくタイマーティックごとに完全/部分的なゲームステートのクローン作成を行うか、ゲームステートの生存空間を EDT から他のスレッドに変更するのでしょうか?

アップデート: (私が与えたコメントから)ゲームは13人のAI制御プレーヤー、1人の人間のプレイヤーで動作し、約10000のゲームオブジェクト(惑星、建物、機器、研究など)を持っています。たとえば、ゲーム オブジェクトには次の属性があります。

World (Planets, Players, Fleets, ...)
Planet (location, owner, population, type, 
    map, buildings, taxation, allocation, ...)
Building (location, enabled, energy, worker, health, ...)

シナリオでは、ユーザーはこの惑星に新しい建物を建てます。地図と建物のコレクションを変更する必要があるため、これは EDT で実行されます。これと並行して、すべてのゲーム プラネット上の建物へのエネルギー割り当てを計算するためのシミュレーションが 500 ミリ秒ごとに実行されます。統計収集のために建物のコレクションを横断する必要があります。割り当てが計算されると、EDT に送信され、各建物のエネルギー フィールドが割り当てられます。

いずれにしても AI 計算の結果が EDT の構造に適用されるため、この特性を持つのは人間のプレイヤー インタラクションのみです。

一般に、オブジェクト属性の 75% は静的で、レンダリングのみに使用されます。残りの部分は、ユーザーの操作またはシミュレーション/AI の決定によって変更可能です。また、前のステップですべての変更が書き戻されるまで、新しいシミュレーション/AI ステップが開始されないことも保証されます。

私の目標は次のとおりです。

  • ユーザーの操作を遅らせることは避けてください。例:ユーザーが建物を地球上に配置すると、0.5 秒後にのみ視覚的なフィードバックが得られます。
  • 計算やロック待機などで EDT をブロックしないようにします。
  • コレクションの走査と変更、属性の変更による同時実行性の問題を回避します。

オプション:

  • きめ細かいオブジェクトのロック
  • 不変のコレクション
  • 揮発性フィールド
  • 部分的なスナップショット

これらすべてには、モデルとゲームにとって長所、短所、および原因があります。

更新 2: 私が話しているのは これ ゲーム。私のクローンは ここ. 。スクリーンショットは、レンダリングとデータ モデルの相互作用を想像するのに役立つ場合があります。

アップデート 3:

コメントから誤解されているように見えるので、私の問題を明確にするために小さなコードサンプルを提供してみます。

List<GameObject> largeListOfGameObjects = ...
List<Building> preFilteredListOfBuildings = ...
// In EDT
public void onAddBuildingClicked() {
    Building b = new Building(100 /* kW */);
    largeListOfGameObjects.add(b);
    preFilteredListOfBuildings.add(b);
}
// In EDT
public void paint(Graphics g) {
    int y = 0;
    for (Building b : preFilteredListOfBuildings) {
        g.drawString(Integer.toString(b.powerAssigned), 0, y);
        y += 20;
    }
}
// In EDT
public void assignPowerTo(Building b, int amount) {
    b.powerAssigned = amount;
}
// In simulation thread
public void distributePower() {
    int sum = 0;
    for (Building b : preFilteredListOfBuildings) {
        sum += b.powerRequired;
    }
    final int alloc = sum / (preFilteredListOfBuildings.size() + 1);
    for (final Building b : preFilteredListOfBuildings) {
        SwingUtilities.invokeLater(=> assignPowerTo(b, alloc));            
    }
}

したがって、重複しているのは onAddBuildingClicked() と distributionPower() の間です。ここで、ゲーム モデルのさまざまな部分の間にこの種の重複が 50 個ある場合を想像してください。

役に立ちましたか?

解決

それは、クライアント/サーバアプローチから利益を得ることができるように

この音ます:

双方向性とその端に起こるのレンダリング -

プレイヤーがクライアントです。だから、プレイヤーがボタンを押すと、要求がサーバーに送られます。サーバーからの応答が戻ってくると、プレイヤーの状態が更新されます。起こって、これらのものの間の任意の時点で、画面が再塗装することができ、そしてクライアントが現在それを知っているとして、それはゲームの状態を反映します。

AIも同様に、クライアントである - それはボットの同等です。

シミュレーションがサーバーです。これは、様々な時間にそのクライアントからの更新情報を取得し、世界の状態を更新し、その後、必要に応じてみんなにこれらの更新を送信します。それはあなたの状況で結びつけるのはここです:シミュレーション/ AIは、静的な世界を必要とし、多くのことを一度起こっています。サーバは単に変更要求をキューし、クライアント(複数可)に戻って更新を送信する前にそれらを適用することができます。だから、限り、サーバーの関係のように、ゲームの世界が実際にリアルタイムで変更されていないサーバがくそもそれがあることを決定するたびに、それは変更です。

最後に、クライアント側で、あなたはボタンを押すと、いくつかの簡単なおおよその計算を行うと、結果を表示して結果を見て(そうすぐに必要が満たされている)、その後、ときに、より正確な結果を表示するまでの遅延を防ぐことができますサーバーは、あなたに話しに周りに取得します。

これは実際にはこれらの用語でそれを考えるのに役立ちますだけのことを、道のオーバーザインターネットソートTCP / IPで実装する必要がないことに注意してください。

代わりに、あなたは彼らがすでに心の中でロックと一貫性で構築されているように、データベース上のシミュレーション中にコヒーレントデータを維持するための責任を配置することができます。 sqliteのような何かが非ネットワークソリューションの一部として働くことができます。

他のヒント

わからない私は完全にあなたが探している動作を理解していますが、そのすべての状態変更は、単一のスレッドによって処理されている状態の変化のスレッド/キューのようなものを必要とするように聞こえる。

多分SwingUtilities.invokeLaterを()、および/またはSwingUtilities.invokeAndWaitのような()あなたの状態変更要求を処理するためにあなたの状態変更キューのため、APIを作成します。

それは私が考えるGUIに反映されているどのように

あなたが探している行動に依存します。すなわち、現在の状態が$ 0であるので、お金を引き出す、あるいは撤退要求が処理されたときのアカウントが空だったユーザーに戻ってポップすることはできません。 (おそらくないという用語に;-))

最も簡単な方法は、EDTで実行するのに十分なシミュレーションを高速にすることです。動作するプログラムを好む!

2スレッドモデルの場合、私がお勧めすることはレンダリングモデルとドメインモデルを同期させています。レンダリングモデルは、ドメインモデルから来たものにデータを維持する必要があります。

アップデートの場合:シミュレーションスレッドでレンダリングモデルをロックします。物事が更新モデルをレンダリングすると予想されているものと異なっているレンダリングモデル更新トラバース。トラバースが終了したら、レンダリングモデルのロックを解除し、再描画をスケジュールします。このアプローチでは、あなたがbazillionリスナーを必要としないことに注意してください。

レンダリングモデルは、異なる深さを持つことができます。一方の極端では、画像と更新操作は、単に新しい画像オブジェクトを持つ単一の基準交換する(この習慣ハンドルを、例えば、非常によくサイズ変更または他の表面的な相互作用)されるかもしれません。あなたがアイテムを変更し、ちょうどeveythingを更新しているかどうかをチェックする気にしないことがあります。

ゲームの状態を変更する場合は、あなたが他のSwingモデルのようなゲームの状態を治療し、唯一のEDTの状態を変更したり、閲覧することができます(あなたがそれを変更するために何を知っていれば)高速です。ゲームの状態を変更することが速くない場合は、状態変化を同期させ、スイング労働者/タイマー(ただし、EDT)でそれを行うことができますいずれか、またはあなたがあなたをその時点でEDT(と同様に扱い、別のスレッドでそれを行うことができます)変更要求を処理するためにBlockingQueueを使用して見てください。 UIは、リスナーやオブザーバーを経由して送信されたレンダリングの変化をゲーム状態から情報を取得するために持っていないが、代わりに持っていない場合は、最後には、より有用である。

これは、インクリメンタルゲームの状態を更新し、まだ一貫性のあるモデルを持つことは可能ですか?例えば、間に惑星/プレーヤ/フリートオブジェクトのサブセットの再計算/ユーザの更新をレンダリングする。

もしそうなら、あなただけEDTは、ユーザーの入力を処理し、レンダリングできるようにする前の状態の小さな部分を計算することEDTで増分更新を実行できます。

EDT内の各増分更新後、あなたは更新されないままであるかのモデルのほとんど覚えていて、保留中のユーザ入力とレンダリングが行われた後、この処理を継続するためにEDT上で新しいSwingWorkerのスケジュールを設定する必要があります。

これは、あなたがまだ応答ユーザーとの対話を維持しながら、ゲームモデルをコピーしたり、ロックを回避できるようにする必要があります。

私はあなたの世界は、唯一のオブジェクトへの参照を維持するために使用されるべき任意のデータを保存したり、任意のオブジェクト自体に変更を加える必要はありませんだと思うと、そのオブジェクトを変更する必要があるときに、プレーヤーが変更の変更を行ってい直接。このイベントでは、あなたがする必要がある唯一のものは、プレーヤーが変更を行っている際に、他のプレイヤーがそうできないようにゲーム世界内の各オブジェクトを同期です。ここで私が考えているものの例です。

プレイヤーAは、それがその惑星のための世界を尋ねて(あなたの実装に依存しているか)、惑星について知る必要があります。世界は、プレイヤーAがを求めプラネットオブジェクトへの参照を返します。プレイヤーAは、変更を行うことを決定したので、それはそう。のは、それが建物を追加するとしましょう。惑星に建物を追加するための方法は、唯一のプレイヤーが一度に行うことができますので、同期されています。建物は、独自の施工時間を追跡します(もしあれば)そう惑星のadd構築方法は、ほとんどすぐに解放されるだろう。この方法では、複数のプレイヤーがお互いに影響を与えることなく、同時に同じ地球上の情報を求めることができ、プレイヤーはラグの多く出現することなく、ほぼ同時に建物を追加することができます。 2人の選手は、建物を置く場所を探している場合(つまり、あなたのゲームの一部である場合)、その場所の適合性をチェックするクエリではない変更になります。

私は、これはあなたが質問している応答しない場合、私はそれを正しく理解している場合はわからないごめんなさいます。

どのようにパイプとフィルタアーキテクチャを実装について。フィルタが十分に速くない場合はパイプが一緒にフィルタとキュー要求を接続します。処理は、内部のフィルターを発生します。レンダリングエンジンは、後続のフィルタのセットによって実現されつつ、第1のフィルタは、AIエンジンである。

すべてのタイマー刻みで、新しいダイナミックな世界の状態は、の最初のパイプに挿入された全ての入力(時間も入力されている)とのコピーに基づいて計算されます。

最も単純なケースでは、あなたのレンダリングエンジンは、単一のフィルタとして実装されます。それはちょうど入力パイプからの状態のスナップショットを取り、静的な状態でそれを一緒にレンダリングします。ライブゲームでは、レンダリングエンジンを使用すると、ベンチマークをやったり、一人一人をレンダリングしたいと思う映像を出力している場合しながら、複数のパイプに存在する場合の状態をスキップすることがあります。

複数のフィルタは、あなたは、並列処理が可能になるより良い、にあなたのレンダリングエンジンを分解することができます。多分例えば、AIエンジンを分解することも可能ですあなたは急速に変化し、低速の変化状態に動的な状態を分離したい場合があります。

このアーキテクチャは、同期があまりなく、あなたに良い並列処理を与えます。

このアーキテクチャの問題は、ガベージコレクションがマルチスレッドから得られるすべての利点を殺して、可能なすべてのスレッドを毎回凍結頻繁に実行しようとしていることである。

ユーザーfrmo更新するには上のモデルに更新を配置する優先度つきキューを必要とするシミュレーションおよび他の入力からの更新よりも優先持っているように、

に見えます。私はあなたが言って聞くと、ユーザーは常によりも長い1つのシミュレーションステップを取ることが労働者を持つことができます(そうでない場合は、シミュレーション)他の入力wheras彼の行動の上に即座にフィードバックを必要とすることです。 そして、優先度つきキューで同期ます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top