(function (global) { 'use strict'; var WorkbenchAgent = global.PWAgent; var U = global.PWAgentUtil; var nodeId = U && U.nodeId; if (!WorkbenchAgent) { console.error('[调试工作台] agent-serialize 需先加载 agent-core'); return; } if (!nodeId) { console.error('[调试工作台] agent-serialize 需先加载 agent-util'); return; } var COMP_PROP_SKIP = { _name: 1, _objFlags: 1, __scriptAsset: 1, node: 1, _enabled: 1, __prefab: 1, __editorExtras__: 1, uuid: 1, name: 1, enabled: 1, }; /** * 读取 CCClass 属性元数据(displayName / type / enumList / visible)。 * @param {Function} ctor * @returns {object} */ function getCtorAttrs(ctor) { if (!ctor) return {}; if (ctor.__attrs__ && typeof ctor.__attrs__ === 'object') return ctor.__attrs__; return {}; } /** * @param {object} attrs * @param {string} key * @param {string} name * @returns {*} */ function attrOf(attrs, key, name) { return attrs[key + '$_$' + name]; } /** * 列出组件应展示的属性名(对齐编辑器:走 __props__,隐藏 _ 开头与 visible:false)。 * @param {Function} ctor * @returns {string[]} */ function listCompPropKeys(ctor) { var attrs = getCtorAttrs(ctor); var raw = (ctor && (ctor.__props__ || ctor.__values__)) || []; var out = []; for (var i = 0; i < raw.length; i++) { var key = raw[i]; if (!key || COMP_PROP_SKIP[key]) continue; if (key.charAt(0) === '_' || key.indexOf('__') === 0) continue; if (attrOf(attrs, key, 'visible') === false) continue; out.push(key); } return out; } /** * 将运行时值序列化为 Inspector DTO。 * @param {*} val * @param {object} attrs * @param {string} key * @param {object} cc * @returns {{type:string, value:*, enumOptions?:Array, readOnly?:boolean}} */ function serializeFieldValue(val, attrs, key, cc) { var enumList = attrOf(attrs, key, 'enumList'); var typeTag = attrOf(attrs, key, 'type'); if (enumList && enumList.length) { return { type: 'enum', value: val, enumOptions: enumList.map(function (it) { return { name: String(it.name), value: it.value }; }), }; } if (typeTag === 'Enum') { return { type: 'number', value: typeof val === 'number' ? val : Number(val) || 0 }; } var t = typeof val; if (t === 'boolean' || t === 'string') return { type: t, value: val }; if (t === 'number') return { type: 'number', value: isFinite(val) ? val : 0 }; if (val == null) { var ctorHint = attrOf(attrs, key, 'ctor'); var hintName = ctorHint && (ctorHint.name || ctorHint.prototype && ctorHint.prototype.__classname__); if (hintName || typeTag === 'Object') { return { type: 'ref', readOnly: true, value: { kind: hintName || 'Object', name: '(空)', uuid: '' }, }; } return { type: 'string', value: '' }; } if (cc && cc.Color && val instanceof cc.Color) { return { type: 'color', value: { r: val.r | 0, g: val.g | 0, b: val.b | 0, a: val.a != null ? (val.a | 0) : 255 }, }; } // Size if (typeof val.width === 'number' && typeof val.height === 'number' && val.x === undefined) { return { type: 'size', value: { width: val.width, height: val.height } }; } // Vec2 / Vec3 if (typeof val.x === 'number' && typeof val.y === 'number') { if (typeof val.z === 'number') { return { type: 'vec3', value: { x: val.x, y: val.y, z: val.z } }; } return { type: 'vec2', value: { x: val.x, y: val.y } }; } // Asset / Node / Component 引用 if (typeof val === 'object') { var kind = (val.constructor && val.constructor.name) || 'Object'; var name = ''; if (val.name != null) name = String(val.name); else if (val.node && val.node.name) name = String(val.node.name); var uuid = val._uuid || val.uuid || ''; return { type: 'ref', readOnly: true, value: { kind: kind, name: name || kind, uuid: uuid ? String(uuid) : '' }, }; } return { type: 'string', value: String(val) }; } /** * 提取单个组件的 Inspector 字段列表。 * @param {object} comp * @param {number} index * @param {object} cc * @returns {{name:string, index:number, fields:Array}} */ function extractCompFields(comp, index, cc) { var ctor = comp.constructor; var cname = (cc && cc.js && typeof cc.js.getClassName === 'function') ? (cc.js.getClassName(ctor) || ctor.name) : (ctor && ctor.name) || 'Component'; var attrs = getCtorAttrs(ctor); var keys = listCompPropKeys(ctor); // 无元数据时有限回退 if (!keys.length) { var own = Object.keys(comp); for (var o = 0; o < own.length && keys.length < 24; o++) { var ok = own[o]; if (!ok || ok.charAt(0) === '_' || COMP_PROP_SKIP[ok]) continue; var ot = typeof comp[ok]; if (ot === 'number' || ot === 'boolean' || ot === 'string') keys.push(ok); } if (!keys.length) { console.warn('[Agent] 组件无 CCClass 属性元数据', cname); } } var fields = []; var max = 80; for (var i = 0; i < keys.length && fields.length < max; i++) { var key = keys[i]; var label = attrOf(attrs, key, 'displayName') || key; var raw; try { raw = comp[key]; } catch (e) { continue; } var ser = serializeFieldValue(raw, attrs, key, cc); fields.push({ key: key, label: label, type: ser.type, value: ser.value, enumOptions: ser.enumOptions || null, readOnly: !!ser.readOnly, }); } return { name: cname, index: index, fields: fields }; } WorkbenchAgent.prototype._getNodeProps = function (uuid) { var node = this._findNode(uuid); if (!node) return null; var cc = this.cc; var p = node.getPosition ? node.getPosition() : node.position; var e = node.eulerAngles || { x: 0, y: 0, z: 0 }; var s = node.getScale ? node.getScale() : node.scale; var comps = []; if (node.components) { for (var i = 0; i < node.components.length; i++) { var comp = node.components[i]; if (!comp || !comp.constructor) continue; comps.push(extractCompFields(comp, i, cc)); } } return { uuid: nodeId(node), name: node.name || '', active: !!node.active, layer: node.layer, pos: { x: p.x, y: p.y, z: p.z }, euler: { x: e.x, y: e.y, z: e.z }, scale: { x: s.x, y: s.y, z: s.z }, comps: comps, }; }; WorkbenchAgent.prototype._setNodeProp = function (uuid, fid, value) { var node = this._findNode(uuid); if (!node || !fid) return false; var cc = this.cc; try { if (fid === 'name') node.name = String(value); else if (fid === 'active') node.active = !!value; else if (fid === 'layer') node.layer = Number(value) || 0; else if (fid === 'px' || fid === 'py' || fid === 'pz') { var p = node.getPosition ? node.getPosition() : node.position; var x = fid === 'px' ? Number(value) : p.x; var y = fid === 'py' ? Number(value) : p.y; var z = fid === 'pz' ? Number(value) : p.z; if (typeof node.setPosition === 'function') node.setPosition(x, y, z); else if (cc && cc.Vec3) node.position = new cc.Vec3(x, y, z); } else if (fid === 'rx' || fid === 'ry' || fid === 'rz') { var e = node.eulerAngles || { x: 0, y: 0, z: 0 }; var ex = fid === 'rx' ? Number(value) : e.x; var ey = fid === 'ry' ? Number(value) : e.y; var ez = fid === 'rz' ? Number(value) : e.z; if (typeof node.setRotationFromEuler === 'function') node.setRotationFromEuler(ex, ey, ez); else if (cc && cc.Vec3) node.eulerAngles = new cc.Vec3(ex, ey, ez); } else if (fid === 'sx' || fid === 'sy' || fid === 'sz') { var sc = node.getScale ? node.getScale() : node.scale; var sx = fid === 'sx' ? Number(value) : sc.x; var sy = fid === 'sy' ? Number(value) : sc.y; var sz = fid === 'sz' ? Number(value) : sc.z; if (typeof node.setScale === 'function') node.setScale(sx, sy, sz); else if (cc && cc.Vec3) node.scale = new cc.Vec3(sx, sy, sz); } else if (fid.indexOf('c') === 0 && fid.indexOf('_') > 0) { return this._setCompProp(node, fid, value); } else { return false; } return true; } catch (err) { console.warn('[Agent] set prop', fid, err); return false; } }; /** * 写回组件字段。fid: c{index}_{key}[_axis] * @param {object} node * @param {string} fid * @param {*} value * @returns {boolean} */ WorkbenchAgent.prototype._setCompProp = function (node, fid, value) { var m = /^c(\d+)_(.+)$/.exec(fid); if (!m) return false; var ci = parseInt(m[1], 10); var rest = m[2]; var comp = node.components && node.components[ci]; if (!comp) return false; var cc = this.cc; var axis = ''; var key = rest; var axisMatch = /^(.*)_(x|y|z|w|h|r|g|b|a|hex)$/.exec(rest); if (axisMatch) { // 优先整键命中(字段名本身含下划线时仍以整键为准) if (!(rest in comp) && (axisMatch[1] in comp || hasCompProp(comp, axisMatch[1]))) { key = axisMatch[1]; axis = axisMatch[2]; } } if (axis) { return setCompAxisValue(comp, key, axis, value, cc); } var attrs = getCtorAttrs(comp.constructor); if (attrOf(attrs, key, 'type') === 'Object' || attrOf(attrs, key, 'ctor')) { // 引用只读 return false; } var cur; try { cur = comp[key]; } catch (e1) { return false; } if (cur && typeof cur === 'object' && !(typeof cur === 'boolean')) { // 未带轴的对象写入拒绝(避免覆盖引用) if (typeof cur.x === 'number' || typeof cur.width === 'number' || (cc && cc.Color && cur instanceof cc.Color)) { return false; } } try { if (typeof cur === 'boolean') comp[key] = !!value; else if (typeof cur === 'number') comp[key] = Number(value); else if (typeof cur === 'string') comp[key] = String(value); else if (attrOf(attrs, key, 'enumList')) comp[key] = Number(value); else comp[key] = value; return true; } catch (e2) { console.warn('[Agent] set comp prop', key, e2); return false; } }; /** * @param {object} comp * @param {string} key * @returns {boolean} */ function hasCompProp(comp, key) { if (!comp || !key) return false; if (key in comp) return true; var ctor = comp.constructor; var props = (ctor && (ctor.__props__ || ctor.__values__)) || []; return props.indexOf(key) >= 0; } /** * @param {object} comp * @param {string} key * @param {string} axis * @param {*} value * @param {object} cc * @returns {boolean} */ function setCompAxisValue(comp, key, axis, value, cc) { var cur; try { cur = comp[key]; } catch (e) { return false; } if (axis === 'hex') { var hex = String(value).replace(/^#/, ''); if (hex.length < 6) return false; var r = parseInt(hex.slice(0, 2), 16); var g = parseInt(hex.slice(2, 4), 16); var b = parseInt(hex.slice(4, 6), 16); var a = hex.length >= 8 ? parseInt(hex.slice(6, 8), 16) : 255; if (cc && cc.Color) { comp[key] = new cc.Color(r, g, b, a); return true; } if (cur && typeof cur.r === 'number') { cur.r = r; cur.g = g; cur.b = b; cur.a = a; return true; } return false; } var num = Number(value); if (!isFinite(num)) return false; if (!cur || typeof cur !== 'object') return false; if (axis === 'w' && typeof cur.width === 'number') { cur.width = num; return true; } if (axis === 'h' && typeof cur.height === 'number') { cur.height = num; return true; } if ((axis === 'x' || axis === 'y' || axis === 'z') && typeof cur[axis] === 'number') { cur[axis] = num; // 触发 setter(部分引擎属性需整体赋值) try { comp[key] = cur; } catch (e2) { /* 就地改写已足够 */ } return true; } if ((axis === 'r' || axis === 'g' || axis === 'b' || axis === 'a') && typeof cur[axis] === 'number') { cur[axis] = num; try { comp[key] = cur; } catch (e3) { /* */ } return true; } return false; } })(window);