我正在 Java 平台上开发一个实时战略游戏克隆,我有一些关于放置位置和如何管理游戏状态的概念性问题。游戏使用 Swing/Java2D 作为渲染。在当前的开发阶段,没有模拟和人工智能,只有用户能够改变游戏的状态(例如,建造/拆除建筑物、添加-移除生产线、组装车队和设备)。因此,可以在事件调度线程中执行游戏状态操作,而无需任何渲染查找。游戏状态还用于向用户显示各种聚合信息。
但是,由于我需要引入模拟(例如,建筑进度、人口变化、舰队移动、制造过程等),在 Timer 和 EDT 中更改游戏状态肯定会减慢渲染速度。
假设每 500 毫秒执行一次模拟/AI 操作,我使用 SwingWorker 进行大约 250 毫秒长度的计算。我如何确保模拟和可能的用户交互之间的游戏状态读取不存在竞争条件?
我知道模拟结果(即少量数据)可以通过 SwingUtilities.invokeLater() 调用有效地移回 EDT。
游戏状态模型似乎太复杂以至于无法在任何地方都使用不可变值类。
是否有相对正确的方法来消除这种读取竞争条件?也许在每个计时器滴答声上进行完整/部分游戏状态克隆,或者将游戏状态的生存空间从 EDT 更改为其他线程?
更新:(根据我给出的评论)该游戏由 13 名 AI 控制的玩家、1 名人类玩家组成,并且有大约 10000 个游戏对象(行星、建筑物、设备、研究等)。例如,游戏对象具有以下属性:
世界(行星、玩家、舰队……) 行星(位置、所有者、人口、类型、 地图、建筑物、税收、分配……) 建筑(位置、启用、能源、工人、健康……)
在一个场景中,用户在这个星球上建造了一座新建筑。这是在 EDT 中执行的,因为需要更改地图和建筑物集合。与此同时,每 500 毫秒运行一次模拟,以计算所有游戏星球上建筑物的能量分配,这需要遍历建筑物集合以进行统计收集。如果计算了分配,则将其提交给 EDT,并分配每个建筑物的能量场。
只有人类玩家交互具有此属性,因为 AI 计算的结果无论如何都会应用于 EDT 中的结构。
通常,75% 的对象属性是静态的,仅用于渲染。其余部分可以通过用户交互或模拟/人工智能决策进行更改。还确保在前一个步骤写回所有更改之前不会启动新的模拟/AI步骤。
我的目标是:
- 避免延迟用户交互,例如用户将建筑物放置在地球上,并且仅在 0.5s 后才获得视觉反馈
- 避免用计算、锁等待等阻塞 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() 和distributePower() 之间。现在想象一下,在游戏模型的各个部分之间有 50 个这样的重叠。