如何使用 TAPython 创建 Editor Mode¶
概述¶
什么是 Editor Mode¶
Editor Mode(编辑器模式)是虚幻引擎提供的一种专门的编辑工具框架,允许开发者为特定的编辑任务创建独立的交互环境。常见的 Editor Mode 包括:
- 地形编辑模式:用于雕刻和修改地形
- 植被绘制模式:用于在场景中绘制植被
- 模型绘制模式:用于为模型绘制顶点颜色
- 选择模式(Selection Mode):虚幻引擎的默认编辑模式
TAPython Editor Mode 的优势¶
自 TAPython v1.3.0 起,您可以使用 JSON 定义界面 + Python 编写逻辑 的方式快速创建自定义 Editor Mode,无需编写任何 C++ 代码,支持实时热重载,极大提升开发效率。
与 Scriptable Tools Editor Mode 的区别¶
虚幻引擎在 5.2 版本中引入了 Scriptable Tools Editor Mode(目前状态Beta),这个功能允许用户通过蓝图来创建自定义的 Editor Mode,但它在下面几个方面存在问题:
-
蓝图节点,但对于复杂的逻辑来说,蓝图的信息密度,维护和扩展性都比较差。
-
Scriptable Tools 每个“工具”是一个按钮,工具中的参数以 Property 的形式显示;各个“工具”之间的联系是非常弱的,如果做一个复杂的工具,比如有多个笔刷,需要管理多个绘制状态和上下文,这个时候用 Scriptable Tools 就会显得非常繁琐和困难。
-
Scriptable 中单个的“工具”的基类是定义好的,比如支持点击的是 ClickTool,支持拖拽的是 DragTool,如果一个工具需要多种交互方式,则需要添加一个新的 C++类来支持。“在开发的第一时间就需要选择继承的基类”,这其实就已经决定了这个工具的上限。
而 TAPython 则是用 Python 脚本来编写逻辑。对于进阶的工具来说,Python 脚本的编写和维护会更加方便和高效,同时也能非常方便地调用第三方库,方便与 LLM、AI、其他的 DCC 等进行集成。

何时使用 Editor Mode¶
我们之前用 TAPython 创建了大量的 Chameleon Tools。那么什么时候需要用 Editor Mode 呢?
简单来说,当您的工具需要以下特性时,应考虑使用 Editor Mode:
- 独立的交互逻辑:需要捕获和处理鼠标、键盘等输入事件
- 自定义用户界面:需要专门的 UI 布局和控件
- 特定工作流支持:如绘制、雕刻、放置等需要视口交互的操作
- 视口覆盖 UI:需要在 3D 视口上显示 2D 控件(如笔刷预览、信息提示)
一个典型的例子是各种“绘制”类的工具,各种“绘制”的操作需要使用鼠标的拖拽操作(拖拽的同时需要锁定视角),提供操作的上下文(比如正在对那个物体进行绘制等)。这类工具非常适合使用 Editor Mode 来实现。
快速上手¶
最小示例¶
让我们从一个最简单的 Editor Mode 开始,它会在鼠标拖拽时显示屏幕坐标。

例如下面的 JSON 文件,定义了一个最小化的 Editor Mode,其中:
EditorModeName:定义编辑器模式的显示名称(必填,用于区分 Editor Mode 和普通 Chameleon Tool)InitPyCmd:Python 工具实例的初始化命令Aliases:定义别名变量,简化后续 Python 调用EditorModeOnDrag:鼠标拖拽时的回调函数Root:UI 布局定义
{
"EditorModeName": "Minimal Editor Mode",
"InitPyCmd": "import EditorModeExample, importlib; importlib.reload(EditorModeExample); EditorModeExample.MinimalEditorMode.MinimalEditorMode(%JsonPath)",
"Aliases": {
"$this_tool": "EditorModeExample.MinimalEditorMode.MinimalEditorMode()"
},
"EditorModeOnDrag": "$this_tool.on_drag(%input_ray, %mouse_button, %delta_time)",
"Root": {
"SBorder": {
"BorderImage": { "Style": "FCoreStyle", "Brush": "Menu.WidgetBorder" },
"Padding": 8,
"BorderBackgroundColor": [1, 0, 1, 0.2],
"Content": {
"SVerticalBox": {
"Slots": [
{
"AutoHeight": true,
"Padding": 4,
"SCheckBox": {
"Content": {
"STextBlock": {
"Margin": [0, 10],
"Text": "Enable Mode",
"Aka": "ButtonText",
"Justification": "Center"
}
},
"CheckBoxStyle": {
"Style": "FEditorStyle",
"StyleName": "DetailsView.ChannelToggleButton"
},
"IsChecked": true,
"OnCheckStateChanged": "$this_tool.on_check_state_changed(%)"
}
},
{
"AutoHeight": true,
"Padding": 4,
"STextBlock": {
"Text": "Mode enabled — drag over a scene object to interact; \n(dragging empty space has no effect.)",
"Aka": "StatusText"
}
}
]
}
}
}
}
}
在 Python 脚本中,我们可以定义这个工具的逻辑:
"""
Minimal Editor Mode Example
A bare-bones editor mode example showing the absolute minimum required functionality:
- Enable/disable editor mode
- Capture mouse drag events
- Display cursor position
"""
import unreal
from Utilities.Utils import Singleton
class MinimalEditorMode(metaclass=Singleton):
def __init__(self, json_path: str):
self.json_path = json_path
self.data: unreal.ChameleonData = unreal.PythonBPLib.get_chameleon_data(self.json_path)
def on_drag(self, input_ray: unreal.InputDeviceRay, mouse_button: str, delta_time: float) -> None:
screen_pos = input_ray.screen_position
status_text = f"Dragging with {mouse_button} at ({screen_pos.x:.0f}, {screen_pos.y:.0f})"
self.data.set_text("StatusText", status_text)
unreal.log(status_text)
def on_check_state_changed(self, is_enabled: bool) -> None:
self.data.set_chameleon_editor_mode_enabled(is_enabled)
button_text = "✓ Mode Active" if is_enabled else "Enable Mode"
self.data.set_text("ButtonText", button_text)
status = "Mode enabled — drag over a scene object to interact; \n(dragging empty space has no effect.)" if is_enabled else "Mode disabled"
self.data.set_text("StatusText", status)
可以看到,on_drag 函数会在鼠标拖拽时被调用, 它可以通过传入的input_ray参数获取鼠标的屏幕位置, 并更新界面上的文本显示. 其他用的到API还有:
- 通过
self.data.set_text()更新 UI 显示 - 使用
set_chameleon_editor_mode_enabled()控制模式启用状态

进阶示例¶
在下面的例子中,我们会以一个简单的"绘制"类 Editor Mode 工具为例,来介绍 Editor Mode 的更多的功能配置和使用方法. 完整的示例可以在 <Your_Project>\TA\TAPython\Python\EditorModeExample 或者 DefaultResources 中找到

菜单系统¶
在 TAPython 的 Editor Mode 中, 我们有三个内置的菜单项
- 可配置的工具菜单(显示为当前工具的名称)
- Available Tools
- Viewport

1. 工具专属菜单¶
其中分隔线上方的菜单项是这个工具专属的菜单项,它来自工具Json中的 "OnTabContextMenu"字段,我们可以在这里定义一些工具相关的操作命令.

"OnTabContextMenu":{
"name": "Custom Editor Mode Menu",
"items":[
{
"name": "Log Editor Mode State",
"Command": "unreal.ChameleonData.log_chameleon_editor_mode()"
}
]
},
分割线下方的菜单项则是所有 Editor Modes 共享的菜单项,它来自全局的配置文件(<Your_Project>/TA/TAPython/UI/MenuConfig.json) 中的 "OnTabContextMenu" 字段,目前这个字段与 Chameleon Tools 共享。
例如下面的功能,包括快速 Reload 工具逻辑,也可以在 Editor Mode 中使用。
"OnTabContextMenu":{
"name": "TA Python Tab",
"items": [
{
"name": "Log tool's json path",
"command": "print(%tool_path)"
},
{
"name": "Log instance variable name",
"command": "import Utilities; Utilities.Utils.guess_instance_name(%tool_path)"
},
{
"name": "Reload this tool",
"command": "unreal.ChameleonData.request_close(%tool_path); unreal.ChameleonData.launch_chameleon_tool(%tool_path)"
}
]
}
2. Available Tools 菜单¶
所有可用的 Editor Modes 工具列表。比如下图中的 Minimal Editor Mode 和 Viewport Painting Example 分别是两个不同的 Editor Mode 工具。我们可以通过这个菜单来快速切换不同的 Editor Mode 工具。
TIP
TAPython 会自动查找项目中的 Editor Mode 文件,无需手动配置。包含 "EditorModeName" 字段的 JSON 文件都会被识别为 Editor Mode 工具。

3. Viewport 菜单¶
我们可以通过这个菜单来控制视口 UI 的显示和交互行为。

视口 UI(Viewport UI)¶
除了在主界面中定义 UI,我们还可以在视口中定义 UI 控件。具体的做法是在 JSON 文件中添加 "ViewportUI" 字段,然后按照之前定义 UI 的方式来定义视窗中的 UI 控件即可。
{
"ViewportUI": {
"SOverlay": {
"Slots":
[
...
]
}
}
}
比如下图,视窗左上角的按钮,中间的 2d 笔刷,以及右下角的信息显示,就是通过 ViewportUI 来实现的.

TIP
需要注意的是,Viewport UI 会覆盖视窗中的所有内容。因此,我们需要更多地使用 "AutoHeight" 和 "AutoWidth" 来让控件自适应内容大小,或者也可以使用 SCanvas 来更灵活地控制位置和大小。
配置方法¶
对于视口中的 UI 控件,默认的"Viewport"菜单项中,提供了以下两个选项:
- Hide Viewport UI: 隐藏视口中的 UI 控件
- Disable Viewport UI Interaction: 禁用视口中的 UI 控件交互响应
当然我们也可以通过 Python 命令来操作:
# 控制视口 UI 显示/隐藏
unreal.ChameleonData.set_chameleon_mode_viewport_widget_visibility(True)
# 控制视口 UI 是否响应点击
unreal.ChameleonData.set_chameleon_mode_viewport_widget_clickable(False)
这两个函数都是 ChameleonData 的静态函数,所以也可以在工具实例中通过self.data.set_chameleon_mode_viewport_widget_visibility(is_visible)来调用。
回调事件¶
Editor Mode 支持丰富的输入事件回调,让您精确控制交互逻辑。
除了上面提到的鼠标拖拽事件on_drag,Editor Mode 还支持更多的回调事件. 下面是目前支持的事件列表:
| 事件名称 | 触发时机 | 回调参数 |
|---|---|---|
EditorModeOnMouseDown |
鼠标按下 | %input_ray, %mouse_button, %delta_time |
EditorModeOnMouseUp |
鼠标抬起 | %input_ray, %mouse_button, %delta_time |
EditorModeOnDrag |
鼠标拖拽中 | %input_ray, %mouse_button, %delta_time |
EditorModeOnDragEnd |
拖拽结束 | %input_ray, %mouse_button, %delta_time |
EditorModeOnMouseWheel |
鼠标滚轮 | %wheel_delta |
EditorModeCanCapture |
判断是否捕获输入 | %input_ray, %mouse_button |
参数说明:
%input_ray:包含鼠标射线和屏幕坐标的结构体,类型为unreal.InputDeviceRay%mouse_button:字符串(可选),可能值为"LeftMouseButton","RightMouseButton","MiddleMouseButton"%delta_time:帧间隔时间(可选)
启用/禁用 Editor Mode¶
我们可以通过界面上的控件,或者 Python 命令来启用/禁用当前的 Editor Mode. 例如上面的最小示例中,我们通过一个复选框来控制 Editor Mode 的启用和禁用.
unreal.ChameleonData.set_chameleon_editor_mode_enabled(is_enabled)
事件执行流程¶
一次完整的拖拽操作会依次触发:
EditorModeOnMouseDown → EditorModeOnDrag (多次) → EditorModeOnDragEnd → EditorModeOnMouseUp
事件捕获控制(EditorModeCanCapture)¶
EditorModeCanCapture 这个功能可以让我们在拖拽或者点击事件被触发之前,先行判断是否需要捕获此次操作,从而避免不必要的事件调用. 比如只对特定类型的物体进行交互; 在用户按住键盘按键时才进行交互等.
EditorModeCanCapture 这个字段通常会定义一个 python 函数的调用,这个函数的三种返回值(True, False, None)会决定后续事件是否被捕获和调用:
返回值定义:
True:强制捕获输入False:拒绝捕获,事件不会触发None:默认行为(鼠标下有可 Trace 对象时捕获, 反之不捕获)
def can_capture_mouse(self, input_ray: unreal.InputDeviceRay, mouse_button: str) -> Optional[bool]:
# 按下左键时捕获输入
if mouse_button == "LeftMouseButton":
return True
# 按下中键且按下 Ctrl 键时捕获输入
if mouse_button == "MiddleMouseButton":
return unreal.PythonBPLib.get_modifier_keys_state().get("IsControlDown", False)
# 其他情况保持默认行为
return None
CAUTION
这个回调函数的执行与其他 Python 命令的执行有少许差异(例如,不会输出 Python 代码的错误信息),它只适合执行逻辑判断,不应该做实际的业务处理。
捕获鼠标按键设置¶
在默认的情况下,我们只能捕获鼠标左键的输入.如果需要捕获其他按键(比如左键绘制,右键擦除),我们可以通过 ChameleonData 的静态函数来设置是否捕获中键和右键的输入:
# 启用右键捕获(例如:左键绘制,右键擦除)
unreal.ChameleonData.set_editor_mode_capture_right_button(True)
# 启用中键捕获
unreal.ChameleonData.set_editor_mode_capture_middle_button(True)
TIP
提示,当我们捕获某个鼠标按键的之后,该按键的默认行为(比如左键旋转视角)会被禁用. 这个时候我们可以按下Alt键来临时恢复默认行为.
PropertySet 属性集¶
在 Editor Mode 中, 我们除了可以使用 ChameleonData 来管理 UI 控件和状态之外, 和可以用 Python 类来定义工具的属性集(PropertySet)的方式(Scriptable Tools), 这些属性会自动显示在工具界面的属性面板中,方便用户查看和修改.
定义属性集¶
具体的做法如下,继承自 unreal.ChameleonEditorModeToolProperties 的类,并使用 @unreal.uproperty() 装饰器来定义属性:
@unreal.uclass()
class ViewportPaintingModeProperties(unreal.ChameleonEditorModeToolProperties):
static_mesh = unreal.uproperty(unreal.StaticMesh)
color = unreal.uproperty(unreal.LinearColor)
设置默认值¶
对 UClass 的默认值的修改,需要通过 get_default_object() 来获取 CDO 对象,然后使用 set_editor_property 来设置属性值:
set_cdo = ViewportPaintingModeProperties.get_default_object()
set_cdo.set_editor_property("color", unreal.LinearColor.GREEN)
set_cdo.set_editor_property("static_mesh", unreal.load_asset('/Engine/BasicShapes/Cube.Cube'))
读取和修改属性¶
在 Editor Mode 的回调函数中,我们可以通过 get_editor_mode_property_set() 来获取当前工具的属性集实例,然后使用 get_editor_property 和 set_editor_property 来获取和修改属性值:
def on_mouse_down(self, input_ray: unreal.InputDeviceRay, mouse_button: str) -> None:
self.brush3d_color = self.data.get_editor_mode_property_set().get_editor_property("color")
PythonAPI¶
ChameleonData 新增 API(v1.3.0)¶
下面列举 V1.3.0 中新增的 Python API, 主要是针对 Editor Mode 的操作和管理. 其他SCanvas 和 Project Settings 相关的 API 也可以在 Editor Mode 中使用.
| 函数名 | 说明 | 返回值 |
|---|---|---|
get_chameleon_editor_mode_json_path() |
获取当前 Editor Mode 的 JSON 文件路径 | str |
is_chameleon_editor_mode_enabled() |
检查 Editor Mode 是否启用 | bool |
set_chameleon_editor_mode_enabled(enabled: bool) |
启用/禁用 Editor Mode | None |
get_editor_mode_event_command(event_name: str) |
获取指定事件的 Python 命令 | str |
get_editor_mode_property_set() |
获取当前工具的 PropertySet 实例 | ChameleonEditorModeToolProperties |
log_chameleon_editor_mode() |
输出 Editor Mode 当前状态到日志 | None |
鼠标捕获控制¶
| 函数名 | 说明 | 返回值 |
|---|---|---|
set_editor_mode_capture_right_button(enabled: bool) |
设置是否捕获鼠标右键 | None |
get_editor_mode_capture_right_button() |
获取是否捕获鼠标右键 | bool |
set_editor_mode_capture_middle_button(enabled: bool) |
设置是否捕获鼠标中键 | None |
get_editor_mode_capture_middle_button() |
获取是否捕获鼠标中键 | bool |
视口 UI 控制¶
| 函数名 | 说明 | 返回值 |
|---|---|---|
set_chameleon_mode_viewport_widget_visibility(visible: bool) |
设置视口 UI 是否可见 | bool |
set_chameleon_mode_viewport_widget_clickable(clickable: bool) |
设置视口 UI 是否可交互 | bool |
SCanvas 元素操作¶
用于精确控制视口 UI 中的元素位置和大小:
| 函数名 | 说明 |
|---|---|
set_canvas_element_position(aka_name, position) |
设置 Canvas 元素位置 |
get_canvas_element_position(aka_name) |
获取 Canvas 元素位置 |
set_canvas_element_size(aka_name, size) |
设置 Canvas 元素大小 |
get_canvas_element_size(aka_name) |
获取 Canvas 元素大小 |
set_canvas_element_position_by_index(canvas_aka, index, position) |
通过索引设置元素位置 |
get_canvas_element_position_by_index(canvas_aka, index) |
通过索引获取元素位置 |
set_canvas_element_size_by_index(canvas_aka, index, size) |
通过索引设置元素大小 |
get_canvas_element_size_by_index(canvas_aka, index) |
通过索引获取元素大小 |