我正在 Java 平台上开发一个实时战略游戏克隆,我有一些概念性的问题关于放置在哪里以及如何管理游戏状态。游戏使用Swing/Java2D作为渲染。在目前的开发阶段,没有模拟,也没有人工智能,只有用户可以改变游戏的状态(例如, build /拆除建筑物、增减生产线、组装车队和设备)。因此,游戏状态操作可以在事件分派(dispatch)线程中执行,无需任何渲染查找。游戏状态还用于向用户显示各种聚合信息。
但是,由于我需要引入模拟(例如,建筑进度、人口变化、舰队移动、制造过程等),在 Timer 和 EDT 中更改游戏状态肯定会减慢渲染速度。
假设模拟/AI 操作每 500 毫秒执行一次,我使用 SwingWorker 进行大约 250 毫秒的计算。我如何确保在模拟和可能的用户交互之间没有关于游戏状态读取的竞争条件?
我知道模拟的结果(少量数据)可以通过 SwingUtilities.invokeLater() 调用有效地移回 EDT。
游戏状态模型似乎过于复杂以至于无法在所有地方使用不可变值类。
是否有相对正确的方法来消除这种读取竞争条件?也许在每个计时器滴答上进行完整/部分游戏状态克隆,或者将游戏状态的生存空间从 EDT 更改为其他线程?
更新:(来 self 给出的评论) 该游戏由 13 名 AI 控制的玩家、1 名人类玩家运营,拥有约 10000 个游戏对象(行星、建筑物、设备、研究等)。例如,游戏对象具有以下属性:
World (Planets, Players, Fleets, ...)
Planet (location, owner, population, type,
map, buildings, taxation, allocation, ...)
Building (location, enabled, energy, worker, health, ...)
In a scenario, the user builds a new building onto this planet. This is performed in EDT as the map and buildings collection needs to be changed. Parallel to this, a simulation is run on every 500ms to compute the energy allocation to the buildings on all game planets, which needs to traverse the buildings collection for statistics gathering. If the allocation is computed, it is submitted to the EDT and each building's energy field gets assigned.
Only human player interactions have this property, because the results of the AI computation are applied to the structures in EDT anyway.
In general, 75% of the object attributes are static and used only for rendering. The rest of it is changeable either via user interaction or simulation/AI decision. It is also ensured, that no new simulation/AI step is started until the previous one has written back all changes.
My objectives are:
Options:
All of these have advantages, disadvantages and causes to the model and the game.
Update 2: I'm talking about this game. My clone is here. The screenshots might help to imagine the rendering and data model interactions.
Update 3:
I'll try to give a small code sample for clarify my problem as it seems from the comments it is misunderstood:
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 个这样的重叠。
最佳答案
这听起来像是可以从客户端/服务器方法中获益:
播放器是客户端 - 交互和渲染发生在这一端。所以玩家按下一个按钮,请求就会发送到服务器。服务器回复回来,玩家状态更新。在这些事情发生之间的任何时候,屏幕都可以重新绘制,它反射(reflect)了客户端当前知道的游戏状态。
人工智能同样是一个客户端——它相当于一个机器人。
模拟就是服务器。它在不同时间从其客户端获取更新并更新世界状态,然后适本地将这些更新发送给每个人。这是它与您的情况相关的地方:模拟/AI 需要一个静态世界,许多事情同时发生。服务器可以简单地排队更改请求并在将更新发送回客户端之前应用它们。因此,就服务器而言,游戏世界实际上并没有实时变化,它会在服务器决定实时变化时发生变化。
最后,在客户端,您可以通过进行一些快速的近似计算并显示结果(满足即时需求),然后在按下按钮时显示更正确的结果,从而避免按下按钮和看到结果之间的延迟服务器开始与您交谈。
请注意,这实际上不必以互联网上的 TCP/IP 方式实现,只是有助于从这些方面考虑它。
或者,您可以将在模拟期间保持数据一致性的责任放在数据库上,因为它们在构建时已经考虑到了锁定和一致性。 sqlite 之类的东西可以作为非联网解决方案的一部分。
关于java - 面对 EDT 如何管理游戏状态?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/981920/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/