Skip to content

插件使用说明

蛋仔 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 已就绪:

javascript
// ✅ 推荐:监听 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 配置文件,编辑器通过它发现和加载插件。

必填字段

字段类型说明
namestring插件名称,用于菜单项显示和标识
versionstring语义化版本号(如 "1.0.0"
entrystring入口 HTML 文件的相对路径(相对于 config.json 所在目录)

⚠️注意⚠️

缺少任意必填字段会导致插件加载失败并被跳过。

选填字段与默认值

字段类型默认值说明
descriptionstring""插件简要描述
categorystring"default"一级菜单分类,相同 category 的插件归入同一子菜单
ui.window_titlestringname 的值窗口标题
ui.is_dockedbooleanfalse是否 dock 模式(false = 独立窗口)
ui.size[int, int][1024, 768]初始窗口尺寸 [width, height]

entry 路径规则

  • 须使用相对路径,相对于 config.json 所在目录。后端自动解析为绝对路径。
  • 入口文件不存在时插件被跳过。

完整示例

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 接口类定义

python
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 标准化返回格式

python
# 成功返回
{'success': True, 'result': result_message}

# 失败返回
{'success': False, 'error': error_message}

2.3 自动类型转换

基于 WebChannel 通信机制和后端类型转换实现,前端 JavaScript 可以传递以下基础数据类型:

前后端对应的 EType 映射

前端类型后端 EType转换说明
stringStr / StringUnicode 编码处理
numberInt / Int32整数转换
numberFloat / Fixed浮点数 / 定点数转换
booleanBool布尔值转换
ArrayList / ListXXX数组类型转换
ObjectDict字典对象转换
[x, y, z] / (x, y, z)Vector3 / Point33D 坐标转换
null / undefinedNone空值处理

注意事项

前端无需关心具体的引擎类型,只需传基础数据类型即可。

后端会根据 API 定义的参数类型描述(EdAPIDef)自动将前端传入的 JS 基础类型转换为目标 Python/引擎类型。

实际传参示例

javascript
// 创建组件
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 异步调用模式:

javascript
// 正确的异步调用方式
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 脚本使用示例:

javascript
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

页面导航示例:

javascript
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 文件列表。前端调用示例:

javascript
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 自动类型转换

javascript
// 基础参数
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 结果显示

javascript
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 输入验证

javascript
function createObstacle() {
    var obstacleKey = parseInt(document.getElementById('obstacleKey').value);
    
    // 参数验证
    if (isNaN(obstacleKey)) {
        showResult('createResult', '请输入有效的组件编号!', true);
        return;
    }
    
    // 继续处理...
}

异常和错误处理

前端错误处理示例

javascript
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);
    }
}

示例插件

⬇️ 下载 sample.7z

配套提供了制作编辑器插件的 AI Skills,可以将 Skills 给到 AI,进行插件的生成。

⬇️ 下载 ugc-plugin.7z

如何管理插件

编辑器上方菜单栏中点击 LUA,下拉菜单中的 编辑器插件管理,即可管理当前使用的插件。

点击 导入插件 后,将会弹出 Windows 的资源管理窗口。

选中插件的 config.json,即可将插件导入至编辑器中使用。

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