【问题标题】:Object Serialization for Undo/Redo Functionality Java撤消/重做功能Java的对象序列化
【发布时间】:2011-01-26 15:18:23
【问题描述】:

我一直在尝试在我用 Java 编码的游戏中实现和撤消/重做系统。我正在采取在每次移动后序列化游戏状态的方法。我有办法将序列化的对象保存在堆栈上并访问它们以进行撤消/重做吗?

【问题讨论】:

    标签: java serialization stack undo


    【解决方案1】:

    游戏编程与“企业应用程序”编程或“网络应用程序编程”没有太大关系。

    因此,这取决于您正在开发的游戏类型,但您的方法和目前给出的方法与“真实”游戏中游戏状态的保存方式绝对无关。性能将非常糟糕,您最终将使用 大量 内存和磁盘空间。

    伟大的游戏,例如暴雪的魔兽争霸 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 编程技术根本无法满足要求。

    【讨论】:

      【解决方案2】:

      Java 提供了一个javax.swing.undo.UndoManager,它本质上是一堆UndoableEdit(即命令)。如果您将之前和之后的状态保存在 UndoableEdit 中,那么每当您发出撤消或重做时,只需在撤消或重做时恢复该状态即可。

      如果你不能使用上面的swing类,你可以使用一个基本的堆栈或列表,只要你确保在执行不同的编辑/命令时执行正确的操作。例如,如果您要撤消一些操作,然后进行不同的编辑,那么之前撤消的命令将会丢失。

      【讨论】:

        【解决方案3】:

        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 中支持它!

        【讨论】:

          【解决方案4】:

          我实现了可重用的撤消/重做,并在博客中介绍了它: https://odoepner.wordpress.com/2020/10/22/undo-redo-in-java-using-protostuff-serialization-and-binary-diffs/

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-10-03
            • 1970-01-01
            • 1970-01-01
            • 2018-09-09
            • 2014-05-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多