插件使用说明
蛋仔 UGC 插件系统采用 H5 技术栈 + QWebChannel + EditorAPI/Lua 架构,让开发者使用最通用的 HTML/CSS/JavaScript 即可构建编辑器扩展工具。
最基础的插件文件结构如下:
sample_plugin/
├── config.json # 插件配置文件(必须)
└── index.html # 前端界面(入口文件)开发范式
1.1 入口文件规范
HTML 页面的兼容性规范详见上文(需 Chrome 77 兼容)。
WebChannel 自动注入与
backendReady事件框架会在页面加载完成后,自动注入
qwebchannel.js并初始化 WebChannel,前端不需要手动引入qwebchannel.js,也不需要手动调用new QWebChannel(...)。前端通过监听
backendReady事件获知window.backend已就绪:
// ✅ 推荐:监听 backendReady 事件
document.addEventListener('backendReady', function() {
console.log('后端连接成功');
// window.backend 已可用,开始业务逻辑
init();
});
// ⚠️ 如果页面脚本可能在 backendReady 之后才运行(如动态加载),需要兼容判断
if (window.backend) {
init();
} else {
document.addEventListener('backendReady', function() { init(); });
}1.2 配置文件规范(config.json)
每个 UGC 插件目录必须包含 config.json 配置文件,编辑器通过它发现和加载插件。
必填字段
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 插件名称,用于菜单项显示和标识 |
version | string | 语义化版本号(如 "1.0.0") |
entry | string | 入口 HTML 文件的相对路径(相对于 config.json 所在目录) |
⚠️注意⚠️
缺少任意必填字段会导致插件加载失败并被跳过。
选填字段与默认值
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
description | string | "" | 插件简要描述 |
category | string | "default" | 一级菜单分类,相同 category 的插件归入同一子菜单 |
ui.window_title | string | 取 name 的值 | 窗口标题 |
ui.is_docked | boolean | false | 是否 dock 模式(false = 独立窗口) |
ui.size | [int, int] | [1024, 768] | 初始窗口尺寸 [width, height] |
entry 路径规则
- 须使用相对路径,相对于
config.json所在目录。后端自动解析为绝对路径。 - 入口文件不存在时插件被跳过。
完整示例
{
"name": "UGC Eggitor Plugin",
"version": "1.0.0",
"description": "Advanced editing tools",
"category": "default",
"entry": "index.html",
"ui": {
"window_title": "UGC Eggitor Plugin Window",
"is_docked": false,
"size": [1024, 768]
}
}后端 API 定义
2.1 接口类定义
class PluginInterface(QObject):
@pyqtSlot(str, list, result='QVariant')
def runEditorAPI(self, api_name, arg_list):
"""
统一的编辑器API调用入口
:param api_name: API方法名
:param arg_list: 参数列表
:return: 标准化返回格式 {'success': bool, 'result'|'error': any}
"""
...
@pyqtSlot(str, result=str)
def runLuaScript(self, path):
# Lua脚本执行接口
...
@pyqtSlot(str, result='QVariant')
def navigateTo(self, path):
# 页面导航,切换到另一个 HTML 页面
...
@pyqtSlot(result='QVariant')
def listHtmlFiles(self):
# 列出插件目录下 HTML 文件列表 [{name: path}, ...]
...
@pyqtSlot(result='QVariant')
def testConnection(self):
# 连接测试接口
...关键点
- 使用
@pyqtSlot装饰器声明 WebChannel 可调用方法 result='QVariant'支持返回任意类型数据- 统一使用
runEditorAPI作为 API 调用入口
2.2 标准化返回格式
# 成功返回
{'success': True, 'result': result_message}
# 失败返回
{'success': False, 'error': error_message}2.3 自动类型转换
基于 WebChannel 通信机制和后端类型转换实现,前端 JavaScript 可以传递以下基础数据类型:
前后端对应的 EType 映射
| 前端类型 | 后端 EType | 转换说明 |
|---|---|---|
string | Str / String | Unicode 编码处理 |
number | Int / Int32 | 整数转换 |
number | Float / Fixed | 浮点数 / 定点数转换 |
boolean | Bool | 布尔值转换 |
Array | List / ListXXX | 数组类型转换 |
Object | Dict | 字典对象转换 |
[x, y, z] / (x, y, z) | Vector3 / Point3 | 3D 坐标转换 |
null / undefined | None | 空值处理 |
注意事项
前端无需关心具体的引擎类型,只需传基础数据类型即可。
后端会根据 API 定义的参数类型描述(EdAPIDef)自动将前端传入的 JS 基础类型转换为目标 Python/引擎类型。
实际传参示例
// 创建组件
window.backend.runEditorAPI('create_obstacle', [
102818, // ObstacleKey (Int)
[0.0, 2.0, 0.0] // Position (Vector3)
], callback);
// 查询单位
window.backend.runEditorAPI('query_unit_ids', [
"方块", // Pattern (String)
true // UseRegex (Bool)
], callback);
// 复杂参数调用(示例)
window.backend.runEditorAPI('create_complex_object', [
"building_001", // ID (String)
{ // Config (Dict)
"type": "house",
"size": [10, 8, 12],
"materials": ["wood", "stone"],
"enabled": true
},
[ // Positions (List)
[0, 0, 0],
[10, 0, 0],
[0, 0, 10]
]
], callback);类型转换限制:
- JavaScript 的
undefined会被转换为 Python 的None - JavaScript 的大数值可能精度丢失
- 循环引用对象无法序列化
编码注意:
- 中文字符串会自动进行 UTF-8 编码转换
- JSON 字符串需要正确转义特殊字符
坐标数据:
- 3D 坐标必须是 3 元素数组:
[x, y, z]、(x, y, z) - 也支持对象格式:
{x: 0, y: 2, z: 0}
前端通信规范
3.1 backendReady 事件
前端的 backendReady 事件标志着后端初始化成功、业务逻辑可用。可以通过监听此事件来进行必要的初始化逻辑,详见 1.1 入口文件规范。
3.2 API 绑定
支持绑定编辑时 API(runEditorAPI)和运行 Lua 脚本(runLuaScript)。
3.2.1 runEditorAPI
所有编辑时 API 调用都遵循统一的 runEditorAPI 异步调用模式:
// 正确的异步调用方式
window.backend.runEditorAPI(apiName, args, function(result) {
if (result && result.success === true) {
// 成功处理
console.log('调用成功:', result.result);
} else {
// 错误处理
console.error('调用失败:', result ? result.error : '未知错误');
}
});注意事项
- PyQt5 WebChannel 只支持异步调用,不能使用同步方式
- 必须使用回调函数接收返回值
- 严格判断
result.success === true
3.2.2 runLuaScript
执行 Lua 脚本使用示例:
function runLuaScript() {
var path = document.getElementById('pathInput').value;
if (!path) {
showResult('scriptResult', '请输入脚本路径!', true);
return;
}
try {
window.backend.runLuaScript(path, function(result) {
showResult('scriptResult', '执行结果: ' + result, result.indexOf('Error') !== -1);
});
} catch (e) {
console.error('调用错误:', e);
showResult('scriptResult', '调用失败: ' + e.message, true);
}
}3.2.3 navigateTo
页面导航示例:
window.backend.navigateTo('abspath/plugin_dir/settings.html', function(result) {
if (result && result.success === true) {
console.log('页面切换成功');
} else {
console.error('切换失败:', result ? result.error : '未知错误');
}
});3.2.4 listHtmlFiles
列出插件目录下 HTML 文件列表。前端调用示例:
window.backend.listHtmlFiles(function(result) {
if (result && result.success === true) {
var files = result.result;
var select = document.getElementById('fileSelect');
select.innerHTML = '';
for (var i = 0; i < files.length; i++) {
var opt = document.createElement('option');
opt.value = files[i].path;
opt.textContent = files[i].name;
select.appendChild(opt);
}
}
});3.3 参数传递规范
参考 2.3 自动类型转换。
// 基础参数
window.backend.runEditorAPI('log', ['Hello World'], callback);
// 坐标参数(数组形式)
var position = [parseFloat(x), parseFloat(y), parseFloat(z)];
window.backend.runEditorAPI('create_obstacle', [obstacleKey, position], callback);
// 复杂参数(对象需要序列化)
var complexArgs = [
obstacleKey,
{x: 0, y: 2, z: 0},
{enable: true, scale: 1.5}
];UI 交互示例
旨在说明如何在前端显示信息和增加反馈。下面仅给出基础示例,实际可自由定制。
4.1 结果显示
function showResult(elementId, message, isError) {
var resultDiv = document.getElementById(elementId);
resultDiv.style.display = 'block';
resultDiv.className = isError ? 'result error' : 'result';
resultDiv.textContent = typeof message === 'object' ?
JSON.stringify(message, null, 2) : message;
}4.2 输入验证
function createObstacle() {
var obstacleKey = parseInt(document.getElementById('obstacleKey').value);
// 参数验证
if (isNaN(obstacleKey)) {
showResult('createResult', '请输入有效的组件编号!', true);
return;
}
// 继续处理...
}异常和错误处理
前端错误处理示例
function callAPI(apiName, args) {
try {
window.backend.runEditorAPI(apiName, args, function(result) {
if (result && result.success === true) {
showResult(elementId, '成功: ' + result.result, false);
} else {
var errorMsg = result ? result.error : '未知错误';
showResult(elementId, '失败: ' + errorMsg, true);
}
});
} catch (e) {
console.error('调用异常:', e);
showResult(elementId, '调用失败: ' + e.message, true);
}
}示例插件


配套提供了制作编辑器插件的 AI Skills,可以将 Skills 给到 AI,进行插件的生成。
如何管理插件
编辑器上方菜单栏中点击 LUA,下拉菜单中的 编辑器插件管理,即可管理当前使用的插件。


点击 导入插件 后,将会弹出 Windows 的资源管理窗口。
选中插件的 config.json,即可将插件导入至编辑器中使用。

添加后的插件可通过上方菜单中的 UGC 入口找到,点击后进行使用。

