文章

UE All about pointers

使用这些指针类型✔

TSoftObjectPtr

  • 用于通过路径引用可能已加载或未加载的对象。
  • 即使其他关卡的Actor没有加载,也可以指向它们。
  • 当指向资源(如网格)时,可以与异步加载函数一起使用以按需加载它们。
  • 与“Soft Object Reference”蓝图变量类型相同。

TSoftClassPtr

  • 用于引用可能已加载或未加载的类或蓝图类型。
  • 一旦加载,它将为您提供一个可以从中创建实例的类类型。
  • 可以与异步加载函数一起使用以加载它们。
  • 与“Soft Class Reference”蓝图变量类型相同。

TWeakObjectPtr

  • 用于引用已实例化的对象。
  • 如果对象被销毁或垃圾回收,将解析为nullptr。

不要使用这些指针类型❌

FSoftObjectPath

  • 由其他指针类型☝内部使用。
  • 由于它不缓存结果,所以速度较慢。
  • 如果在编辑器中设置,它将指向 UBlueprint 类而不是 UBlueprintGeneratedClass ,这通常不是游戏玩法代码所需的。
  • 编辑器插件的制作者可能需要这个功能。

FSoftClassPath

  • FSoftObjectPath相同,但具有一些与加载类相关的辅助函数。

FSoftObjectPtr

  • TSoftObjectPtr的非模板和非BP暴露版本。

Weak vs Soft Pointers

Weak Pointer

  • 为指向已 实例化的 UObjectGUObjectArray 中 创建 或 设置 索引
  • 指针无需作为 UPROPERTY 即可知道指向的 对象是否已被垃圾回收

Soft Pointer

  • 用 String 代表可能 已加载或未加载 的 对象或资产 的路径。
  • 在非编辑器构建中,内部会存储一个额外的 Weak Pointer 来跟踪 查询和查找操作后的 对象。

Soft Pointers

在几乎所有情况下,您应该使用 TSoftObjectPtrTSoftClassPtr

FSoftObjectPath

实例详细信息/类默认值视图中的 FSoftObjectPath

  • 不要为游戏玩法代码创建自己的FSoftObjectPath变量。
    • 它们不缓存已解析的对象,因此每次查询都会重新搜索对象。
  • 它们还指向蓝图类资产(UBlueprint),而不是生成的类(UBlueprintGeneratedClass,“_C”在路径名中)。
  • 在打包构建中,UBlueprint 会被剥离。应使用 TSoftObjectPtrTSoftClassPtr

  • FSoftObjectPath 主要用于软指针的内部使用。

  • SoftObjectPath 变量可以在蓝图中创建,但只能用于编辑器程序
    • 除非是不想指向 UBlueprintGenerateClass 类,并且不需要缓存。
  • 如果在蓝图中使用,它将只允许选择磁盘上的资产,而不是对象实例。
    • 要指向实例,应使用 TSoftObjectPtr
  • 它不是模板化的,要将选择限制为资产类型
    • 可以使用 MetaClass UProperty 元数据(或使用 TSoftClassPtr 来代替此类功能)。
  • 当对象被解析时,它将使用哈希来搜索它,与普通指针或弱指针相比,这有一点开销。

  • 扩展类通常会缓存它们,因此一旦解析,就不会再次搜索。

FSoftClassPath

  • FSoftObjectPath 相同,但具有一些与加载类相关的辅助函数
  • 建议使用 TSoftClassPtr

FSoftObjectPtr

  • 内部保留一个 FSoftObjectPath 用于查找对象,以及一个 FWeakObjectPtr 用于在找到对象后缓存它。
  • 不是 USTRUCT ,因此无法在蓝图中使用,仅适用于C++
  • 有两个子类型,TSoftObjectPtrTSoftClassPtr,它们分别提供了一个模板化的、BP可见的方法,用于指向对象实例或资产(CDO)。应该使用它们而不是 FSoftObjectPtr
  • 当调用 Get() 时,它会在基类上调用它,首先检查内部的 WeakPtr 是否已经被缓存(在非PIE会话中),否则它将调用 FSoftObjectPath.ResolveObject 来查找对象。

TSoftObjectPtr

实例详细信息/类默认值视图中的 TSoftObjectPtr

在蓝图中创建软对象指针。

  • TSoftObjectPtr 是通用 FSoftObjectPtr 的模板化包装器,可以在蓝图中使用。
  • 可以用于指向具有路径名的任何对象
    • 最适合用于指向可能尚未加载的对象,无论是磁盘上的资源还是任何关卡中的对象。
  • 当指向的对象尚未加载时调用Get(),它将返回nullptr。一旦加载,它将返回具有匹配全名的Actor,并将其缓存以便进一步查询。

💡 由于它可以指向任何 level 中可能尚未加载的对象,因此在跨 level 通信方面非常强大。Actor 位于当前未加载的另一个级别,但属性仍保留其路径值。

如果您想引用已经实例化的Actor,请改用 TWeakObjectPtr

在编辑器中,不能将默认值设置为蓝图类型(请改用 TSoftClassPtr), 但它可以指向数据资产。

该值可以通过选择已加载的Actor在实例详细信息窗口中设置,也可以在当前打开的地图中为任何Actor设置类默认值。

TSoftClassPtr

实例详细信息/类默认值视图中的 TSoftClassPtr。

在蓝图中创建软类指针。

  • TSoftClassPtr 是围绕 FSoftObjectPtr 的模板化包装器,类似于 TSubclassOf,可以在 UProperties 中用于蓝图子类。
  • 使用它来指向蓝图类型UBlueprintGeneratedClass,以便查询它们是否已加载。
  • 也可以用于异步加载类(或同步加载,尽管这会引入延迟)。
  • 但对于DataAssets来说不适用,因为它们不应该被实例化,对于这些情况请使用TSoftObjectPtr

Weak Pointers

弱指针不像软指针那样存储路径名,它们仅引用已经实例化的对象。

底层原理

  • 弱指针仅存储两个内容:

    • 1
      2
      
      int32 ObjectIndex;
      int32 ObjectSerialNumber;
      
  • 当调用 Get() 时,它首先会使用 ObjectIndexGUObjectArray 获取对象(如果存在)。

    • *但是 `ObjectIndex` 只有32位,如果不断生成和销毁actor,它不是很容易耗尽吗?*
      • 理论上是这样,但由于在任何时候最多只有几十万个对象存活,所以这些索引会被重用
      • 正因为如此,我们有 ObjectSerialNumber 来确保在索引重用的情况下,它确实是我们想要的对象。
        • ObjectSerialNumber 仅在第一次将弱指针指向 UObject 时分配,否则为零。它是一个线程安全的递增int32计数器。
    • *当我们用完这些序列号时会发生什么?*
      • 这就是游戏发生致命断言(即崩溃)的地方。
      • 这意味着在用完序列号之前,您最多可以创建 $2147483647$ 个指向唯一 UObject 的新弱指针。
      • 对于大多数用例来说,这应该远远超过了足够,但要考虑到非常长时间运行的进程或弱指针的奇怪用法
  • 在比较序列号之后,它最终检查 UObject 是否有效IsValid()

    • 如果无效(在 PendingKillIsGarbage 的情况下),返回 nullptr , 否则返回对象
    • 因此,您可以确保解引用弱指针总是返回有效的 UObjectnullptr(如果不存在)。
  • 当使用 AddUObject / BindUObject 函数时,所有委托都通过弱指针保持对其 UObject 的引用,因此您可以安全地订阅委托,而无需担心在销毁期间取消订阅。

    • 但是 AddRaw / BindRaw 函数相反,这些函数使用非托管的C++指针,因此要小心处理它们,并尽量不在处理UObject时使用它们。

总而言之,

  • 弱指针 是指向 UObject 的一种出色且安全的方法,在取消引用 UObject 时会产生一些较小的开销。
  • 因为它们处理获取和验证 UObject 的所有内部机制,所以它们也不必是 UPROPERTY

FWeakObjectPtr

  • 不是 USTRUCT,因此不能在蓝图中使用,只能在 C++ 中使用。
  • 一般使用 TWeakObjectPtr 而非 FWeakObjectPtr

TWeakObjectPtr

实例详细信息视图中的 TWeakObjectPtr

  • TWeakObjectPtr 是通用 FWeakObjectPtr 的模板版本。可以在蓝图中使用,但必须在C++中声明。
  • 即使它在内部不使用对象的字符串表示,也可以在实例详细信息视图(不是类默认值)中设置该值。
  • 因为它不使用字符串路径,所以跳过了查找资产的开销,但它只能设置为已经实例化的对象
  • 在C++中,您可以 指向任何已实例化的 UObject ,无论它所在的关卡,只要它已经加载。
本文由作者按照 CC BY 4.0 进行授权