我一直在尝试在我用 Java 编码的游戏中实现和撤消/重做系统。我正在采取在每次移动后序列化游戏状态的方法。我有办法将序列化的对象保存在堆栈上并访问它们以进行撤消/重做吗?
4 回答
游戏编程与“企业应用程序”编程或“webapp 编程”没有太大关系。
所以这取决于你正在开发什么样的游戏,但你的方法和给出的方法与“真实”游戏中游戏状态的保存方式完全无关。性能将非常糟糕,您最终将使用大量内存和磁盘空间。
伟大的游戏,例如暴雪的魔兽争霸 III或微软的帝国时代,只存储重现任何游戏状态所需的用户输入。
这就是他们如何实现超高效的网络传输,即使玩家拥有数百或数千个对象,这就是他们如何实现微小的保存游戏文件(可用于继续游戏或进行重播):想要低延迟?简单,只需通过 UDP 发送玩家的击键和鼠标点击以及这些输入发生的时间。
这与撤消/重做有什么关系?琐碎的...
想象一下,自从您的游戏开始以来,您已经有 200 个用户输入,并且想要撤消最后一步:您从头开始游戏并提供 199 个第一个输入。而已。完美撤消。
当然你只需要在重放这 199 个步骤时重放逻辑,而不需要更新你的视图。对于大多数游戏来说,逻辑只占总时间的一小部分(大部分时间都花在视图中)。所以重放这些输入非常快。
去过也做过 :)
现在,如果您想要另一种方法来进行无限撤消/重做,那么必须对“OO over immutable objects”说些什么:我也编写了具有无限撤消/重做的游戏工具编辑器(如地图编辑器)。诀窍是只使用不可变对象。然后保持以前的状态很容易:只是层次结构顶部的对象('gamemap239')。
因为您只使用过不可变对象,所以“新”游戏地图与之前的游戏地图共享其 99.9% 的对象,因此内存使用率低得离谱,可以保持大量状态。
我已经在非游戏的商业软件中成功地应用了这种“OO over immutable objects for Effective undo/redo”,这确实是一项了不起的技术。
现在这取决于您正在做的游戏:当然,对于扫雷游戏,您可以使用 Swing 和常规 Java 编程技术。但是对于更高级的游戏,常规的 Java 集合和 API 以及常规的 entreprise/webapp/fatclient 编程技术根本无法满足要求。
Java提供了一个javax.swing.undo.UndoManager
本质上是一堆UndoableEdit
's(即命令)的东西。如果您将之前和之后的状态保存在 中UndoableEdit
,那么每当您发出撤消或重做时,只需在撤消或重做时恢复该状态即可。
如果你不能使用上面的swing类,你可以使用一个基本的堆栈或列表,只要你确保在执行不同的编辑/命令时执行正确的操作。例如,如果您要撤消一些操作,然后进行不同的编辑,则先前撤消的命令将会丢失。
Kaleb 的建议很棒(+1),但没有提供细节。我无法在评论中提供足够的细节,也没有看到关于如何使用它的良好、简单的解释。
可撤消的编辑由 3 个重要部分组成——构造函数、撤消方法和重做方法。诀窍是让每个“编辑”都非常简单。根据您的游戏的复杂程度,“移动”之间可能需要进行数百次编辑。
假设在基于回合的模拟中,您移动一支军队——这是一个编辑——然后计算机移动它的所有军队(每个军队都是一个编辑)。一个随机的天气条件——另一个编辑,一个城市建造了一个新的坦克——另一个编辑,......
每种类型的编辑都是不同的对象类型。假设您要将一个单位从 (35,150) 移动到 (35, 151)。采取行动可能如下所示:
undoManager.addEdit( new MoveOne(map, new Point(35,150), new Point(35,151)) );
撤消此编辑将只是:
undoManager.undo();
它会自动撤消最后一次“重要”编辑(人类玩家所做的最后一次)之前的所有内容
这是“移动”类的样子:
MoveOne extends AbstractUndoableEdit {
private final Point start;
private final Point finish;
private final Map map;
public MoveOne(Map pMap, Point pStart, Point pFinish) {
start=pStart;
finish=pFinish;
map=pMap;
redo();
}
public redo() {
MapObject piece=map.getObjectAt(start);
map.moveObject(piece, finish);
}
public undo() {
MapObject piece=map.getObjectAt(finish);
map.moveObject(piece, start);
}
}
像这样的一堆小动作可能会累积成一个“人类”动作。这些“人类”可以用 isSignificant 标记来标记——这样你就可以轻松地撤消到最后一个“重要”事件。游戏中的其他一切都变成了简单地操纵这些微不足道的小对象的问题。
我喜欢这种模式,并支持@Kaleb 指出 java 现在在 JDK 中支持它!
我实现了可重用的撤消/重做并在此处发表了博客: https ://odoepner.wordpress.com/2020/10/22/undo-redo-in-java-using-protostuff-serialization-and-binary-diffs/