news 2026/10/20 5:23:34

Cocos Creator 3.x编辑器扩展实战:场景节点批量编辑插件开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cocos Creator 3.x编辑器扩展实战:场景节点批量编辑插件开发

上周美术和策划联名提了个需求:项目里一百多个场景,需要把一批挂在 UI 节点下面的背景节点统一置顶,再把某些分类节点的激活状态批量关掉。逐个场景、逐个节点手改,一晚上都改不完,而且特别容易漏。这种重复劳动最适合交给编辑器工具解决。于是我用 Cocos Creator 3.x 的扩展机制做了一款节点批量编辑插件,前前后后踩了不少坑,也把整个流程彻底跑通了。这篇记录不是官方文档的复述,是我从这个需求出发,从零做完一个可用扩展插件全过程的真实总结,包括目录怎么组织、package.json 怎么写、消息链路怎么打通、面板怎么做、撤销怎么处理、上线分发要注意什么。如果你也在做 Cocos Creator 编辑器扩展,或者正准备给自己的项目写一个提效小工具,这份记录应该能帮你省掉大量试错时间。

1. 为什么我把这个需求做成扩展插件,而不是写个脚本

1.1 这个需求原本长什么样

我一开始想的特别简单:既然要在场景里批量改节点属性,那写一个 Editor 脚本或者在项目里写一段初始化逻辑不就行了?后来仔细掂量了一下,发现问题没那么简单。

第一,这个操作不是运行时逻辑,而是编辑期操作。美术改场景、策划调界面,他们需要的是一个可视化的工具,而不是手动改配置。第二,操作范围是整个场景的节点树,需要先筛选、预览,确认哪些节点会被影响,然后才执行修改。这个"先看再改"的交互流程,普通脚本根本做不了。第三,批量修改在编辑器里必须有撤销支撑,否则误操作一次,面板就留在项目里吃灰了。

1.2 扩展插件和普通脚本的本质区别

Cocos Creator 的扩展插件,本质上是编辑器能力的外挂。它运行在编辑器进程内,可以拿到编辑器的菜单、面板、消息系统、场景数据这些内部资源。普通脚本运行在游戏运行时,能操作的是运行时场景;而扩展插件里可以通过场景脚本直接操作编辑器正在编辑的场景,这才是批量工具能落地的关键。

打个比方:普通脚本像是游戏里的一把武器,只能在游戏战斗时挥动;扩展插件像是游戏引擎编辑器里的工具箱,可以直接修改正在制作的关卡图纸。我需要的是改图纸,而不是改游戏里的表现,所以必须走扩展插件的路子。

1.3 先定义清楚功能边界

动工之前,我把功能清单压缩到最小可用范围,避免给自己挖坑。最终版只需要四个能力:

功能说明技术关键点
名称筛选按关键词过滤当前场景节点遍历场景节点树,匹配节点名
批量激活/禁用设置选中节点的 active 属性修改节点属性并纳入撤销
批量置顶/置底调整选中节点在父节点下的兄弟排序通过 setSiblingIndex 操作
批量改名把节点名中的关键词替换成新文本字符串替换后写回节点名

这个范围是我刻意压过的。没有碰 Prefab 资源批量处理,没有碰组件参数批量调整,因为那些功能在 3.x 里涉及更多资源系统和组件系统 API,第一版做进去会严重拖慢进度。第一版先解决我自己最痛的节点批量操作,跑通整个扩展链路,后面再往资源方向扩展。

2. 一个最小的扩展包长什么样:目录与 package.json 拆解

2.1 扩展包目录结构

Cocos Creator 3.x 的扩展包,通常放在项目根目录下的extensions文件夹里。一个规范的扩展包自带完整的模块边界,不依赖具体项目内容,随项目走或者单独分发都可以。

我做的这个工具目录结构长这样:

extensions/ node-batch-editor/ package.json src/ main.ts panel.ts scene.ts dist/ main.js panel.js scene.js docs/ README.md

源码放在src下,编译产物放dist。package.json里的main指向对应编译后的入口文件。这个结构基本是 3.x 扩展包的标准骨架,新增功能模块时按需往src里加文件即可。

这里有个很重要的认知:扩展包和普通 Cocos 项目是两个世界。项目管理器加载的是extensions/node-batch-editor这个目录,它有自己的 TypeScript 编译配置,不能直接用项目里的 tsconfig,也不能直接 import 项目里的assets脚本。它用到的是编辑器提供的扩展接口,以及场景脚本运行时提供的cc模块。

2.2 package.json 里那些不能写错的字段

扩展包的package.json是编辑器的"说明书",字段写错直接导致加载失败。我这里贴的是一份可用的最小配置:

{ "name": "node-batch-editor", "version": "1.0.0", "description": "场景节点批量编辑工具", "author": "internal-tools", "main": "./dist/main.js", "editor": ">=3.4.0", "contributions": { "menu": [ { "path": "开发/节点批量编辑", "message": "node-batch-editor:openPanel" } ], "messages": { "node-batch-editor:openPanel": { "methods": ["openPanel"] }, "node-batch-editor:queryNodeList": { "methods": ["queryNodeList"] }, "node-batch-editor:batchUpdate": { "methods": ["batchUpdate"] } }, "panels": { "node-batch-editor.panel": { "type": "dockable", "title": "节点批量编辑", "main": "./dist/panel.js" } }, "scene": { "script": "./dist/scene.js" } } }

几个容易踩坑的细节:

name字段是扩展包的唯一标识,编辑器内部所有消息分发都依赖它。main必须指向编译后的 JS 文件,如果写成 TS 路径,编辑器不会帮你做转换,会直接报错。editor字段声明兼容的编辑器版本范围,3.4 到 3.8 的扩展 API 大体一致,但细分接口有差异,建议先锁定一个主版本再测兼容。

2.3 菜单、面板和场景脚本的声明逻辑

contributions是整个扩展包的能力注册表。菜单项、消息、面板、场景脚本,全部要在里面声明,编辑器才能识别。

我的注册思路是这样的:

  • menu注册了一个菜单入口,通过消息node-batch-editor:openPanel打开面板。
  • messages里注册了三个消息,分别对应打开面板、查询节点列表、执行批量修改。这些消息方法会在主进程入口main.js里实现。
  • panels注册了一个可停靠面板,标题叫"节点批量编辑",面板入口指向panel.js。
  • scene.script是关键项,它声明了一个场景脚本文件。场景脚本运行在场景进程里,可以直接访问当前正在编辑的场景数据。没有这一项,插件就碰不到场景节点。

我最初犯过一个错误:想当然地认为主进程里可以直接director.getScene()操作场景节点。实际上主进程和场景进程是分开的,场景数据的真正操作必须通过场景脚本完成。理解了进程模型,package.json 里scene.script的作用就一目了然了。

3. 主进程入口:生命周期里该干的事,和不该干的事

3.1 load / unload / start / run 各自的意义

扩展包加载时,编辑器会调用生命周期钩子。不同钩子适合干不同的事情,用错了轻则逻辑错乱,重则拖累编辑器启动速度。

load在扩展包被加载时调用,适合做资源初始化、读取配置。unload反向对应,在扩展被禁用或移除时调用,用来释放资源、清理监听。start在编辑器和扩展的所有初始化结束后调用,此时适合拉起面板、注册快捷键。run则是用户主动点击扩展时调用的入口,相当于"运行按钮"。

我这次的扩展大部分功能都在面板和菜单里,主进程生命周期做的事很简单:

export function load() { // 读取本地配置文件,如果不存在则使用默认配置 } export function unload() { // 无全局监听,资源由编辑器回收 } export function start() { // 不自动打开面板,等用户通过菜单打开 }

unload里我特意什么也没放。因为插件没有注册全局事件监听,也没有持有编辑器资源引用,强制清理反而可能在编辑器退出时报错。这里的原则是:只在确实注册了东西的时候做对称释放,没有注册就不要硬写清理逻辑。

3.2 在 main.js 里只做消息中转,不碰场景节点

主进程是扩展包的"总部",但真正干活的场景脚本在"分厂"。所以主进程的职责应该是路由和编排,而不是直接操作场景数据。

我的 main 文件里定义了三个方法,分别对应 package.json 里声明的消息:

import { Message } from '@editor/message'; export const methods = { openPanel() { Message.request('node-batch-editor', 'openPanel'); }, async queryNodeList(filter: string) { const result = await Message.request('scene', 'execute-scene-script', { name: 'node-batch-editor', method: 'queryNodes', args: [filter] }); return result; }, async batchUpdate(options: { uuids: string[]; active?: boolean; sortToTop?: boolean; replaceName?: { from: string; to: string }; }) { const result = await Message.request('scene', 'execute-scene-script', { name: 'node-batch-editor', method: 'batchUpdate', args: [options] }); return result; } };

这里一定要理解execute-scene-script的意图。它表示"把这段调用发给场景进程,执行里面指定的场景脚本方法"。name字段填的是扩展包名,method是场景脚本里导出的方法名,args是传给该方法的参数数组。

3.3 第一个能跑通的小目标:注册菜单并打开面板

很多新手写扩展一上来就想把功能全写完,结果面板还没出来就卡在消息通信上。我的建议是分阶段,第一步只做菜单加空面板。

先写一个空的 panel 类,然后通过菜单打开。这个目标一旦跑通,说明 package.json、目录结构、编译流程、主进程加载都没有问题,后面所有功能开发都建立在这个地基上。

打开面板的消息链路是:点击菜单开发/节点批量编辑,主进程收到openPanel消息,通过Message.request('node-batch-editor', 'openPanel')请求打开对应面板。面板注册时我用了dockable类型,它支持像资源面板那样停靠在编辑器四周,比detached独立窗口更顺手。

这个阶段踩的最多的坑是:改了扩展代码之后,编辑器没有刷新。扩展包代码不像项目脚本那样保存后自动热更,需要去扩展管理器里手动"重载"一次,甚至有时候需要重启编辑器。我把这个写进了自己的开发流程,每次改完主进程代码就重载扩展,否则查半天才发现跑的还是旧代码。

4. 消息链路与场景脚本:数据到底是怎么拿到手的

4.1 编辑器三层架构和消息路径

Cocos Creator 3.x 的编辑器扩展模型是分进程的,必须把这个模型搞明白,否则插件开发寸步难行。

整个链路里涉及三个逻辑层:浏览器层、主进程层、场景进程层。面板和 UI 跑在浏览器层,也就是 Editor 的前端;扩展主进程跑在主进程层,类似后台服务;场景脚本跑在场景进程层,只有它能访问编辑器正在编辑的场景。

消息从面板发起,经过主进程转发,最终落到场景脚本,是这个链路的核心。例如查询节点列表的完整路径是:

面板(浏览器层) -> 主进程(扩展 main) -> 场景进程(scene script) -> 返回数据

面板发起查询用Message.request发到主进程,主进程再通过execute-scene-script发给场景脚本。场景脚本执行完把结果原样返回,主进程拿到后再返给面板。整个过程是异步的,所以主进程里到处是async / await。

4.2 场景脚本的写法:遍历节点并回传数据

场景脚本文件运行在场景进程,可以像项目脚本一样直接require('cc')。我做的是一个编辑期节点批量工具,所以用的是director.getScene()获取当前编辑场景,然后遍历节点树。注意这里拿到的节点就是编辑器场景里的真实节点,不是序列化副本,这意味着改什么立刻生效,也能配合撤销系统。

查询节点的方法我简化成了这样:

const { director } = require('cc'); export const methods = { queryNodes(filter: string) { const scene = director.getScene(); if (!scene) { return []; } const result = []; walkNode(scene, filter, result); return result.map((node) => ({ uuid: node.uuid, name: node.name, active: node.active, siblingIndex: node.getSiblingIndex(), parentName: node.parent ? node.parent.name : '' })); }, batchUpdate(options: any, undoManager: any) { if (!options || !Array.isArray(options.uuids)) { return { success: false, message: '没有有效的节点 UUID 列表' }; } // 具体批量修改逻辑在下面展开 } }; function walkNode(node, filter, result) { if (filter && !node.name.includes(filter)) { // 当前节点不匹配,但它的子节点可能匹配,继续往下走 } else if (filter) { result.push(node); } for (const child of node.children) { walkNode(child, filter, result); } }

这里有一个细节我特别提醒自己注意:名称筛选不能只看当前节点是否匹配,必须继续向下遍历子树,因为父节点不匹配不代表子节点不匹配。如果中途剪枝,很可能漏掉目标节点。

4.3 批量修改与 undoManager 的正确姿势

批量修改不能裸写属性赋值,否则撤销系统完全不知道发生了什么。场景脚本方法会注入一个undoManager参数,这是编辑器场景撤销系统的管理器。修改前把节点原始状态记进来,修改后再提交,用户按 Ctrl+Z 才能逐级撤销回修改前。

批量修改的核心逻辑我只保留了三种操作:

batchUpdate(options: any, undoManager: any) { const scene = director.getScene(); if (!scene) { return { success: false, message: '当前没有打开的场景' }; } const nodes = options.uuids .map((uuid) => findNodeByUuid(scene, uuid)) .filter((node) => node !== null); if (nodes.length === 0) { return { success: false, message: '未找到对应节点' }; } if (options.active !== undefined) { nodes.forEach((node) => { undoManager.add(); node.active = options.active; }); } if (options.sortToTop !== undefined) { nodes.forEach((node) => { undoManager.add(); node.setSiblingIndex(node.parent.children.length - 1); }); } if (options.replaceName) { nodes.forEach((node) => { undoManager.add(); node.name = node.name.replace(options.replaceName.from, options.replaceName.to); }); } return { success: true, count: nodes.length }; }

实际开发中我每个操作都调用了undoManager.add(),这样撤销系统能把每步操作当成独立的撤销单元。如果多个属性一起改,通常希望一次撤销全部还原,这时可以考虑在操作前只调一次add()记录起始状态。这个取舍要结合业务体验来,我在最终版本里讨了个巧,每类操作单独add(),这样撤销回退更细粒度。

需要特别说明的是:undoManager的具体接口在不同小版本里略有差异。如果你的编辑器版本里方法签名不一致,先用console.log(undoManager)看看结构,再决定怎么调。这是我在 3.4 和 3.8 之间切换时总结出来的经验。

4.4 失败与空结果的处理

批量操作最怕"静默失败"。用户点了一下按钮,什么反馈都没有,还以为成功了,结果打开场景一看啥也没变,这是最影响信任感的。

我做了两件事:第一,场景脚本所有方法都返回结构化结果,包括success和message字段,以及实际处理数量;第二,主进程把结果原样回传给面板,面板根据结果显示成功或错误提示。

查询为空同样要处理。我最初查询不到节点时直接返回空数组,面板上一片空白,用户根本搞不清是场景没打开还是筛选条件没匹配对。后来改成返回一个带code的包装对象,面板根据code显示"未打开场景"或"未匹配到节点",问题定位就清晰多了。这类看似不起眼的反馈设计,实际使用体验差距非常大。

5. 面板开发:用最朴素的方式做出一个能用的 UI

5.1 从空模板起步:创建面板类

面板文件是扩展里离用户最近的部分。Cocos Creator 3.x 的面板支持 Vue、React 等框架,但我的第一版选择的是最原始的方式:HTML 模板加原生 DOM 操作。

选择原生方式不是因为我不会框架,而是因为扩展面板的构建链路本来比较简单,引入 Vue 至少多几层配置和依赖,也更容易在升级编辑器时出现兼容问题。等面板复杂到需要复杂状态管理时再迁移不迟。

一个最小可用的面板类长这样:

import { Panel } from '@editor/panel'; type A = { template: string; style: string; $: Record<string, HTMLElement>; ready(): void; close?(): void; }; export default class NodeBatchEditorPanel extends Panel { template = ``; style = ``; $ = {}; ready() { this.queryNodes(); } }

模板和样式写在类的字段里,$里放通过 id 选择器拿到的元素引用。面板显示时会自动执行ready方法,适合做初始数据加载。

5.2 用内置控件拼出查询区、结果列表和操作按钮

Cocos Creator 扩展面板自带一套ui-前缀的界面组件,比如ui-input、ui-checkbox、ui-button。用它们能让面板风格和编辑器保持一致,省去大量手写 CSS 的时间。

我的面板模板很简单,分成三个区域:顶部是关键词输入框和查询按钮,中间是节点列表结果区,底部是批量操作按钮。

<ui-prop> <span>节点名关键词</span> <ui-input id="filter-input" placeholder="支持模糊匹配"></ui-input> <ui-button id="query-btn">查询</ui-button> </ui-prop> <div id="node-list" style="flex: 1; overflow-y: auto;"></div> <ui-prop> <ui-checkbox id="active-checkbox">激活</ui-checkbox> <ui-checkbox id="inactive-checkbox">禁用</ui-checkbox> <ui-checkbox id="to-top-checkbox">置顶</ui-checkbox> <ui-checkbox id="to-bottom-checkbox">置底</ui-checkbox> </ui-prop> <div> <ui-input id="replace-from" placeholder="原词"></ui-input> <span>-></span> <ui-input id="replace-to" placeholder="新词"></ui-input> </div> <ui-button id="apply-btn" class="red">执行批量修改</ui-button>

中间的结果列表起初我用了一个带滚动条的div,在里面动态塞 checkbox,让用户可以逐项勾选需要处理的节点。这个交互设计很关键:批量修改是先筛选出候选列表,再由用户二次确认,避免"筛选即执行"造成的误伤。

5.3 数据和事件的绑定

ready方法里第一件事是把查询按钮的点击事件绑定好。使用this.$['query-btn']直接拿 DOM 元素,然后监听它的点击事件。

ready() { this.$['query-btn'].addEventListener('click', () => this.queryNodes()); this.$['apply-btn'].addEventListener('click', () => this.applyBatch()); this.queryNodes(); }

查询节点时,面板通过消息向主进程发请求,拿回结果后渲染到列表区域。每个节点前面加一个复选框,后面显示节点名、激活状态和父节点名,让用户有足够信息判断该不该打勾。默认全选,允许手动取消。

5.4 面板与主进程之间的消息收尾

面板发消息的代码非常直接:

async queryNodes() { const filter = this.$['filter-input'].value; const result = await Message.request('node-batch-editor', 'queryNodeList', filter); this.renderList(result); }

注意面板向主进程发请求时,用的是扩展包名加方法名。而主进程向外发场景命令时,用的是scene和execute-scene-script。这两个方向的消息目标不一样,形式也不同,最初我总把两者搞混。记住一个原则:目标是扩展主进程,写扩展包名;目标是场景脚本,写scene加execute-scene-script,就不容易错了。

执行批量修改时,面板把勾选节点的 uuid 数组和操作参数打包,发消息给主进程,主进程再交给场景脚本。整个链路看着长,但延迟基本在几十毫秒内,面板的交互流畅度不会有任何问题。

我在写完面板第一版后发现一个体验问题:结果列表刷新时,没有把用户之前的勾选状态保留。比如用户勾了前三个节点,然后重新改了关键词再查询,勾选全部丢失。后来我改成以 uuid 为 key 维护一个已选集合,渲染时检查是否已被选中,就解决了。这种细节很琐碎,但直接影响用户愿不愿意长期用。

6. 端到端实测:点击一次批量修改,发生了什么

6.1 实测场景的准备

开发完成后,我在一个模拟项目里做了完整实测。场景里摆放了几十个节点,命名分成bg_和ui_两组,故意让一部分bg_节点在一级节点下面,一部分嵌套在二级节点下面,用来验证递归遍历有没有问题。

操作流程我直接照真实的编辑器操作来了:打开编辑器,加载项目,打开一个测试场景,通过菜单开发/节点批量编辑打开面板,输入关键词bg_,点击查询,确认列表里出现了所有带bg_前缀的节点,然后勾选全部,把激活状态设为禁用,点击执行。

6.2 完整操作路径和编辑器日志输出

我把关键的日志输出集成到了主进程和场景脚本里,方便调试时观察链路:

执行前,场景脚本接到execute-scene-script指令后,先输出[scene] 开始批量修改,目标节点数: 12,说明节点解析正常。批量修改完成后,输出[scene] 修改完成,已处理: 12。回到主进程,输出[main] batchUpdate 返回: {success: true, count: 12}。最后面板拿到结果,显示修改完成,共处理 12 个节点。

整个过程链路是通的。关键是执行完成后我手工检查了场景层级面板,确认目标节点的 active 复选框确实变成了灰色,节点仍然存在,没有丢失引用,没有意外的名称变化。

6.3 撤销/重做验证

批量操作没有撤销就是事故。我专门验证了:

修改完成后,按 Ctrl+Z,场景开关状态立刻恢复到执行前,层级管理器的节点勾选状态也同步还原。继续按 Ctrl+Z,某个置顶操作也会回到之前的位置。因为我在场景脚本里每个操作前都调用了undoManager.add(),所以撤销粒度是逐类操作,而不是整体一起还原。

再按 Ctrl+Shift+Z 重做,修改又出现在场景里。这个环节确认后,我才敢把工具交给别的同事试。

6.4 这套方案在真实项目里的边界问题

实测中暴露了几个边界问题,我记录下来供参考。

第一,筛选条件不能太宽泛。如果在真实项目里输入一个很通用的词,比如ui,可能一下子匹配上千节点,面板数据量和实际执行时间都会显著增长。我给面板加了最大返回数量保护,超过五百条就提示用户收窄关键词。

第二,同名节点极多。真实项目里名字重复的节点非常多,仅凭名字筛选会误伤一大片。我在列表里额外显示了父节点路径,让用户能通过路径区分同名节点。更进一步的想法是支持正则表达式筛选,但这个放到后续版本再考虑。

第三,场景层级未打开时,查询结果为空,但编辑器并未报错。最开始我很困惑,后来定位到是director.getScene()返回了null。这说明场景脚本的执行依赖场景编辑器处于激活状态。我加了明确提示:"当前没有打开场景,请先打开需要处理的场景。"

7. 调试技巧与分发时的几个注意点

7.1 三个好用的调试手段

扩展调试的过程比游戏逻辑调试要绕一些,因为跨进程。我实际用的最多的三个手段:

第一个是日志分层。主进程日志用Editor.log输出,场景脚本用console.log输出。场景脚本日志默认能在编辑器控制台看到,但主进程日志有时需要打开开发者工具才能看清对象结构。我习惯在关键路径都输出一条带前缀的日志,比如[main]、[scene]、[panel],这样一眼就能定位消息到了哪一层。

第二个是重载扩展包。桌面端扩展包代码改变后,必须在扩展管理器里点重载,甚至重启编辑器,否则跑的是旧代码。为了减少重启次数,我把核心逻辑都放到场景脚本里,因为场景脚本在某些版本里是可以跟着场景重载一起刷新的,迭代效率高很多。

第三个是面板的开发者工具。面板本质上是浏览器页面,按住正确快捷键或者通过右键菜单打开面板开发者工具后,可以像调试网页一样看 DOM、看网络、看控制台。排查数据渲染问题我全靠这个,比在黑盒里瞎猜高效太多了。

7.2 团队内部分发与上架扩展商店的差异

打包插件给团队内部用,最简单的做法就是把整个node-batch-editor目录压缩成 zip,同事导入编辑器后放到extensions目录或者通过扩展管理器导入即可。这个方式完全不依赖扩展商店,适合内部工具。

如果要上架扩展到扩展商店,就要额外准备商店素材,包括插件图标、功能截图、说明文档、版本更新记录等。商店审核会把插件和 Creator 的版本兼容性纳为重点,我建议在package.json里明确声明editor版本范围,并且在多个版本上做冒烟测试。

分发还有一个隐藏问题:不同团队用的 Creator 版本可能不同,扩展的 API 表现也随之变化。内部工具可以只锁一个版本,商店扩展则要考虑更宽泛的兼容性。我的做法是尽量少用前沿 API,用处在 import 时注意版本差异,找不到就用编辑器自带的消息替代方案。

7.3 后续扩展方向

第一版功能够用,但还不算完美。我脑子里已经列了几个后续方向:

一是支持组件属性批量修改。比如把多个 Sprite 的 color 统一调整,这类需求在界面换肤时频繁出现。二是支持正则筛选。名字匹配用上正则之后,筛选能力会强很多,可以匹配模式而不是固定字符串。三是支持跨场景操作。当前工具一次只作用于一个场景,如果能把一组场景串起来批量执行,那才是真正的提效王炸。

我实际用下来的一个体会是:这种编辑器工具先小步快跑,把核心链路跑通,再逐步加功能。一次做太大,往往卡在细节里出不来。开发过程中最有价值的决策是"场景脚本负责操作、主进程负责路由、面板只负责交互",这个分层思路让每一层都足够简单,出问题也容易定位。最后再分享一个小技巧,开发这种批量修改工具时,无论如何都要给用户留一个"查询后预览"的中间环节,挑选节点确认后再执行,这是避免批量操作事故的最有效手段。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 5:23:27

Unity3D轻量级数字孪生工业骨架:可部署、可替换、可拆解

简介&#xff1a;本资源是一个基于Unity3d&#xff08;WebGL&#xff09;实现的轻量级数字孪生实践项目&#xff0c;面向Unity开发初学者、物联网与Web三维可视化学习者&#xff0c;以及希望掌握软硬协同数据交互流程的全栈开发者。项目完整覆盖硬件&#xff08;NodeMCU&#x…

作者头像 李华
网站建设 2026/10/20 5:22:51

Blending算法实战:模型融合中的原理、代码与避坑指南

堆叠学习&#xff08;Stacking&#xff09;和Blending算法&#xff0c;是我在做模型融合时最先认真研究的一对概念。很多人第一次听到Blending&#xff0c;是在某个数据挖掘比赛的Top方案里&#xff1b;等真到自己手上写代码时&#xff0c;往往会发现它对数据划分的要求比想象中…

作者头像 李华
网站建设 2026/10/20 5:22:13

AlcoholTestBro 隐私政策 / Privacy Policy

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 5:21:31

RAG已死?文件系统能否替代检索?混合架构实战解析

1. 从“RAG 已死”的论调说起&#xff1a;一场关于检索范式的路线之争最近半年&#xff0c;技术圈里隔三差五就冒出“RAG 已死”的声音。起因也不复杂&#xff1a;一批 Agent 产品在演示里表现得像是把整份文档“吃”进了脑子里&#xff0c;用户问什么它答什么&#xff0c;既没…

作者头像 李华
网站建设 2026/10/20 5:21:08

大模型安全评估测试题设计:从风险分类到自动化评估

大模型备案安全评估测试题&#xff0c;这几个字放一起&#xff0c;很容易让刚接手的人误以为就是找一堆问题丢给模型回答。我见过不少团队第一版测试题就是几个同事凭印象攒了五十条&#xff0c;跑完一轮&#xff0c;模型该过还是过&#xff0c;该出事的还是出事。真正管用的安…

作者头像 李华
网站建设 2026/10/20 5:21:07

基于YOLOv12的3D打印缺陷检测系统实战:从数据标注到部署

最开始接到3D打印质检需求的时候&#xff0c;我心里想的还是老一套&#xff1a;拍几张照片&#xff0c;拿OpenCV做个边缘检测&#xff0c;把翘曲、拉丝找出来。结果在实验室样本上跑得很顺&#xff0c;一到真实打印现场就全面翻车——不同材料颜色反光不一样&#xff0c;支撑结…

作者头像 李华