用Unity实现简单的撤销/重做(undo/redo)功能(附源项目)
用Unity实现简单的撤销/重做(undo/redo)功能(附源项目)
在Unity开发中,撤销(Undo)和重做(Redo)功能是许多项目中常见的需求。本文将通过一个简单的示例,详细介绍如何在Unity中实现这一功能。
一、了解撤销重做实现原理
查阅资料,发现基本上的实现都是基于栈(stack)这个数据结构,栈具有先入后出的特点,这个特点就很好的适应了撤消重做的需求,因为我们总是会撤回到离我们最近的前一步,而不是最久远、最先前的那一步。
我们在项目中将设置两个栈,分别存储undo的内容和redo的内容,也就是两个栈并行的来工作。
下面我将通过一个简单的操作流程,随之编写代码和界面,实现一个很简单的撤消重做demo。
二、在unity中编辑UI交互界面
这一步相信大家自己就可以完成了,我也是初学者,所以我还是大体说一下,因为后面会用到按钮点击事件调用函数。
功能分析:点哪个按钮,小物块就移动到哪个按钮的下面。创建UI-Legacy-Button,复制两个出来,依次摆好。
小物块reset一下位置(0,0,0)
三、分析工作流与编写脚本
(一)分析工作流
现在,假装你的老师/上司给了你一个任务,你要做的是将小物块按左-中-右的顺序移动,是的,假装你有这样一个很简单的任务。
第一步我要点left按钮,可是,我不小心点错了,点成了right,这时候我需要撤回一下,让他回到原位置,也就是回到中心,这是一步错误,而在现实中,人们不一定只走错一步路,还可能连续错好几次,这时候用户就会疯狂点撤回,也就是说,我们的undo栈是需要保存许多步的。(此处先不分析最大步数)这样用户在连续撤回的时候,才能逐步后退,直到他退到了某个之前的状态。
同理,假如我们做错了5步,但是不小心按了六下撤回按钮,多按了一次,那我想再重做一次,回到第五次按撤回按钮那个时候的状态,这就是redo栈。也就是说,你按了撤回,回到了上一步,但现在,此时这个状态,是要被保存进redo栈的。以下是完整的图示:
注意每当我们按下Left等位移按钮时,除了记录上一个状态(并压入undo栈)和设置新状态时,还需要将redo栈清空,因为我这一步是最新添加的操作,我没有“下一步”,所以redo为空很好理解。还有其他操作redo栈中的数据以保证逻辑正确的方法,但我觉得这是最通用且最好理解的一个。你可以用office或者什么绘画软件试一下,看是不是你引入新操作后,重做的那个按钮变灰了。
其中六、七步是逐步分析了我们连续撤回和重做时的出入栈情况,注意,这个过程中一直没有新操作引入。
(二)位置移动(新操作)的代码
现在我们大体清楚了工作流,开始写脚本。
我们create一个C#脚本,命名为UndoRedoManager.cs,挂载到场景中的小物块上。
定义两个栈,注意,c#要求Stack
private Stack<Vector3> undoStack = new Stack<Vector3>();
private Stack<Vector3> redoStack = new Stack<Vector3>();
private Vector3 currentPosition;
别忘了在start中初始化一下我们的current。
void Start()
{
currentPosition = transform.position;
}
我们先来写left函数,并绑定到button上,另外两个按钮你可以照猫画虎。
让我们看看他做了什么?首先,把小物块的位置改了,保证你显示上没问题,他现在确实显示在屏幕左侧了;其次,将当前的current压入undo栈,请问现在的current值是什么?还是(0,0,0),因为你没让他变过。
压完了,我们才修改current的值,改为当前的,如你所见,现在的current应该是(-7,0,0)了。
最后,不要忘记,这是一个新操作!我们需要清空整个redo栈!
public void CubeTransLeft()
{
gameObject.transform.position = new Vector3(-7, 0, 0);
//一定要先压入,然后再改变current数值
undoStack.Push(currentPosition);
currentPosition = gameObject.transform.position;
redoStack.Clear();
}
在button的onclick中,拖拽、选择刚刚编写的函数,如此以配置按钮交互事件。
你可以自己试着写写center和right,不想写也没关系,我附上了源文件。
(三)undo&redo的代码
让我们用快捷键Z和Y来实现,在update中:
void Update()
{
//使用键盘快捷键触发撤销和重做
if (Input.GetKeyDown(KeyCode.Z))
{
Undo();
}
if (Input.GetKeyDown(KeyCode.Y))
{
Redo();
}
}
所调用的undo方法:
如果只看代码不好理解,可以对照刚刚的图示一起看。
分为非空和空的情况,这里如果为空的话我直接debug.log了,其实你完全可以做一个UI窗口弹出,或者改成按钮,当空的时候按钮就禁用的模式。
首先把当前状态压入重做栈,因为这是你“上一步”的“下一步”,它存在,必须保留下来以防用户点击“重做”。
在undo栈中,取出最顶层的,放到current里面,并将物体的position设置为current。
public void Undo()
{
if (undoStack.Count > 0)
{
// 将当前状态推入重做堆栈
redoStack.Push(currentPosition);
// 从撤销堆栈弹出上一个状态并应用
currentPosition = undoStack.Pop();
transform.position = currentPosition;
}
else
{
Debug.Log("No actions to undo");
}
}
redo方法:
同理。current压入undo栈,因为这是你“下一步”的“上一步”。同样current取出最顶层,位置也改变。
public void Redo()
{
if (redoStack.Count > 0)
{
// 将当前状态推入撤销堆栈
undoStack.Push(currentPosition);
// 从重做堆栈弹出下一个状态并应用
currentPosition = redoStack.Pop();
transform.position = currentPosition;
}
else
{
Debug.Log("No actions to redo");
}
}
测试运行一下!完成!你可以在此流程的基础上添加任何你需要的变量类型、操作类型。
文件地址:【免费】用Unity实现简单的撤销/重做(undo/redo)功能资源-CSDN文库