你的浏览器不支持canvas

让我们携手共创奇迹!

UE Actor的构造

Date: Author: LBD

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。转载请注明来自WindCrazy

这是一篇关于UE中蓝图Construction Script和C++中Constructor、OnConstruction

官方文档参考:Actor Lifecycle Construction Script

Constructor

C++的构造函数是要比OnConstructionConstruction Script、C++Beginplay、蓝图Beginplay要早的

Construction Script & OnConstruction

在蓝图编辑器中访问构造脚本

在 C++ 中,它由 UserConstructionScript 方法表示:

/**
 * Construction script, the place to spawn components and do other setup.
 * @note Name used in CreateBlueprint function
 */
UFUNCTION(BlueprintImplementableEvent, meta=(BlueprintInternalUseOnly = "true", DisplayName = "Construction Script"))
ENGINE_API void UserConstructionScript();

这个方便的工具对于定义如何构建 Actor 非常有用。一个典型的用例是当您希望能够设置 Actor 的属性并看到它自动地根据编辑器中的这些参数进行更新时。例如,我们可以使用它来构建一个以边数作为属性的多边形Actor,每次编辑边数时,我们都可以看到多边形在编辑器中重建和更新(而不是在运行时,但是在运行前,会运行一次构造脚本)。

但是,虽然它非常有用,但如果使用不当,也非常危险,甚至导致编辑器崩溃。

为了避免任何潜在的问题,我们需要:

  • 知道何时调用构造脚本
  • 了解它如何导致编辑器崩溃,使项目无法编辑,甚至一打开就崩溃
  • 学习如何正确地使用它(构造脚本并不总是唯一的办法)

OnConstructionUserConstructionScript统一由ExecuteConstruction函数调用并且UserConstructionScript调用要早于OnConstruction

编辑器何时调用构造脚本

这是构造脚本最有趣的一点:它是在编辑器中调用的,而不是在运行时调用的。

并且在编辑器中,可以多次调用它:

  • 当Actor产生(Spawn)时 UWorld::SpawnActor -> Actor->PostSpawnInitialize -> AActor::FinishSpawning -> ExecuteConstruction
  • 当Actor移动时PostEditMove事件):在这种情况下,当此Object移动时它将被调用多次 PostEditMove -> RerunConstructionScripts -> ExecuteConstruction
  • 当Actor的属性发生更改时PostEditChangeProperty事件)PostEditChangeProperty -> RerunConstructionScripts -> ExecuteConstruction

正如我们所看到的,它可以被频繁调用。虽然它确实很有用(如果我更改任何属性,我可以直接看到构造脚本正在重建的对象),但也出现了一些缺点。

另外

/** Called after an actor has been moved in the editor */
	virtual void PostEditMove(bool bFinished);
/**
	 * Called when a property on this object has been modified externally
	 *
	 * @param PropertyThatChanged the property that was modified
	 */
	virtual void PostEditChangeProperty( struct FPropertyChangedEvent& PropertyChangedEvent);

重载时(参考PaperGroupedSpriteComponentVPRootActor)一般添加#if WITH_EDITOR与#endif

	// UObject interface
#if WITH_EDITOR
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
	// End of UObject interface
#if WITH_EDITOR
	virtual void PostEditMove(bool bFinished) override;
#endif

滥用的主要威胁

使用构造脚本时存在两个主要威胁。第一个很明显,第二个则比较棘手。

第一个是对项目性能的影响:即使只在编辑器中调用Construction Script,如果Construction Script太复杂,将无法再在编辑器中移动Actor。假设运行 ConstructionScript 需要一秒钟,如果尝试移动 Actor,则在移动 Actor 时,Construction Script 将被调用数十次,因此编辑器可能卡数十秒。那么对于美术同学来说,记住不要移动这个特定对象将是一件非常痛苦的事情,因为总是会对这个Actor摆放的位置不满意。

第二个威胁更加棘手。它涉及构造脚本的循环调用:如果属性的更改导致Construction Script的循环调用,编辑器将崩溃。原理如下:假设我们在一个 Actor 上有一个属性 A 和一个属性 B,并且Construction Script声明如果 A 改变,则 B 等于 A+1,如果 B 改变,则 A 等于等于B+1。理论上,这可能会由于无限循环而造成的崩溃。幸运的是,这种特定情况是由编辑器管理的:Construction Script无法调用自己(即使它更改了A/B属性)。但是,当您的演员具有ChildActorComponent并且这个Actor和这个ChildActorComponent之间存在循环关系时,编辑器将无能为力,它只会崩溃(根据Isara的经验,不是每次都会崩溃,这使得诊断变得更加困难)。因此,如果父级的构造脚本更改了子级的属性,并且子级的构造脚本更改了父级的属性,我们将进行循环调用(在两个不同的构造脚本之间)。

建议

构建脚本的使用建议如下:

  • 如果没有必要,请避免使用它们:它是一个自动化工具,仅当您有重复性任务需要自动化时才使用它
  • 保持构建脚本简单快速,它只是作为一个方便的工具而不是当成制作一个完全程序化的游戏
  • 避免在 Construction Script 中更改另一个 actor(甚至是子 actor)的属性,如果必须这样做,请记住,对 Construction Script 的循环调用可能会使编辑器崩溃

参考:Be careful with the Construction Script

构造函数和 BeginPlay() 中应该包含什么样的代码?


对于本文内容有问题或建议的小伙伴,欢迎提出问题