前言
我们在UE4编辑器开发中,各种编辑器的操作经常涉及到(Ctr + Z, Undo)和(Ctr + Y, Redo).那么UE4的编辑器这套回撤系统怎么进行的?
针对UObject的回撤系统
首先UE4内部有一套针对UObject系统的回撤系统,不过这套系统针对的基本对象为UObject对象,我们撤回的是某个UObject对象的某个属性,使某个属性回到修改之前,也就是所谓的撤回。想要实现完备的UObject属性撤回得遵守下面几点
UObject创建标记
RF_Transactional(控制UObject是否能回撤)和依赖的UObject链必须存在GetTransientPackage()
一个UObject的EObjectFlags得标记为 “RF_Transactional”,才具备回撤功能。
UTestObject* TestObject = NewObject<UTestObject>(GetTransientPackage(), NAME_None, RF_Transactional);
或者
TestObject->SetFlags(RF_Transactional)
具备撤回性质属性得标记UPROPERTY(控制UObject的一个属性是否能回撤)
如果你想要你的UObject的属性在回撤系统中起作用,那么你得为这个属性添加UPROPERTY()标记
UCLASS()
class TESTCTRZ_API UTestObject : public UObject
{
GENERATED_BODY()
public:
UPROPERTY()
float a;
float b;
};
如上面float a就在某次撤回中值就变为修改之前。
一个问题来了,如果我们希望UOBject的一个属性能把暴露给编辑器,也就是得UROPERTY(EditAnywhere)",但是处于某些因素,我们并不希望这个属性进入回撤系统,那怎么办?UE4有个UROPERTY标记,“NonTransactional”,可以让我们的属性不进入撤回系统,比如下面"float a"暴露属性给编辑器修改的同时又不进入撤回系统
UCLASS()
class TESTCTRZ_API UTestObject : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, NonTransactional)
float a;
};
UObject的一次修改记录的界定
并不是我们每次修改一个UObject的属性,都会把这次修改添加到修改记录中(编辑器Undo History),而且我们也需要如果界定何为“一次修改记录”,比如我们移动一个Actor一个单位是一次修改记录,还是移动一百个单位是一次修改距离,还是说从点鼠标选择一个Actor移动一段距离后释放鼠标才视为一次修改记录?这些一次修改记录都添加到UE4编辑器Undo History。
所以“一次修改记录”存在一个界定,UE4提供了内置API让我们可以把UObject属性的每次修改灵活的添加到编辑器Undo History
UE4内部有两种限定“一次修改记录”的方法
(1)GEditor->BeginTransaction, GEditor->EndTransaction和UObject的Modify
GEditor->BeginTransaction(FText::FromString("TestObject"));
TestObject->Modify();
TestObject->a = 100.0f;
GEditor->EndTransaction();
在GEditor->BeginTransaction, GEditor->EndTransaction之间的"xxxxx"内容即是UObject的修改内容,当然UObject修改前的调用Modify函数将UObject的属性修改Save到TransactionBuffer
BeginTransaction里的参数为自定义的修改记录的名称。像上面图里的“Click on Actors‘, "Move Actors"
(2)FScopedTransaction和UObject的Modify
FScopedTransaction Transaction(FText::FromString("TestObject"));
TestObject->Modify();
TestObject->a = 100.0f;
FScopedTransaction灵活性比GEditor->BeginTransaction, GEditor->EndTransaction低得多,也就是FScopedTransaction变量声明到退出所在的局部函数的时候被视为“一次修改”。
撤回之后的动作(PostEditUndo)
按照上面的步骤之后,我们按住CtrZ或者CtrY将UObject中被标记的属性值可以撤回或者后退,然而单单是属性值
回撤并不满足我们的需求,我们往往需要用回撤的属性值做些事情以达到UObject完全的回撤。这个时候就用了“PostEditUndo”函数,当我们按照我们的各种标记后,并做了修改记录,按住CtrZ或者CtrY将会触发UObject的回撤函数PostEditUndo,这个函数是执行在UObject的属性值回撤之后的。
UCLASS()
class TESTCTRZ_API UTestObject : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, NonTransactional)
float a;
public:
#if WITH_EDITOR
virtual void PostEditUndo() override;
#endif
};
void UTestObject::PostEditUndo()
{
Super::PostEditUndo();
//do something
}
撤回之前的动作(PreEditUndo)
有PostEditUndo,自然就有PreEditUndo, 顾名思义就是在撤销UObject的属性之前调用的函数
class UTestObject: public UObject:
{
#if WITH_EDITOR
virtual void PreEditUndo() override;
#endif
};
void UTestObject::PreEditUndo()
{
Super::PreEditUndo();
//do something
}
资料参考
[1].https://answers.unrealengine.com/questions/412356/how-does-the-transaction-undoredo-system-work.html
[2].https://answers.unrealengine.com/questions/477593/view.html