Unity Undo&Redo(撤销&重做)功能

需求

撤销与重做功能

思考

关于记录的数据的两点思考:

  1. 记录操作
  2. 记录影响显示和逻辑的所有数据

很显然这里就要考虑取舍了:
记录操作 这种方案只需要记录每一步的操作,具体这个操作要怎么渲染和实现出来完全需要自己去实现,这种方式的好处是记录的数据量比较小,节省内存,但业务逻辑复杂,适合复杂庞大的数据记录
记录影响显示的所有数据 这种方案要记录所有影响显示和逻辑的所有数据,无疑,这种数据量肯定是比第一种大的,内存占用会比较大,但是业务逻辑简单

这里由于我的数据量不大所以选择了第二种方式来进行处理

首先肯定要有一个Manager来负责管理数据并实现撤销和重做的分发(我命名为UndoManager),还要有一个数据对象来记录每一步的数据,由于需要记录的信息未知,可能是一个json也可能是一个对象,所以这里用最抽象的接口来表示每一步的数据信息,后续怎么处理这份数据完全根据项目需求自己定制(我命名为IRecordData)

Redo Undo的功能都是先进后出的数据结构,这种数据结构对应到程序世界的话刚好对应的是Stack,这里就先用Stack来进行思考
定义有两个Stack 分别是UndoStack 和 RedoStack 分别来存储待撤销记录和待重做记录,假设这两个Stack的当前状态如下:
在这里插入图片描述
假设这个时候来个一个新的步骤Step4 ,这个时候Step4需要记录,记录的过程如下:

在这里插入图片描述
假设我现在需要撤销一步回到Step3,具体过程如下:
在这里插入图片描述

假设现在我又想重做上一步,想回到Step4,具体过程如下:
在这里插入图片描述
至此我们的记录、撤销、重做的核心逻辑就已经基本梳理清楚了,但是发现Record还有一个小问题,问题如下:
在这里插入图片描述
当我们RedoStack中存在数据时,如果记录新的步骤Step4,成功之后我们会发现,现在Undo会回到Step1,Redo会回到Step2,这样流程就乱了,所以,修改一个Record的逻辑,修改后如下:
在这里插入图片描述
有两种处理方案,方案一为每次记录新数步骤时清空RedoStack,方案二为每次记录新数据时将RedoStack中的数据尽数移动到UndoStack中,可根据需求选择。

由于我们选择的数据记录是记录影响显示和逻辑的所有数据 本身内存占用就比记录操作要大,如果再对这两个Stack的容量不做限制,就可能会浪费很多的内存,导致程序内存占用过高。所以我们需要对UndoStack和RedoStack做容量限制,超过容量时最早进入Stack的数据直接丢弃,但Stack本身是先进后出的数据结构,显然不能支持这种操作,所以权衡利弊之后,我选择将Stack改为LinkedList,具体的操作流程是一样的。

实现

下面直接展示源码:

using System.Collections.Generic;

namespace S
{
    public interface IRecordData
    {
    }

    public class UndoManager
    {
        private LinkedList<IRecordData> undoLinkedList;
        private LinkedList<IRecordData> redoLinkedList;
        private IRecordData currentData;

        /// <summary>
        /// Undo数量
        /// </summary>
        public int undoCount => undoLinkedList == null ? 0 : undoLinkedList.Count;

        /// <summary>
        /// Redo数量
        /// </summary>
        public int redoCount => redoLinkedList == null ? 0 : redoLinkedList.Count;

        /// <summary>
        /// 记录的最大容量  <=0时 代表无限容量
        /// </summary>
        public int maxCount=0;

        public UndoManager()
        {
            undoLinkedList = new LinkedList<IRecordData>();
            redoLinkedList = new LinkedList<IRecordData>();
            currentData = null;
        }

        /// <summary>
        /// 重做一步
        /// </summary>
        /// <returns></returns>
        public IRecordData Redo()
        {
            if (currentData != null)
            {
                undoLinkedList.AddLast(currentData);
            }

            if (redoLinkedList.Count == 0)
            {
                currentData = null;
            }
            else
            {
                currentData = redoLinkedList.Last.Value;
                redoLinkedList.RemoveLast();
            }

            return currentData;
        }

        /// <summary>
        /// 撤销一步
        /// </summary>
        /// <returns></returns>
        public IRecordData Undo()
        {
            if (currentData != null)
            {
                redoLinkedList.AddLast(currentData);
            }

            if (undoLinkedList.Count == 0)
            {
                currentData = null;
            }
            else
            {
                currentData = undoLinkedList.Last.Value;
                undoLinkedList.RemoveLast();
            }

            return currentData;
        }

        /// <summary>
        /// 记录
        /// </summary>
        /// <param name="recordData">记录对象</param>
        /// <param name="overrideRedo">是否覆盖Redo链表</param>
        public void Record(IRecordData recordData,bool overrideRedo=true)
        {
            if (currentData != null)
            {
                undoLinkedList.AddLast(currentData);
            }

            if (overrideRedo)
            {
                redoLinkedList.Clear();
            }
            else
            {
                while (redoLinkedList.Last != null)
                {
                    IRecordData value = redoLinkedList.Last.Value;
                    redoLinkedList.RemoveLast();
                    undoLinkedList.AddLast(value);
                }
            }

            currentData = recordData;

            if (maxCount>0&&undoCount > maxCount) //容量维护
            {
                undoLinkedList.RemoveFirst();
            }
        }

        /// <summary>
        /// 清空undo&redo
        /// </summary>
        public void Clear()
        {
            undoLinkedList.Clear();
            redoLinkedList.Clear();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

司军礼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值