8

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

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

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

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

ゲーム状態モデルは複雑すぎて、どこでも不変の値クラスを使用するだけでは実行不可能なようです。

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

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

ワールド (惑星、プレイヤー、フリートなど)
Planet (場所、所有者、人口、タイプ、
    地図、建物、課税、割り当て、...)
建物 (場所、有効化、エネルギー、労働者、健康、...)

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

いずれにせよ、AI 計算の結果が EDT の構造に適用されるため、このプロパティを持つのは人間のプレイヤー インタラクションだけです。

一般に、オブジェクト属性の 75% は静的であり、レンダリングのみに使用されます。残りの部分は、ユーザーの操作またはシミュレーション/AI の決定によって変更できます。また、前のシミュレーション/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() と distributedPower() の間です。ここで、ゲーム モデルのさまざまな部分の間にこの種のオーバーラップが 50 ある場合を想像してください。

4

8 に答える 8

3

これは、クライアント/サーバー アプローチの恩恵を受ける可能性があるように思えます。

プレーヤーはクライアントです。インタラクティブ性とレンダリングはクライアント側で行われます。プレーヤーがボタンを押すと、リクエストがサーバーに送られます。サーバーからの返信が返ってきて、プレイヤーの状態が更新されます。これらの発生の間の任意の時点で、画面を再描画することができ、クライアントが現在認識しているゲームの状態が反映されます。

AI も同様にクライアントであり、ボットに相当します。

シミュレーションはサーバーです。さまざまなタイミングでクライアントから更新を取得し、世界の状態を更新してから、これらの更新を必要に応じて全員に送信します。これがあなたの状況と結びつくところです: シミュレーション/AI は静的な世界を必要とし、多くのことが同時に起こっています。サーバーは、更新をクライアントに送り返す前に、変更要求をキューに入れ、それらを適用するだけです。したがって、サーバーに関する限り、ゲームの世界は実際にはリアルタイムで変化しているのではなく、サーバーが適切に判断したときにいつでも変化しています。

最後に、クライアント側では、ボタンを押してから結果が表示されるまでの遅延を防ぐことができます。これには、簡単なおおよその計算を行って結果を表示し (そのため、当面の必要性が満たされます)、サーバーが動き回ったときに、より正確な結果を表示します。あなたと話すこと。

これは、実際には TCP/IP over the Internet のような方法で実装する必要はありません。そのような用語で考えるのに役立つだけです。

あるいは、データベースはロックと一貫性を考慮して既に構築されているため、シミュレーション中にデータの一貫性を維持する責任をデータベースに置くことができます。sqlite のようなものは、ネットワーク化されていないソリューションの一部として機能する可能性があります。

于 2009-06-30T19:15:30.420 に答える
0

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

プレイヤー A は惑星について知る必要があるため、ワールドにその惑星を要求します (実装によって異なります)。World は、プレイヤー A が要求した Planet オブジェクトへの参照を返します。プレイヤー A が変更を行うことにしたので、変更を行いました。建物を追加するとしましょう。建物を惑星に追加する方法は同期されているため、一度に 1 人のプレイヤーしか追加できません。建物はそれ自体の建設時間を (もしあれば) 追跡するので、Planet の add building メソッドはほぼ即座に解放されます。このようにして、複数のプレイヤーが互いに影響を与えることなく同時に同じ惑星に関する情報を求めることができ、プレイヤーはラグをほとんど発生させずにほぼ同時に建物を追加できます。2 人のプレイヤーが建物を置く場所を探している場合 (それがゲームの一部である場合)、

これがあなたの質問に答えていない場合は申し訳ありませんが、正しく理解できたかどうかはわかりません。

于 2009-06-19T03:58:42.780 に答える
0

パイプとフィルターのアーキテクチャを実装するのはどうですか。パイプはフィルターを接続し、フィルターの速度が十分でない場合は要求をキューに入れます。処理はフィルター内で行われます。最初のフィルターは AI エンジンであり、レンダリング エンジンは後続の一連のフィルターによって実装されます。

タイマー ティックごとに、すべての入力 (Time も入力) と最初のパイプに挿入されたコピーに基づいて、新しい動的な世界の状態が計算されます。

最も単純なケースでは、レンダリング エンジンは単一のフィルターとして実装されます。入力パイプから状態のスナップショットを取得し、静的な状態と一緒にレンダリングします。ライブ ゲームでは、パイプに複数のステートがある場合、レンダリング エンジンはステートをスキップしたい場合がありますが、ベンチマークを実行している場合やビデオを出力している場合は、すべてのステートをレンダリングする必要があります。

レンダリング エンジンを分解できるフィルタが多いほど、並列処理が向上します。AI エンジンを分解することさえ可能かもしれません。例えば、動的な状態を変化の速い状態と変化の遅い状態に分けたいと思うかもしれません。

このアーキテクチャは、多くの同期を必要とせずに優れた並列処理を提供します。

このアーキテクチャの問題は、ガベージ コレクションが頻繁に実行され、毎回すべてのスレッドがフリーズし、マルチスレッドから得られる利点が失われる可能性があることです。

于 2009-06-21T16:49:29.153 に答える
0

あなたが探している動作を完全に理解しているかどうかはわかりませんが、状態変更スレッド/キューのようなものが必要なようで、すべての状態変更が単一のスレッドで処理されます。

SwingUtilities.invokeLater() や SwingUtilities.invokeAndWait() のような API を作成して、状態変更キューが状態変更要求を処理できるようにします。

それがGUIにどのように反映されるかは、探している動作に依存すると思います。つまり、現在の状態が $0 であるためお金を引き出すことができない、または引き出しリクエストが処理されたときにアカウントが空だったことをユーザーに通知します。(おそらくその用語ではありません;-))

于 2009-06-11T17:18:02.620 に答える
0

最も簡単な方法は、シミュレーションを EDT で実行できるほど高速にすることです。動作するプログラムを優先してください!

2 スレッド モデルの場合、ドメイン モデルをレンダリング モデルと同期することをお勧めします。レンダー モデルは、ドメイン モデルから取得したデータを保持する必要があります。

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

レンダー モデルは異なる深さを持つことができます。極端な例では、画像の場合、更新操作は単一の参照を新しい画像オブジェクトに置き換えるだけです (これは、たとえば、サイズ変更やその他の表面的な相互作用をうまく処理しません)。アイテムに変更があるかどうかをわざわざチェックせずに、すべてを更新するだけかもしれません。

于 2009-06-11T17:35:24.060 に答える
0

ゲームの状態をすばやく変更できる場合 (何を変更すればよいかがわかれば)、ゲームの状態を他の Swing モデルと同様に扱い、EDT でのみ状態を変更または表示できます。ゲームの状態の変更が速くない場合は、状態の変更を同期して Swing ワーカー/タイマー (EDT ではなく) で行うか、EDT と同様に扱う別のスレッドで行うことができます (その時点で、を使用しBlockingQueueて変更要求を処理する方法を参照してください)。後者は、UI がゲームの状態から情報を取得する必要がなく、代わりにリスナーまたはオブザーバーを介してレンダリングの変更を送信する場合に便利です。

于 2009-06-11T19:35:39.653 に答える