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 ある場合を想像してください。