// eval 注入脚本的代码,变量尽量使用var,后来发现在import之后,let会自动变为var import { uniq } from "lodash"; import { Msg, PluginEvent, RequestLogData, RequestNodeInfoData, RequestSetPropertyData, ResponseGameInfoData, ResponseNodeInfoData, ResponseSetPropertyData, ResponseSupportData, ResponseTreeInfoData } from "../../core/types"; import { CompType, getNodeIcon } from "../../views/devtools/comp"; import { ArrayData, BoolData, ColorData, DataType, EngineData, EnumData, Group, ImageData, Info, InvalidData, NodeInfoData, NumberData, ObjectCircleData, ObjectData, Property, StringData, TreeData, Vec2Data, Vec3Data, Vec4Data } from "../../views/devtools/data"; import { getEnumListConfig } from "./enumConfig"; import { InjectEvent } from "./event"; import { Hint } from "./hint"; import { injectView } from "./inject-view"; import { inspectTarget } from "./inspect-list"; import { getValue, trySetValueWithConfig } from "./setValue"; import { BuildArrayOptions, BuildImageOptions, BuildObjectOptions, BuildVecOptions } from "./types"; import { isHasProperty } from "./util"; declare const cc: any; export class Inspector extends InjectEvent { inspectorGameMemoryStorage: Record = {}; private hint: Hint = new Hint(this); private getAtlasViewFunction() { // 之前只有v2版本支持 return cc?.dynamicAtlasManager?.showDebug; } onMessage(pluginEvent: PluginEvent): void { switch (pluginEvent.msg) { case Msg.RequestSupport: { const isCocosGame = this._isCocosGame(); this.notifySupportGame(isCocosGame); break; } case Msg.RequstTreeInfo: { this.updateTreeInfo(); break; } case Msg.VisibleFPS: { const b = pluginEvent.data as boolean; if (b) { if (cc.profiler?.showStats) { cc.profiler.showStats(); } else if (cc.debug?.setDisplayStats) { cc.debug.setDisplayStats(true); } } else { if (cc.profiler?.hideStats) { cc.profiler.hideStats(); } else if (cc.debug?.setDisplayStats) { cc.debug.setDisplayStats(false); } } break; } case Msg.RequestGameInfo: { const ret = new ResponseGameInfoData(); const atlasManager = cc?.dynamicAtlasManager || cc.internal?.dynamicAtlasManager || null; if (atlasManager) { ret.dynamicAtals.enable = atlasManager.enabled; ret.dynamicAtals.atlasCount = atlasManager.atlasCount; ret.dynamicAtals.maxAtlasCount = atlasManager.maxAtlasCount; ret.dynamicAtals.maxFrameSize = atlasManager.maxFrameSize; ret.dynamicAtals.textureSize = atlasManager.textureSize; ret.dynamicAtals.textureBleeding = atlasManager.textureBleeding; ret.dynamicAtals.supportView = !!this.getAtlasViewFunction(); this.sendMsgToContent(Msg.ResponseGameInfo, ret); } break; } case Msg.RequestDynamicAtlasView: { const b = pluginEvent.data as boolean; const cb = this.getAtlasViewFunction(); if (cb) { cb(b); this.sendMsgToContent(Msg.ResponseDynamicAtlasView, b); } break; } case Msg.RequestNodeInfo: { const data = pluginEvent.data as RequestNodeInfoData; this.getNodeInfo(data.uuid); break; } case Msg.RequestSetProperty: { const data: RequestSetPropertyData = pluginEvent.data; let value = data.data; if (data.type === DataType.Color) { // @ts-ignore value = cc.color().fromHEX(value); } if (this.setValue(data.path, value)) { this.sendMsgToContent(Msg.ResponseSetProperty, data as ResponseSetPropertyData); } else { console.warn(`设置失败:${data.path}`); } break; } case Msg.RequestLogData: { const data: RequestLogData = pluginEvent.data; const value = getValue(this.inspectorGameMemoryStorage, data); // 直接写console.log会被tree shaking const logFunction = console.log; logFunction(value); break; } case Msg.RequestLogCustom: { const logFunction = console.log; logFunction(pluginEvent.data); break; } case Msg.ReqWriteClipboard: { navigator.clipboard .writeText(pluginEvent.data) .then(() => { console.log("ok"); }) .catch((e) => { console.log("fail", e); }); break; } case Msg.RequestVisible: { const uuid: string = pluginEvent.data; const node = this.inspectorGameMemoryStorage[uuid]; if (node) { node.active = !node.active; } break; } case Msg.HoverNode: { const uuid: string = pluginEvent.data; if (uuid) { const node = this.inspectorGameMemoryStorage[uuid]; if (node && node.isValid) { this.hint.setHover(node); } else { this.hint.cleanHover(); } } else { this.hint.cleanHover(); } break; } case Msg.SelectNode: { const uuid: string = pluginEvent.data; if (uuid) { const node = this.inspectorGameMemoryStorage[uuid]; if (node && node.isValid) { this.hint.setSelected(node); } else { this.hint.cleanSelected(); } } else { this.hint.cleanSelected(); } break; } case Msg.RequestDestroy: { const uuid: string = pluginEvent.data; const node = this.inspectorGameMemoryStorage[uuid]; if (node && node.isValid && node.destroy) { node.destroy(); } break; } } } init() { console.log(...this.terminal.init()); this.watchIsCocosGame(); } private watchIsCocosGame() { const timer = setInterval(() => { const b = this._isCocosGame(); if (b) { clearInterval(timer); injectView.init(); cc.director.on(cc.Director.EVENT_AFTER_DRAW, () => { this.hint.update(); }); const version = this.getEngineVersion(); this.hint.setEngineVersion(version); if (b && version) { this.uploadEngineVersion(version); } } }, 300); } private getEngineVersion(): string { if (this._isCocosGame()) { return cc.ENGINE_VERSION || ""; } else { return ""; } } public isCreatorV2() { return this.getEngineVersion().startsWith("2."); } public isCreatorV3() { return this.getEngineVersion().startsWith("3."); } notifySupportGame(b: boolean) { const version = this.getEngineVersion(); this.sendMsgToContent(Msg.ResponseSupport, { support: b, msg: "", version } as ResponseSupportData); } /** * 防止后台收到大量的Engine版本数据,每次调试只上传一次 */ private hasUploadEngineVersion = false; private uploadEngineVersion(version: string) { if (this.hasUploadEngineVersion) { return; } if (version) { this.hasUploadEngineVersion = true; this.sendEngineVersion(version); } } forEachNode(cb: (node: any) => void) { for (let key in this.inspectorGameMemoryStorage) { const item = this.inspectorGameMemoryStorage[key]; if (item && item.isValid && item instanceof cc.Node) { if (inspectTarget.enabled) { if (inspectTarget.checkNodeComponentsIsInList(item)) { cb(item); } } else { cb(item); } } } } updateTreeInfo(notify: boolean = true) { let isCocosCreatorGame = this._isCocosGame(); if (isCocosCreatorGame) { let scene = cc.director.getScene(); if (scene) { let treeData = new TreeData(); this.getNodeChildren(scene, treeData); notify && this.sendMsgToContent(Msg.ResponseTreeInfo, treeData as ResponseTreeInfoData); } else { let treeData = new TreeData(); treeData.id = ""; treeData.text = "empty scene"; treeData.icon = "icon_cocos"; notify && this.sendMsgToContent(Msg.ResponseTreeInfo, treeData as ResponseTreeInfoData); } } else { notify && this.notifySupportGame(false); } } private calcIcon(node: any): string { if (node instanceof cc.Scene) { return getNodeIcon(CompType.Scene); } const components = node._components; if (components) { const types: CompType[] = []; // widget组件的优先级低于其他渲染组件 for (let i = 0; i < components.length; i++) { const component = components[i]; const type = this.checkComponent(component); if (type) { types.push(type); } } if (types.length) { //按照字母的顺序排序,目前是符合预期 types.sort(); return getNodeIcon(types[0]); } } return getNodeIcon(CompType.Node); } private checkComponent(comp: any): CompType | null { const map = {}; map[CompType.Animation] = [cc.Animation]; map[CompType.Button] = [cc.Button]; map[CompType.Spirte] = [cc.Sprite]; map[CompType.Label] = [cc.Label]; map[CompType.Node] = [cc.Node]; map[CompType.Prefab] = [cc.Prefab]; map[CompType.EditBox] = [cc.EditBox]; map[CompType.Scene] = [cc.Scene]; map[CompType.ScrollView] = [cc.ScrollView]; map[CompType.Camera] = [cc.Camera]; map[CompType.Canvas] = [cc.Canvas]; map[CompType.Mask] = [cc.Mask]; map[CompType.ProgressBar] = [cc.ProgressBar]; map[CompType.Layout] = [cc.Layout]; map[CompType.Graphics] = [cc.Graphics]; map[CompType.Widget] = [cc.Widget]; for (let key in map) { const item = map[key]; const ret = item.find((el: any) => { return el && comp instanceof el; }); if (ret) { return key as CompType; } } return null; } private calcColor(node: any) { if (node._prefab) { return "#00ff00ff"; } else { return ""; } } // 收集节点信息 getNodeChildren(node: any, data: TreeData) { data.id = node.uuid; data.text = node.name; data.icon = this.calcIcon(node); data.color = this.calcColor(node); // @ts-ignore if (node instanceof cc.Scene) { // 场景不允许获取active,引擎会报错 } else { data.active = !!node.active && !!node.activeInHierarchy; } this.inspectorGameMemoryStorage[node.uuid] = node; let nodeChildren = node.children; for (let i = 0; i < nodeChildren.length; i++) { let childItem = nodeChildren[i]; let treeData = new TreeData(); this.getNodeChildren(childItem, treeData); data.children.push(treeData); } } _isCocosGame() { // @ts-ignore 检测是否包含cc变量 return typeof cc !== "undefined"; } getAllPropertyDescriptors(obj: Object): string[] { let keys: string[] = []; function circle(root: Object) { const descriptors = Object.getOwnPropertyDescriptors(root); for (let descriptorsKey in descriptors) { if (Object.hasOwnProperty.call(descriptors, descriptorsKey)) { const value = descriptors[descriptorsKey]; // 不可枚举的属性,并且允许修改get set的才有效 if (!value.enumerable && value.configurable) { keys.push(descriptorsKey); } } } const proto = Object.getPrototypeOf(root); if (proto) { circle(proto); } } circle(obj); return keys; } isExcludeProperty(node: any, key: string) { const mapArray = [ { type: cc.UITransformComponent || cc.UITransform, keys: ["priority"], }, { type: null, // null 表示所有适配类型 keys: [ "children", "quat", "node", "components", "enabledInHierarchy", "hideFlags", "isValid", "parent", // 生命周期函数 "onFocusInEditor", "onRestore", "start", "lateUpdate", "update", "resetInEditor", "onLostFocusInEditor", "onEnable", "onDisable", "onDestroy", "onLoad", "internalLateUpdate", "internalOnDestroy", "internalOnEnable", "internalOnDisable", "internalUpdate", "internalPreload", "internalOnLoad", "internalStart", ], }, ]; for (let i = 0; i < mapArray.length; i++) { const { type, keys } = mapArray[i]; if (type === null) { if (keys.find((v) => v === key)) { return true; } } else { if (type && node instanceof type) { if (keys.find((v) => v === key)) { return true; } } } } return false; } _getNodeKeys(node: any) { // 3.x变成了getter const keyHidden = this.getAllPropertyDescriptors(node); const keyVisible1 = Object.keys(node); // Object不走原型链 let keyVisible2: string[] = []; for (let nodeKey in node) { // 走原型链 keyVisible2.push(nodeKey); } let allKeys: string[] = uniq(keyHidden.concat(keyVisible1, keyVisible2)).sort(); allKeys = allKeys.filter((key) => { return !key.startsWith("_") && !this.isExcludeProperty(node, key); }); allKeys = allKeys.filter((key) => { try { this.warnSilent(true); const type = typeof node[key]; this.warnSilent(false); return type !== "function"; } catch (e) { // console.warn(`属性${key}出现异常:\n`, e); return false; } }); return allKeys; } _getPairProperty(key: string): null | { key: string; values: string[] } { let pairProperty: Record = { rotation: ["rotationX", "rotationY"], anchor: ["anchorX", "anchorY"], size: ["width", "height"], skew: ["skewX", "skewY"], position: ["x", "y", "z"], // position比较特殊,过来的key就是position也需要能处理 scale: ["scaleX", "scaleY", "scaleZ"], }; for (let pairPropertyKey in pairProperty) { if (pairProperty.hasOwnProperty(pairPropertyKey)) { let pair = pairProperty[pairPropertyKey]; if (pair.includes(key) || key === pairPropertyKey) { return { key: pairPropertyKey, values: pair }; } } } return null; } _buildVecData(options: BuildVecOptions) { const ctor: Function = options.ctor; const keys = options.keys; const value: Object = options.value; const data: Vec3Data | Vec2Data = options.data; const path: Array = options.path; if (ctor && value instanceof ctor) { let hasUnOwnProperty = keys.find((item) => !value.hasOwnProperty(item.key)); if (!hasUnOwnProperty) { for (let key in keys) { const propName = keys[key].key; const stepFunc = keys[key].step; const disabledFunc = keys[key].disabled; if (value.hasOwnProperty(propName)) { let propPath = path.concat(propName); let itemData = this._genInfoData(value, propName, propPath); if (itemData) { if (itemData instanceof NumberData) { if (stepFunc) { itemData.step = stepFunc(propName); } else if (options.step) { itemData.step = options.step; } else { itemData.step = 1; } itemData.disabled = disabledFunc ? disabledFunc(propName, itemData) : false; } data.add(new Property(propName, itemData)); } } } return data; } } return null; } private getUrl() { const url = window.location.href; const arr = url.split("/"); if (arr[arr.length - 1].indexOf(".html") > -1) { arr.pop(); } const ret = arr.join("/"); return ret; } _buildImageData(options: BuildImageOptions) { const ctor: Function = options.ctor; const value: Object = options.value; const data: ImageData = options.data; const path: Array = options.path; if (ctor && value instanceof ctor) { data.path = path; // 2.4.6 没有了这个属性 if (value.hasOwnProperty("_textureFilename")) { //@ts-ignore data.data = `${this.getUrl()}/${value._textureFilename}`; } else { data.data = null; } const desc = []; if (value["_rect"]) { const rect = value["_rect"]; desc.push(`frame[${rect.width}*${rect.height}]`); } if (value["_texture"]) { const texture = value["_texture"]; desc.push(`texture[${texture.width}*${texture.height}]`); } data.desc = desc.join(","); return data; } return null; } private warnSilent(v: boolean) { if (typeof cc !== undefined && typeof cc.warn === "function") { if (v) { cc.warn = () => {}; } else { cc.warn = console.warn.bind(console); } } } private isDisabledX(node: any, item: Info) { const widget = node.getComponent(cc.Widget); if (widget && widget.enabled) { if (widget.isAlignLeft) { item.tip = "widget.isAlignLeft"; return true; } if (widget.isAlignRight) { item.tip = "widget.isAlignRight"; return true; } } item.tip = ""; return false; } private isDisabledY(node: any, item: Info) { const widget = node.getComponent(cc.Widget); if (widget && widget.enabled) { if (widget.isAlignTop) { item.tip = "widget.isAlignTop"; return true; } if (widget.isAlignBottom) { item.tip = "widget.isAlignBottom"; return true; } } item.tip = ""; return false; } private getEnum(node: any, key: string): Array<{ name: string; value: number }> | null { const cfgArray = getEnumListConfig(); for (let i = 0; i < cfgArray.length; i++) { const { type, list } = cfgArray[i]; if (type && node instanceof type) { const ret = list.find((item) => item.key === key); if (ret) { return ret.values(); } } } return null; } private getDisabled(node: any, key: string, item: Info) { const cfgArray: Array<{ type: any; keys: Array<{ key: string[]; disabled: () => boolean }> }> = [ { type: cc.Node, keys: [ { key: ["position.x", "x"], disabled: () => { return this.isDisabledX(node, item); }, }, { key: ["position.y", "y"], disabled: () => { return this.isDisabledY(node, item); }, }, ], }, ]; for (let i = 0; i < cfgArray.length; i++) { const { type, keys } = cfgArray[i]; if (node instanceof type) { const ret = keys.find((item) => !!item.key.find((el) => el === key)); if (ret) { return ret.disabled(); } } } return false; } private getStep(node: any, key: string | number) { if (typeof key !== "string") { return 1; } const cfgArray: Array<{ type: any; keys: Array<{ key: string; step: number }> }> = []; if (cc.Node) { cfgArray.push({ type: cc.Node, keys: [ { key: "anchorX", step: 0.1 }, { key: "anchorY", step: 0.1 }, { key: "scaleX", step: 0.1 }, { key: "scaleY", step: 0.1 }, { key: "scale", step: 0.1 }, { key: "worldScale", step: 0.1 }, ], }); } const tr = cc.UITransformComponent || cc.UITransform; if (tr) { cfgArray.push({ type: tr, keys: [{ key: "anchorPoint", step: 0.1 }], }); } for (let i = 0; i < cfgArray.length; i++) { const { type, keys } = cfgArray[i]; if (node instanceof type) { const ret = keys.find((item) => item.key === key); if (ret) { return ret.step; } } } return 1; } _genInfoData(node: any, key: string | number, path: Array): Info | null { this.warnSilent(true); const propertyValue = node[key]; this.warnSilent(false); const make = (info: Info | null) => { info.readonly = this._isReadonly(node, key); info.path = path; return info; }; let invalidType = this._isInvalidValue(propertyValue); if (invalidType) { const info = new InvalidData(invalidType); return make(info); } switch (typeof propertyValue) { case "boolean": { const info = new BoolData(propertyValue); return make(info); } case "number": { const enumList = this.getEnum(node, key.toString()); if (enumList) { const info = new EnumData(propertyValue); info.values = enumList; return make(info); } else { const info = new NumberData(propertyValue); info.step = this.getStep(node, key); info.disabled = this.getDisabled(node, key.toString(), info); return make(info); } } case "string": { const info = new StringData(propertyValue); return make(info); } } if (propertyValue instanceof cc.Color) { let hex = propertyValue.toHEX(); const info = new ColorData(`#${hex}`); return make(info); } if (Array.isArray(propertyValue)) { let keys: number[] = []; for (let i = 0; i < propertyValue.length; i++) { keys.push(i); } const info = this._buildArrayData({ data: new ArrayData(), path: path, value: propertyValue, keys: keys, }); return make(info); } let info: Info = this._buildVecData({ ctor: cc.Size, path: path, data: new Vec2Data(), keys: [{ key: "width" }, { key: "height" }], value: propertyValue, }); if (info) { return make(info); } info = this._buildVecData({ // @ts-ignore ctor: cc.Vec3, path: path, data: new Vec3Data(), keys: [ { key: "x", disabled: (v: string, item: Info) => { return this.getDisabled(node, `${key}.${v}`, item); }, }, { key: "y", disabled: (v: string, item: Info) => { return this.getDisabled(node, `${key}.${v}`, item); }, }, { key: "z" }, ], step: this.getStep(node, key), value: propertyValue, }); if (info) { return make(info); } info = this._buildVecData({ // @ts-ignore ctor: cc.Quat, path: path, data: new Vec4Data(), keys: [{ key: "x" }, { key: "y" }, { key: "z" }, { key: "w" }], value: propertyValue, }); if (info) { return make(info); } info = this._buildVecData({ // @ts-ignore ctor: cc.Vec2, path: path, data: new Vec2Data(), step: this.getStep(node, key), keys: [ { key: "x", disabled: (v: string, item: Info) => { return this.getDisabled(node, `${key}.${v}`, item); }, }, { key: "y", disabled: (v: string, item: Info) => { return this.getDisabled(node, `${key}.${v}`, item); }, }, ], value: propertyValue, }); if (info) { return make(info); } info = this._buildImageData({ ctor: cc.SpriteFrame, data: new ImageData(), path: path, value: propertyValue, }); if (info) { return make(info); } let ctorName = propertyValue.__classname__; if (ctorName) { const engine = new EngineData(); engine.engineType = ctorName; engine.engineName = propertyValue.name; if (propertyValue instanceof cc.Node) { engine.engineNode = engine.engineNode = propertyValue.uuid; } else if (propertyValue instanceof cc.Asset) { engine.engineUUID = propertyValue._uuid; engine.engineNode = ""; } else if (propertyValue instanceof cc.Component) { engine.engineUUID = propertyValue.uuid; if (propertyValue.node) { engine.engineNode = propertyValue.node.uuid; } else { engine.engineNode = ""; } } return make(engine); } if (typeof propertyValue === "object") { // 空{} MaterialVariant 2.4.0 try { JSON.stringify(propertyValue); info = this._buildObjectData({ data: new ObjectData(), path: path, value: propertyValue, }); } catch { // Object存在循环引用的情况 info = new ObjectCircleData(); } if (info) { return make(info); } } return null; } _buildArrayData({ value, path, data, keys }: BuildArrayOptions) { keys = keys.filter((key) => !key.toString().startsWith("_")); for (let i = 0; i < keys.length; i++) { let key = keys[i]; let propPath = path.concat(key.toString()); let itemData = this._genInfoData(value, key, propPath); if (itemData) { data.add(new Property(key.toString(), itemData)); } } return data; } _buildObjectData({ value, path, data }: BuildObjectOptions) { let keys = Object.keys(value); keys = this.filterKeys(keys); for (let i = 0; i < keys.length; i++) { let key = keys[i]; let propPath = path.concat(key.toString()); let itemData = this._genInfoData(value, key, propPath); if (itemData) { data.add(new Property(key, itemData)); } } return data; } filterKeys(keys: string[]) { // 剔除_开头的属性 return keys.filter((key) => !key.toString().startsWith("_")); } _isInvalidValue(value: any) { // !!Infinity=true if ((value && value !== Infinity) || value === 0 || value === false || value === "") { return false; } if (value === null) { return "null"; } else if (value === Infinity) { return "Infinity"; } else if (value === undefined) { return "undefined"; } else if (Number.isNaN(value)) { return "NaN"; } else { return false; } } private getCompName(comp: any): string { const nameKeys = [ "__classname__", // 2.4.0 验证通过 ]; for (let i = 0; i < nameKeys.length; i++) { let key = nameKeys[i]; // 一般来说,这里的name是不会出现假值 if (comp[key]) { return comp[key]; } } return comp.constructor.name; } // 校验keys的有效性,3.x有position,没有x,y,z _checkKeysValid(obj: any, keys: string[]) { for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (!isHasProperty(obj, key)) { return false; } } return true; } _getGroupData(node: any) { const name = this.getCompName(node); let nodeGroup = new Group(name, node.uuid); let keys = this._getNodeKeys(node); for (let i = 0; i < keys.length; ) { let key = keys[i]; let pair = this._getPairProperty(key); if (pair && this._checkKeysValid(node, pair.values)) { let bSplice = false; // 把这个成对的属性剔除掉 pair.values.forEach((item: string) => { let index = keys.findIndex((el) => el === item); if (index !== -1) { keys.splice(index, 1); if (pair && item === pair.key) { // 切掉了自己,才能步进+1 bSplice = true; } } }); // 序列化成对的属性 let info: Vec2Data | Vec3Data | Vec4Data | null = null; let pairValues = pair.values; if (pairValues.length === 2) { info = new Vec2Data(); } else if (pairValues.length === 3) { info = new Vec3Data(); } else if (pairValues.length === 4) { info = new Vec4Data(); } // todo path pairValues.forEach((el: string) => { let propertyPath = [node.uuid, el]; let vecData = this._genInfoData(node, el, propertyPath); if (vecData) { info && info.add(new Property(el, vecData)); } }); if (info) { let property = new Property(pair.key, info); nodeGroup.addProperty(property); } if (!bSplice) { i++; } } else { let propertyPath = [node.uuid, key]; let info = this._genInfoData(node, key, propertyPath); if (info) { nodeGroup.addProperty(new Property(key, info)); } i++; } } nodeGroup.sort(); return nodeGroup; } getNodeInfo(uuid: string) { let node = this.inspectorGameMemoryStorage[uuid]; if (node) { let groupData = []; if (node.isValid) { // 收集节点信息 let nodeGroup = this._getGroupData(node); groupData.push(nodeGroup); // 收集组件信息 const nodeComp = node._components; if (nodeComp) { for (let i = 0; i < nodeComp.length; i++) { let itemComp = nodeComp[i]; this.inspectorGameMemoryStorage[itemComp.uuid] = itemComp; let compGroup = this._getGroupData(itemComp); groupData.push(compGroup); } } } const data: NodeInfoData = new NodeInfoData(uuid, groupData); this.sendMsgToContent(Msg.ResponseNodeInfo, data as ResponseNodeInfoData); } else { const data: NodeInfoData = new NodeInfoData(uuid, []); this.sendMsgToContent(Msg.ResponseNodeInfo, data as ResponseNodeInfoData); } } logValue(uuid: string, key: string) { let nodeOrComp = this.inspectorGameMemoryStorage[uuid]; if (nodeOrComp) { console.log(nodeOrComp[key]); } } _isReadonly(base: Object, key: string | number): boolean { let ret = Object.getOwnPropertyDescriptor(base, key); if (ret) { return !(ret.set || ret.writable); } else { let proto = Object.getPrototypeOf(base); if (proto) { return this._isReadonly(proto, key); } else { return false; } } } setValue(pathArray: Array, value: string): boolean { let target = this.inspectorGameMemoryStorage; // 尝试设置creator3.x的数据 if (trySetValueWithConfig(pathArray, target, value)) { return true; } for (let i = 0; i < pathArray.length; i++) { let path = pathArray[i]; if (i === pathArray.length - 1) { // 到最后的key了 if (this._isReadonly(target, path)) { console.warn(`值不允许修改`); } else { target[path] = value; return true; } } else { // creator3.x的enumerable导致无法判断 if (target.hasOwnProperty(path) || target[path]) { target = target[path]; } else { return false; } } } return false; } }