扩展属性¶
UE 插件内置了一组扩展属性,将对象名称、Actor 位置和组件变换等信息作为普通绑定属性提供给页面。插件启动时自动注册,无需在业务类中重复声明。
调用线程
所有扩展属性背后的 Get/Set 函数都会在游戏线程调用。
蓝图节点不等于可绑定属性
蓝图中能看到某个节点,不代表对象上存在同名的 UPROPERTY 或符合属性访问约定的 UFUNCTION。例如,Get Actor Location 对应的反射函数实际名为 K2_GetActorLocation,而 C++ 的 GetActorLocation() 不是 UFUNCTION,对象上也没有名为 ActorLocation 的 UPROPERTY。因此,仅凭蓝图节点名称无法直接进行属性绑定;插件通过内置扩展属性补充了 ActorLocation,页面才能以该名称读取位置。
内置属性¶
以下属性均为只读,适用于对应类型及其派生类:
| 对象类型 | 属性名称 | UE 值类型 | 可访问性 | 说明 |
|---|---|---|---|---|
UObject |
DisplayName |
FString |
只读 | 对象显示名称,使用 UE 的 GetDisplayName 结果 |
UObject |
ObjectName |
FString |
只读 | 对象名称 |
UObject |
ObjectPathString |
FString |
只读 | 对象的完整路径字符串 |
AActor |
ActorLocation |
FVector |
只读 | Actor 的世界空间位置 |
AActor |
ActorRotation |
FRotator |
只读 | Actor 的世界空间旋转 |
USceneComponent |
WorldLocation |
FVector |
只读 | 组件的世界空间位置 |
USceneComponent |
WorldRotation |
FRotator |
只读 | 组件的世界空间旋转 |
USceneComponent |
WorldTransform |
FTransform |
只读 | 组件的世界空间变换 |
普通属性优先于同名扩展属性。派生类无需再次注册基类的扩展属性。
更新扩展属性需要发送通知
扩展属性的值变化后,必须调用属性通知,界面才会重新读取并更新显示。具体用法见属性更改通知。
绑定 ActorLocation¶
将一个 Actor 实例设置为页面的数据上下文后,可以直接绑定 ActorLocation,无需额外定义位置属性:
<!doctype html>
<html>
<body>
<!-- 当前数据上下文需要是 AActor 或其派生类的实例。 -->
<h2>{{ ObjectName }}</h2>
<p>世界位置:{{ ActorLocation }}</p>
<p>X:{{ ActorLocation.X }}</p>
<p>Y:{{ ActorLocation.Y }}</p>
<p>Z:{{ ActorLocation.Z }}</p>
</body>
</html>
例如,Actor 位于 (100, 200, 300) 时,三个分量分别显示 100、200、300。若数据上下文通过 TargetActor 属性引用 Actor,也可以使用 TargetActor.ActorLocation.X 等路径。
位置变化通知¶
扩展属性在读取时获取当前值,不会自动监听 Actor 移动。需要页面随位置变化刷新时,在位置更新后发送属性通知:
#include "CoreMinimal.h"
// 必须引入 UE 专用 SDK 头文件,提供 UE 类型适配并支持编译期类型检查。
#include "MarkupUISdk.h"
// TargetActor 是页面绑定的 Actor 实例。
MarkupUI::Binding::NotifyPropertyChanged(TargetActor, TEXT("ActorLocation"));
通知方式与普通属性相同,详见属性绑定。ActorLocation 是只读扩展属性,不能通过双向绑定移动 Actor;移动操作由业务代码执行。
自定义 UObject 扩展属性¶
可以用 C++ 为 UObject 及其派生类注册自定义扩展属性,无需在原有类中添加 UPROPERTY。注册后,页面像访问普通属性一样读取它,派生类也可使用。
例如,为 AActor 增加 ActorSpeed 属性,读取当前速度向量的长度。示例同时提供一个空 setter,仅用于展示可写属性的注册方式:
#include "CoreMinimal.h"
// 必须引入 UE 专用 SDK 头文件,提供 UE 类型适配并支持编译期类型检查。
#include "MarkupUISdk.h"
#include "Extension/ExtensionProperty.h"
#include "GameFramework/Actor.h"
class FActorBindingExtensions final
{
private:
static double GetActorSpeed(const AActor& Actor)
{
return Actor.GetVelocity().Size();
}
static void SetActorSpeed(AActor& Actor, const double& Value)
{
// 仅作为可写扩展属性的例子;实际写入该属性没有意义,此处不修改 Actor。
}
public:
// 在游戏线程调用,并在打开使用该属性的页面之前完成注册。
static bool Register()
{
return MarkupUI::Binding::RegisterExtensionProperty(
TEXT("ActorSpeed"), &GetActorSpeed);
// 如需支持写入,传入 Setter 函数:
// return MarkupUI::Binding::RegisterExtensionProperty(
// TEXT("ActorSpeed"), &GetActorSpeed, &SetActorSpeed);
}
};
在应用或模块初始化流程中调用 FActorBindingExtensions::Register() 一次,检查返回值是否为 true。注册接口根据访问器推导对象类型和属性值类型;同一类型重复注册相同名称会失败。
- getter 必须是可转换为函数指针的访问器,接收
const TObject&并返回属性值。 - 如需支持写入,可传入第三个参数 setter,其签名为
void(TObject&, const TValue&);不提供 setter 时只读。 - 访问器无需声明为
UFUNCTION,值类型应符合插件支持的数据类型。
将 Actor 实例作为数据上下文后,页面可以直接绑定:
<!-- 当前数据上下文是 AActor 或其派生类的实例。 -->
<p>速度:{{ ActorSpeed }}</p>
速度变化后,业务代码发送扩展属性通知,页面才会重新读取:
MarkupUI::Binding::NotifyPropertyChanged(TargetActor, TEXT("ActorSpeed"));
SDK 不自动推断 getter 的数据依赖,setter 也不会自动发送通知;可写扩展属性需要在成功修改数据后通知对应属性变化。扩展属性注册不会保留 UObject 实例,其生命周期仍由业务代码按 UE 规则管理。