
开发过数字孪生、运行时编辑器、模型摆放类项目的 UE5 开发者大概率都卡过同一个问题编辑器里拖动 Actor 的移动、旋转、缩放手柄非常顺手可一旦点下 Play或者把项目打包给客户这些手柄就全部消失了。想要在运行时让用户自由调整物体位置、角度、大小只能自己写射线拾取、自己算坐标变换、自己做 UI 手柄。工作量不小效果还未必如意。RuntimeGizmo 就是来解决这个问题的。它把 UE 编辑器中的 Gizmo 交互能力搬到了运行时——无论是 PIE 播放模式还是打包后的独立程序玩家都能像在编辑器里一样通过移动、旋转、缩放三个手柄直接操作场景中的 Actor。本篇文章是 UE5 Runtime 系列教程的第四篇重点讲解 RuntimeGizmo 插件的安装、核心原理、蓝图和 C 接入方式以及实际项目中常见的坑和最佳实践。读完这篇文章你会得到三样东西第一理解 RuntimeGizmo 的运行机制和核心 API第二拿到一份可复制的蓝图和 C 示例第三避开接入过程中最容易出错的几个关键细节。1. RuntimeGizmo 到底解决了什么问题先下一个明确判断RuntimeGizmo 并不是一个“给编辑器增加小功能”的玩具插件它是连接“编辑态能力”和“运行时产品”的桥梁。很多团队在项目前期规划时会把“运行时物体编辑”想得太简单。比如做数字孪生项目客户希望在界面上点击一台设备就能拖动它换位置旋转调整朝向缩放改变大小。这个需求听起来很朴实把编辑器里的 Gizmo 用起来不就行了但实际开发时会发现UE 编辑器自带的 Gizmo 组件是 Editor Only 的它依赖编辑器视口和编辑器交互框架在 Game 模式下不会生效。没有 RuntimeGizmo 时开发者只能走一条很长的自研路线用鼠标射线检测 HitResult选中目标 Actor根据屏幕空间投射计算拖拽方向手动换算世界坐标区分移动、旋转、缩放的交互逻辑并处理鼠标偏差、吸附、多轴限制绘制光标附近的自定义 UI 手柄或模型处理相机移动、物体自转、父子层级关系带来的坐标空间混乱问题。这套流程熟练的工程师至少也得写一两周而且 Bug 率极高。RuntimeGizmo 的贡献在于它把这些通用的交互逻辑封装成了开箱即用的组件和节点开发者只需要关心两件事让 Gizmo 操作哪个物体以及在操作完成后把变化同步给业务系统。从适用场景来看RuntimeGizmo 特别适合以下项目类型场景具体需求典型项目数字孪生在三维场景中调整设备、建筑部件的位置和朝向智慧园区、工厂产线可视化模型摆放工具运行时放置家具、道具、装饰物并允许二次调整家装设计、游戏建造模式关卡编辑工具游戏里允许玩家或策划在运行状态下修改场景元素沙盒游戏、UGC 关卡创作运维可视化调整摄像头角度、标注位置、告警点位置智慧城市、监控系统3D 展示应用用户在展示模型时旋转、缩放单件物体工业产品展示、汽车配置器如果你正在做这些方向的项目RuntimeGizmo 可以直接把你从“矩阵数学地狱”里捞出来。你不需要重新发明 Gizmo只需要学会如何控制它。2. RuntimeGizmo 的核心概念与运行机制2.1 什么是 GizmoGizmo 是三维编辑器中常见的一种可视化交互控件通常由三段箭头移动、三个圆弧旋转、三个立方体手柄缩放组成。用户通过鼠标拖拽这些手柄就能对选中物体做相应变换。在 UE 编辑器视口中选中一个 Actor 后看到的箭头和圆圈就是 Gizmo。RuntimeGizmo 就是把这套控件从编辑器里移植到了运行时让最终用户也能获得同样的交互体验。2.2 一个完整的操作周期RuntimeGizmo 的运行逻辑可以拆成四步运行时生成一个 GizmoActor插件提供的外形和交互逻辑调用接口把目标 Actor 绑定给 Gizmo比如 SetSelectedActor玩家拖拽手柄GizmoActor 内部计算变换量并修改目标 Actor 的 TransformGizmo 对外广播“变换更新”事件业务代码收到事件后同步数据、触发保存或与服务器通信。理解这四步很重要。很多人接入时只做了前两步发现界面能显示手柄、也能拖动但业务系统完全没有反应。原因就是漏了第四步——没有监听 OnTransformUpdated 事件。2.3 核心概念变换类型、坐标空间和轴向约束RuntimeGizmo 提供了三组核心控制参数理解它们才能控制好交互行为概念可选值含义TransformType 变换类型Translate / Rotate / Scale当前显示的是移动、旋转还是缩放手柄TransformSpace 坐标空间World / Local手柄按世界坐标轴还是按物体自身坐标轴显示Axis 轴向约束X / Y / Z / XYZ允许操作哪些轴还是三个方向自由拖动世界空间和局部空间是新手最容易搞混的地方。世界坐标轴固定朝上、朝北不管物体怎么旋转移动手柄的方向都是绝对的东南西北和上下。局部坐标轴跟随物体自身旋转物体在场景中转了一个角度后它的 X 轴可能指向世界坐标的东南方向手柄也会跟着偏转。在数字孪生场景中设备模型经常带有父级 Actor 或被人为旋转过。默认用世界空间操作模型自身轴向不与世界坐标对齐就会出现拖动方向与预期不符的问题。此时切到 Local 空间会更直观。2.4 从组件模型看插件设计从实现角度讲RuntimeGizmo 不是简单地在 SceneComponent 上画几个箭头它是一套“输入交互 射线拾取 数学计算 网格更新”的组合。核心类是一个 TransformGizmoActor这个 Actor 内部持有多个 SceneComponent 作为手柄的挂点并通过指针记录当前选中的 Actor 列表。每次拖拽时它会把鼠标在屏幕上的位移投射到三维空间再按照当前 TransformSpace 和 Axis 计算目标变换量。由于插件把交互和渲染封装在了一起开发者不需要关心内部数学细节只需要理解几个对外接口就足够在项目里使用了。3. RuntimeGizmo 环境准备与安装3.1 确认引擎版本本教程示例基于 UE5.3 编写但 RuntimeGizmo 对 UE5.0 到 UE5.4 都有较好的支持。不同版本之间的 API 差异很小主力接口基本一致。如果你的引擎版本更早建议先查看插件的 Release 说明或 GitHub README确认兼容性。3.2 下载与放置插件从 GitHub 搜索“RuntimeGizmo”即可找到作者仓库。下载插件源码后将整个 RuntimeGizmo 文件夹放入的项目 Plugins 目录中。如果项目还没有 Plugins 目录可以自己新建一个。最终的目录结构类似YourProject/ ├── Config/ ├── Content/ ├── Plugins/ │ └── RuntimeGizmo/ │ ├── Binaries/ │ ├── Content/ │ ├── Source/ │ └── RuntimeGizmo.uplugin ├── Source/ └── YourProject.uproject放入后右键点击 .uproject 文件选择 Generate Visual Studio project files如果还没有生成过然后重新打开 UE 编辑器。编辑器会提示重新编译插件或重新加载模块按提示操作即可。3.3 启用插件如果插件没有自动启用打开 Edit - Plugins在搜索框中输入 RuntimeGizmo勾选 Enabled然后重启编辑器。正确安装后Content Browser 左侧会出现插件的 Content 目录里面包含 Gizmo 相关的蓝图类、材质和网格资源。3.4 验证安装新建一个空白项目在关卡中随便放一个 Cube。在 Level Blueprint 或 Level Sequence 里尝试搜索“Transform Gizmo”相关节点。如果能在节点面板中搜到 Set Selected Actor、Set Transform Type 等节点说明插件已经编译并加载成功。4. RuntimeGizmo 的核心 API 与节点RuntimeGizmo 为蓝图用户准备了完整的节点池大部分交互逻辑不需要写一行 C。下面按功能模块介绍最常用的接口。4.1 创建 Gizmo蓝图和 C 中都需要先创建一个 TransformGizmoActor。蓝图里通常用 SpawnActor 节点Class 选择“TransformGizmo”Spawn Transform 选择默认或者指定位置。C 核心代码// 在 APlayerController 或 APawn 的 BeginPlay 中 ATransformGizmo* Gizmo GetWorld()-SpawnActorATransformGizmo( ATransformGizmo::StaticClass(), GetActorLocation(), FRotator::ZeroRotator ); if (Gizmo) { Gizmo-SetSelectedActor(TargetActor); }代码说明SpawnActor 返回的是 ATransformGizmo 指针如果创建成功立即调用 SetSelectedActor 把目标 Actor 绑定给 Gizmo。此时场景中会自动出现移动、旋转、缩放三组手柄。4.2 选中目标 Actor功能蓝图节点C 方法选中单个 ActorSet Selected ActorSetSelectedActor(AActor* Actor)选中多个 ActorSet Selected ActorsSetSelectedActors(TArrayAActor* Actors)清除选中Clear SelectionClearSelection()选中多个 Actor 时Gizmo 默认会把所有选中 Actor 当作一个整体以它们的中心点为操作中心。这个功能适合批量摆放、批量调整场景物体的场景。4.3 控制变换类型Gizmo-SetTransformType(ETransformType::RTS_Translate); // 移动 Gizmo-SetTransformType(ETransformType::RTS_Rotate); // 旋转 Gizmo-SetTransformType(ETransformType::RTS_Scale); // 缩放枚举前缀以实际插件源码为准蓝图中直接使用 Transform Type 枚举节点选择即可。4.4 控制坐标空间Gizmo-SetTransformSpace(ETransformSpace::RTS_Local); // 局部空间 Gizmo-SetTransformSpace(ETransformSpace::RTS_World); // 世界空间4.5 控制轴向Gizmo-SetAxis(EAxisList::RTS_XYZ); // 显示 XYZ 全轴 Gizmo-SetAxis(EAxisList::RTS_X); // 只显示 X 轴蓝图中的对应节点是 Set Axis参数可以选 X、Y、Z、XY、XZ、YZ、XYZ。限制轴向在数字孪生中非常实用比如某些设备只能沿 Y 轴移动时直接屏蔽其他方向的手柄可以避免用户误操作。4.6 监听变换更新这是整个插件使用中最关键的一部分。拖动手柄修改目标 Actor 的 Transform 后插件会通过一个动态委托向外广播。蓝图侧需要提前准备一个事件绑定到 Gizmo 的 OnTransformUpdated 事件上。在蓝图中绑定事件的路径是选中 Spawn 出来的 GizmoActor - Details 面板 - Events - On Transform Updated - 点击 号即可在 Level Blueprint 或 Actor 蓝图中生成对应事件节点。事件内部可以拿到更新后的 Actor 指针继而把新的 Transform 同步给业务系统。C 侧绑定示例Gizmo-OnTransformUpdated.AddDynamic(this, AMyPlayerController::HandleTransformUpdated); void AMyPlayerController::HandleTransformUpdated() { // 在这里拿到当前选中的 Actor做后续业务处理 AActor* SelectedActor Gizmo-GetSelectedActor(); if (SelectedActor) { FTransform NewTransform SelectedActor-GetActorTransform(); // 同步给服务器、数据库或 UI } }5. 蓝图完整示例运行时点击选中并拖动模型这一节我们从零搭建一个最典型的示例玩家在场景中点击某个模型模型上方出现 Gizmo玩家通过拖动 Gizmo 移动、旋转、缩放模型。5.1 场景布置在关卡中放置一个 StaticMeshActor比如 SM_Chair、SM_Table 都行。再放置一个玩家出生点PlayerStart。把场景中自带的网格地板保留方便测算物体移动距离。给目标模型随便起一个有意义的名称例如 Target_Chair方便在蓝图中引用。5.2 射线检测选中 Actor打开关卡蓝图通过鼠标点击来选中物体。这里需要一个 Player Controller 配合启用鼠标点击事件最简单的方式是在关卡蓝图的 BeginPlay 中调用 SetInputModeGameAndUI并把鼠标光标显示出来。射线检测的逻辑通常写作在关卡蓝图里监听 Mouse Button ReleasedLeft事件调用 Get Hit Result Under Cursor by Channel 节点TraceChannel 选择 Visibility从 Hit Result 中拿到 Hit Actor判断 Hit Actor 是否是需要被操作的目标可以判断 Tag 或 Class如果是目标则执行后续的 Gizmo 绑定逻辑。这里需要特别说明一个常见问题如果不做任何判断玩家点击地板或 UI 时也会触发 Gizmo 绑定。实际项目中建议给可交互物体挂一个自定义 Tag比如 Interactable然后在蓝图中判断 Hit Actor 的 Tag 是否为 Interactable。5.3 创建并绑定 Gizmo在关卡蓝图中创建一个 TransformGizmo 对象。为了让 Gizmo 只在选中物体后显示建议在“点击选中成功”的分支里才执行 SpawnActor并把 SpawnActor 的返回值存入一个变量比如 GizmoRef。每次继续点击新物体时如果 GizmoRef 已经存在直接调用 Set Selected Actor把新的 Actor 传进去如果 GizmoRef 为空先 SpawnActor 再调用 Set Selected Actor。这样可以避免不断生成重复的 Gizmo 实例。一个更省心的做法是在关卡开始时就创建一个全局唯一的 Gizmo先隐藏掉它的可视组件或关闭所有手柄碰撞选中物体时再调用 SetSelectedActor 并显示取消选中时再隐藏。具体实现取决于插件版本是否提供隐藏接口。如果插件没有现成的显示/隐藏接口最简单的隐藏方式是 SetActorHiddenInGame(true) 或者 SetActorEnableCollision(false)。5.4 监听变换更新并同步数据蓝图总体的节点连接思路[鼠标点击] - [射线检测] - [判断Tag] - [Set Selected Actor] - [绑定事件 On Transform Updated]OnTransformUpdated 事件触发后业务侧需要做什么以数字孪生项目为例当设备移动后通常要把新的位置、转角、缩放值写入后台数据库或者同步给其他客户端。此时可以在 OnTransformUpdated 事件里拿到当前选中的 Actor调用它的 GetActorTransform然后用 REST API 或者 WebSocket 把数据发出去。如果不涉及网络同步直接打印 Actor 的 Transform 值也能验证 Gizmo 是否生效。5.5 在蓝图接口中解耦业务逻辑稍微正规一点的项目都会使用蓝图接口Blueprint Interface来解耦交互层和业务层。这个思路和 RuntimeGizmo 是绝配。做法是创建一个 Blueprint Interface比如 BPI_Interactable在接口中定义 OnGizmoTransformUpdated 函数把需要被操作的所有 Actor 蓝图上都实现这个接口Gizmo 的 OnTransformUpdated 事件中直接调用目标 Actor 的 OnGizmoTransformUpdated 接口各个 Actor 在接口函数内部处理自己的逻辑比如保存数据、播放音效、更新 UI。这样做的好处是Gizmo 不需要知道目标 Actor 是什么类型只需要调用统一接口。以后扩展新的可编辑物体只要在新 Actor 蓝图中实现接口就行不需要再改 Gizmo 相关逻辑。6. C 完整示例在运行状态下创建 Gizmo 并操作 Actor6.1 创建玩家控制器如果你更喜欢 C 开发可以写一个简单的 PlayerController 子类在 BeginPlay 中创建 Gizmo并通过按键或点击来切换选中目标。头文件示例// MyPlayerController.h #pragma once #include CoreMinimal.h #include GameFramework/PlayerController.h #include MyPlayerController.generated.h class ATransformGizmo; UCLASS() class RUNTIMEGIZMODEMO_API AMyPlayerController : public APlayerController { GENERATED_BODY() public: virtual void BeginPlay() override; virtual void SetupInputComponent() override; protected: void OnLeftMouseClick(); void CycleTransformType(); void SetSelectedActor(AActor* NewActor); UPROPERTY() ATransformGizmo* Gizmo; };.cpp 文件示例// MyPlayerController.cpp #include MyPlayerController.h #include TransformGizmoActor.h #include Engine/World.h #include Kismet/GameplayStatics.h void AMyPlayerController::BeginPlay() { Super::BeginPlay(); bShowMouseCursor true; SetInputMode(FInputModeGameAndUI()); // 在运行时创建 Gizmo FActorSpawnParameters SpawnParams; Gizmo GetWorld()-SpawnActorATransformGizmo( ATransformGizmo::StaticClass(), GetPawn()-GetActorLocation(), FRotator::ZeroRotator, SpawnParams ); if (Gizmo) { // 默认选中场景中 Tag 为 Target 的第一个 Actor TArrayAActor* FoundActors; UGameplayStatics::GetAllActorsWithTag(GetWorld(), TEXT(Target), FoundActors); if (FoundActors.Num() 0) { SetSelectedActor(FoundActors[0]); } } } void AMyPlayerController::SetupInputComponent() { Super::SetupInputComponent(); InputComponent-BindAction(LeftClick, IE_Pressed, this, AMyPlayerController::OnLeftMouseClick); InputComponent-BindKey(EKeys::Tab, IE_Pressed, this, AMyPlayerController::CycleTransformType); } void AMyPlayerController::OnLeftMouseClick() { // 简化示意点击时通过射线命中 Tag 为 Target 的 Actor并让 Gizmo 选中它 FHitResult HitResult; if (GetHitResultUnderCursor(ECC_Visibility, true, HitResult)) { AActor* HitActor HitResult.GetActor(); if (HitActor HitActor-ActorHasTag(TEXT(Target))) { SetSelectedActor(HitActor); } } } void AMyPlayerController::SetSelectedActor(AActor* NewActor) { if (Gizmo) { Gizmo-SetSelectedActor(NewActor); Gizmo-SetTransformSpace(ETransformSpace::RTS_Local); Gizmo-SetTransformType(ETransformType::RTS_Translate); } } void AMyPlayerController::CycleTransformType() { if (!Gizmo) return; switch (Gizmo-GetTransformType()) { case ETransformType::RTS_Translate: Gizmo-SetTransformType(ETransformType::RTS_Rotate); break; case ETransformType::RTS_Rotate: Gizmo-SetTransformType(ETransformType::RTS_Scale); break; default: Gizmo-SetTransformType(ETransformType::RTS_Translate); break; } }代码需要注意几点头文件包含路径请以你实际下载的插件目录为准不同版本命名可能略有差别GetHitResultUnderCursor 需要在启用了鼠标光标的前提下才有效LeftClick 输入映射需要在项目设置 Input 中配置或者直接在代码里 BindKey(EKeys::LeftMouseButton)插件中的枚举命名可能不是 RTS_Translate 这种格式写代码前先打开插件头文件确认一下。6.2 设置输入映射如果在项目设置里已经配置了 LeftClick 的 Action Mapping直接用 BindAction 即可。如果没配置更快捷的方式是用 BindKeyInputComponent-BindKey(EKeys::LeftMouseButton, IE_Pressed, this, AMyPlayerController::OnLeftMouseClick);这样就不需要在项目设置里单独添加输入映射了。注意不要在 BindAction 和 BindKey 中重复绑定同一个按键否则一次点击会触发两次事件。6.3 把 PlayerController 设为默认在项目的 Game Mode 中把 Player Controller Class 设置为 AMyPlayerController并把 Game Mode 设置为当前关卡使用的 Default GameMode。之后运行项目Engine 会自动使用这个控制器Gizmo 就会在场景中生成。7. 运行结果与效果验证运行项目后按以下步骤验证步骤操作预期结果1启动 PIE场景中出现 Gizmo 手柄目标 Actor 中心带三色箭头2用鼠标拖动红色 X 轴箭头Actor 沿世界/局部 X 轴移动3按 Tab 键切换变换类型Gizmo 依次切换为移动、旋转、缩放4再次点击其他带 Target 标签的物体Gizmo 切换到新物体中心5在事件回调中打印 Transform控制台显示物体实时坐标如果第 2 步拖动手柄没有反应优先检查目标 Actor 是否被锁定Lock Location / Lock Rotation / Lock Scale 勾选状态以及 Actor 是否设置了 Mobility 为 Static。Static 类型的 Actor 在运行时不能通过 Transform 改变位置Gizmo 拖上去没有任何反馈。建议将可编辑物体设置为 Movable或者使用 SetActorTransform 的 Teleport 参数强制修改。如果第 4 步点击新物体没有切换检查射线检测的 Collision Channel 是否允许 Visibility 命中。项目中自定义碰撞预设可能挡住了 Visibility 通道导致射线直接穿过模型。8. 常见问题与排查思路问题现象可能原因排查方式解决方案Gizmo 没有出现在场景里SpawnActor 没执行或插件未启用检查 Output Log 有无报错确认 Place Actors 面板可见重新启用插件在蓝图里搜索 Set Selected Actor 验证编译Gizmo 出现了但拖动手柄没有变化目标 Actor 是 Static Mobility在引擎查看 Actor Mobility将 Actor 改为 Movable或者用其他方式同步 Transform移动方向不对TransformSpace 与目标不一致打印 Actor 旋转值切换到 Local 空间或确认目标 Actor 已设为根节点鼠标点击时 UI 也被误触发UI 和游戏共用输入检查 UI 的事件处理使用 Hit Result Under Cursor by Channel 并判断 bBlockingHit或先判断 IsFocusable旋转物体时出现剧烈抖动鼠标位移被放大或者本地旋转换算问题查看 Transform 数值跳变降低鼠标灵敏度检查是否同时旋转了 Gizmo 自身多个手机端操作时拥塞移动端触摸事件被多路触发打印 TouchIndex使用 Controller 的 Touch 事件绑定并屏蔽多点冲突打包后手柄网格变成白色插件资源或材质未包含进打包列表检查打包日志缺失资源在 Plugin 的 Content 目录确认资源被引用必要时把 Gizmo 相关资源挪到项目 Content这里再提一个容易被忽视的坑RuntimeGizmo 的拖拽本质上是通过输入事件计算鼠标/触摸位移。如果玩家在拖拽过程中鼠标光标突然移出了单独设置的窗口区域或者触控点被系统手势拦截Gizmo 的拖拽状态可能无法正确结束导致插件一直处于“按住”状态。遇到这种情况可以先检查项目是否启用了“鼠标光标锁定”或者系统的触摸手势拦截。9. 最佳实践与工程建议9.1 明确区分数据层和表现层经过实际项目验证比较合理的分层方式是RuntimeGizmo 只负责“表现层”的交互它把物体的 Transform 改掉后业务层必须自己负责数据同步。比如在数字孪生项目中物体坐标可能需要同步给服务器、写入数据库甚至在多人协同场景中同步给其他客户端。不要把这些同步逻辑直接写在 Gizmo 的事件回调里而是借助蓝图接口或事件分发器将“物体被用户修改”这件事发送到业务层由业务层决定是否保存、是否上传、是否广播。9.2 合理限制轴向和坐标空间运行时 Gizmo 是开放给最终用户使用的而很多用户并不理解什么是“世界坐标”和“局部坐标”。如果项目要求设备只能沿固定方向移动直接锁定其他方向的手柄不要让用户自由操作。例如一台传送带业务上只允许沿 X 轴传送方向移动。那么在绑定 Gizmo 时直接设置为只显示 X 轴并固定为世界空间。用户即使乱拖也无法把模型挪到错误位置。这远比你依赖用户“理解操作规则”要可靠。9.3 对静态和动态物体的不同处理前面提到 Static 类型 Actor 无法在运行时修改 Transform。在实际项目中模型来源复杂导入时默认可能是 Static。建议在将模型绑定给 Gizmo 之前先统一调整 Mobilityif (TargetActor TargetActor-GetRootComponent()) { TargetActor-GetRootComponent()-SetMobility(EComponentMobility::Movable); }如果目标 Actor 有多个组件最好遍历所有组件把需要移动的组件都设为 Movable。否则会出现整个 Actor 位置变了但子组件没有跟着变的情况。9.4 多用户协同时的同步策略RuntimeGizmo 不会自动处理多人同步。如果项目是多人协同编辑或数字孪生联动Gizmo 操作产生的变化需要你自己通过 RPC 或服务器转发给其他客户端。一种简单的实现方案是客户端 A 拖动物体后在 OnTransformUpdated 中向服务器发送目标 Actor 的 ID 和最新的 Transform 数据服务器校验权限后把状态广播给其他客户端客户端 B 收到数据后调用 SetActorLocation / SetActorRotation / SetActorScale3D。这套流程可以在很短的时间内完成但要注意避免把 Gizmo 的中间过程也同步给所有玩家否则频繁的拖拽事件会把带宽打满。通常做法是拖拽结束后只同步最终结果。9.5 相机、UI 与输入冲突凡是给运行时用户提供编辑功能的项目输入事件冲突都是绕不开的问题。最常见的是 UI 按钮点击时穿透到场景导致用户刚点完“保存”场景中的物体又被拖走了一次。建议养成统一输入分发的习惯当鼠标或触摸点落在 UI 上时使用 IsPointerOverWidget 节点提前判断如果是 UI则直接跳过 Gizmo 和射线检测逻辑。9.6 性能控制RuntimeGizmo 本身开销很小但如果场景中同时存在几十个可编辑物体并且 Gizmo 一直在刷新目标位置的捕捉逻辑会带来一定的性能开销。更合理的做法是让 Gizmo 作为一个全局单例存在只在用户选中某个物体时激活取消选中时隐藏或停用不要让 Gizmo 时刻处于“可编辑”状态。9.7 代码和蓝图的选择建议如果团队蓝图为主完全可以用蓝图完成全部功能。RuntimeGizmo 的节点封装得很完整最复杂的逻辑也只是一个射线检测加一次绑定调用。如果项目涉及多人同步、复杂数据协议或者需要把 Gizmo 能力封装给多个子系统调用建议至少把人机交互回调部分用 C 写好再暴露给蓝图。这样不仅逻辑更稳定调试时也能直接打断点查看 Transform 数据。10. 总结RuntimeGizmo 是运行时交互类 UE5 项目中非常值得引入的插件。它不是万能的——它不负责业务数据、不处理多人同步、不解决模型碰撞预设但它把三维世界里“让最终用户直接操作物体变换”这一层最琐碎、最底层的交互逻辑做到了开箱即用。真正的价值在于你可以把省下来的时间投入到业务流程、数据架构和用户体验中去而不是浪费在手动计算旋转矩阵和鼠标投射上。建议下一步这样实践先在测试项目中按本文的蓝图示例跑通点击选中和拖拽变换然后尝试接入一个简单的蓝图接口把 Gizmo 的变换回调转发给业务逻辑。跑通之后再去啃插件的源码重点看它内部如何处理射线拾取和坐标空间换算。理解到那一层你就具备了在真实项目中扩展它、改造它的能力比如增加吸附、增加对齐、增加自定义手柄样式。Runtime 系列后续还会继续深入这些方向。