1051 lines
31 KiB
TypeScript
Raw Normal View History

2024-12-27 14:14:38 +08:00
// eval 注入脚本的代码,变量尽量使用var,后来发现在import之后,let会自动变为var
import { uniq } from "lodash";
2025-01-14 17:44:26 +08:00
import { Msg, PluginEvent, RequestLogData, RequestNodeInfoData, RequestSetPropertyData, ResponseGameInfoData, ResponseNodeInfoData, ResponseSetPropertyData, ResponseSupportData, ResponseTreeInfoData } from "../../core/types";
2025-02-02 14:09:12 +08:00
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";
2025-01-29 20:53:10 +08:00
import { getEnumListConfig } from "./enumConfig";
2024-12-27 14:14:38 +08:00
import { InjectEvent } from "./event";
import { Hint } from "./hint";
import { injectView } from "./inject-view";
import { inspectTarget } from "./inspect-list";
2024-12-27 14:14:38 +08:00
import { getValue, trySetValueWithConfig } from "./setValue";
import { BuildArrayOptions, BuildImageOptions, BuildObjectOptions, BuildVecOptions } from "./types";
import { isHasProperty } from "./util";
2024-12-27 14:14:38 +08:00
declare const cc: any;
export class Inspector extends InjectEvent {
inspectorGameMemoryStorage: Record<string, any> = {};
private hint: Hint = new Hint(this);
2024-12-27 14:14:38 +08:00
2025-01-14 17:44:26 +08:00
private getAtlasViewFunction() {
// 之前只有v2版本支持
return cc?.dynamicAtlasManager?.showDebug;
}
2024-12-27 14:14:38 +08:00
onMessage(pluginEvent: PluginEvent): void {
switch (pluginEvent.msg) {
case Msg.RequestSupport: {
const isCocosGame = this._isCocosGame();
2024-12-27 14:14:38 +08:00
this.notifySupportGame(isCocosGame);
break;
}
case Msg.RequstTreeInfo: {
2024-12-27 14:14:38 +08:00
this.updateTreeInfo();
break;
}
2025-01-14 18:33:28 +08:00
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;
}
2025-01-14 17:44:26 +08:00
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);
2024-12-27 14:14:38 +08:00
break;
}
case Msg.RequestSetProperty: {
const data: RequestSetPropertyData = pluginEvent.data;
2024-12-27 14:14:38 +08:00
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);
2024-12-27 14:14:38 +08:00
} else {
console.warn(`设置失败:${data.path}`);
}
break;
}
case Msg.RequestLogData: {
const data: RequestLogData = pluginEvent.data;
2024-12-27 14:14:38 +08:00
const value = getValue(this.inspectorGameMemoryStorage, data);
// 直接写console.log会被tree shaking
const logFunction = console.log;
logFunction(value);
break;
}
2025-01-25 19:36:11 +08:00
case Msg.RequestLogCustom: {
const logFunction = console.log;
logFunction(pluginEvent.data);
2025-01-27 09:08:30 +08:00
break;
2025-01-25 19:36:11 +08:00
}
case Msg.ReqWriteClipboard: {
navigator.clipboard
.writeText(pluginEvent.data)
.then(() => {
console.log("ok");
})
.catch((e) => {
console.log("fail", e);
});
break;
}
2025-01-06 19:46:38 +08:00
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];
2025-01-24 16:05:05 +08:00
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];
2025-01-24 16:05:05 +08:00
if (node && node.isValid) {
this.hint.setSelected(node);
} else {
this.hint.cleanSelected();
}
} else {
this.hint.cleanSelected();
}
break;
}
2025-01-06 19:46:38 +08:00
case Msg.RequestDestroy: {
const uuid: string = pluginEvent.data;
const node = this.inspectorGameMemoryStorage[uuid];
if (node && node.isValid && node.destroy) {
node.destroy();
}
break;
}
2024-12-27 14:14:38 +08:00
}
}
init() {
console.log(...this.terminal.init());
2025-01-12 17:57:02 +08:00
this.watchIsCocosGame();
}
2025-01-12 17:57:02 +08:00
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();
});
2025-01-12 17:57:02 +08:00
const version = this.getEngineVersion();
2025-01-24 16:05:05 +08:00
this.hint.setEngineVersion(version);
2025-01-12 17:57:02 +08:00
if (b && version) {
this.uploadEngineVersion(version);
}
}
}, 300);
}
private getEngineVersion(): string {
2025-01-12 17:57:02 +08:00
if (this._isCocosGame()) {
return cc.ENGINE_VERSION || "";
} else {
return "";
}
2024-12-27 14:14:38 +08:00
}
2025-01-14 17:44:26 +08:00
public isCreatorV2() {
return this.getEngineVersion().startsWith("2.");
}
public isCreatorV3() {
return this.getEngineVersion().startsWith("3.");
}
2024-12-27 14:14:38 +08:00
notifySupportGame(b: boolean) {
2025-01-12 17:57:02 +08:00
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);
}
2024-12-27 14:14:38 +08:00
}
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) {
2024-12-27 14:14:38 +08:00
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);
2024-12-27 14:14:38 +08:00
} else {
let treeData = new TreeData();
treeData.id = "";
treeData.text = "empty scene";
2025-02-02 14:09:12 +08:00
treeData.icon = "icon_cocos";
notify && this.sendMsgToContent(Msg.ResponseTreeInfo, treeData as ResponseTreeInfoData);
2024-12-27 14:14:38 +08:00
}
} else {
notify && this.notifySupportGame(false);
2024-12-27 14:14:38 +08:00
}
}
2025-02-02 14:09:12 +08:00
private calcIcon(node: any): string {
if (node instanceof cc.Scene) {
return getNodeIcon(CompType.Scene);
}
2024-12-27 14:14:38 +08:00
2025-02-02 14:09:12 +08:00
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) {
2025-02-02 14:37:15 +08:00
if (node._prefab) {
return "#00ff00ff";
} else {
return "";
}
2025-02-02 14:09:12 +08:00
}
2024-12-27 14:14:38 +08:00
// 收集节点信息
getNodeChildren(node: any, data: TreeData) {
data.id = node.uuid;
data.text = node.name;
2025-02-02 14:09:12 +08:00
data.icon = this.calcIcon(node);
data.color = this.calcColor(node);
2024-12-27 14:14:38 +08:00
// @ts-ignore
if (node instanceof cc.Scene) {
// 场景不允许获取active引擎会报错
} else {
2025-01-29 21:19:38 +08:00
data.active = !!node.active && !!node.activeInHierarchy;
2024-12-27 14:14:38 +08:00
}
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 {
2025-01-27 09:08:30 +08:00
if (type && node instanceof type) {
if (keys.find((v) => v === key)) {
return true;
}
}
}
}
return false;
}
2024-12-27 14:14:38 +08:00
_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);
2024-12-27 14:14:38 +08:00
});
allKeys = allKeys.filter((key) => {
try {
2025-01-19 18:40:25 +08:00
this.warnSilent(true);
const type = typeof node[key];
this.warnSilent(false);
return type !== "function";
2024-12-27 14:14:38 +08:00
} catch (e) {
// console.warn(`属性${key}出现异常:\n`, e);
return false;
}
});
return allKeys;
}
_getPairProperty(key: string): null | { key: string; values: string[] } {
let pairProperty: Record<string, any> = {
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;
2025-01-29 13:33:23 +08:00
const keys = options.keys;
2024-12-27 14:14:38 +08:00
const value: Object = options.value;
const data: Vec3Data | Vec2Data = options.data;
const path: Array<string> = options.path;
if (ctor && value instanceof ctor) {
2025-01-29 13:33:23 +08:00
let hasUnOwnProperty = keys.find((item) => !value.hasOwnProperty(item.key));
2024-12-27 14:14:38 +08:00
if (!hasUnOwnProperty) {
for (let key in keys) {
2025-01-29 13:33:23 +08:00
const propName = keys[key].key;
const stepFunc = keys[key].step;
const disabledFunc = keys[key].disabled;
2024-12-27 14:14:38 +08:00
if (value.hasOwnProperty(propName)) {
let propPath = path.concat(propName);
let itemData = this._genInfoData(value, propName, propPath);
if (itemData) {
2025-01-29 13:33:23 +08:00
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;
2025-01-28 13:14:53 +08:00
}
2024-12-27 14:14:38 +08:00
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;
}
2024-12-27 14:14:38 +08:00
_buildImageData(options: BuildImageOptions) {
const ctor: Function = options.ctor;
const value: Object = options.value;
const data: ImageData = options.data;
const path: Array<string> = 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}`;
2024-12-27 14:14:38 +08:00
} else {
data.data = null;
}
2025-01-08 11:54:37 +08:00
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(",");
2024-12-27 14:14:38 +08:00
return data;
}
return null;
}
2025-01-19 18:40:25 +08:00
private warnSilent(v: boolean) {
if (typeof cc !== undefined && typeof cc.warn === "function") {
if (v) {
cc.warn = () => {};
} else {
cc.warn = console.warn.bind(console);
}
}
}
2025-01-29 13:33:23 +08:00
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 {
2025-01-29 20:53:10 +08:00
const cfgArray = getEnumListConfig();
for (let i = 0; i < cfgArray.length; i++) {
const { type, list } = cfgArray[i];
2025-01-29 20:50:18 +08:00
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 }> }> = [
2025-01-29 13:33:23 +08:00
{
type: cc.Node,
keys: [
{
key: ["position.x", "x"],
2025-01-29 13:33:23 +08:00
disabled: () => {
return this.isDisabledX(node, item);
},
},
{
key: ["position.y", "y"],
2025-01-29 13:33:23 +08:00
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));
2025-01-29 13:33:23 +08:00
if (ret) {
return ret.disabled();
}
}
}
return false;
}
2025-01-28 13:14:53 +08:00
private getStep(node: any, key: string | number) {
if (typeof key !== "string") {
return 1;
}
2025-01-28 12:26:49 +08:00
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 },
2025-01-28 13:14:53 +08:00
{ key: "scale", step: 0.1 },
{ key: "worldScale", step: 0.1 },
2025-01-28 12:26:49 +08:00
],
});
}
2025-01-28 13:14:53 +08:00
const tr = cc.UITransformComponent || cc.UITransform;
if (tr) {
cfgArray.push({
type: tr,
keys: [{ key: "anchorPoint", step: 0.1 }],
});
}
2025-01-28 12:26:49 +08:00
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<string>): Info | null {
2025-01-19 18:40:25 +08:00
this.warnSilent(true);
const propertyValue = node[key];
2025-01-19 18:40:25 +08:00
this.warnSilent(false);
const make = (info: Info | null) => {
info.readonly = this._isReadonly(node, key);
info.path = path;
return info;
};
2024-12-27 14:14:38 +08:00
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);
2024-12-27 14:14:38 +08:00
}
}
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(),
2025-01-29 13:33:23 +08:00
keys: [{ key: "width" }, { key: "height" }],
value: propertyValue,
});
2024-12-27 14:14:38 +08:00
if (info) {
return make(info);
2024-12-27 14:14:38 +08:00
}
info = this._buildVecData({
// @ts-ignore
ctor: cc.Vec3,
path: path,
data: new Vec3Data(),
2025-01-29 13:33:23 +08:00
keys: [
{
key: "x",
disabled: (v: string, item: Info) => {
return this.getDisabled(node, `${key}.${v}`, item);
2025-01-29 13:33:23 +08:00
},
},
{
key: "y",
disabled: (v: string, item: Info) => {
return this.getDisabled(node, `${key}.${v}`, item);
2025-01-29 13:33:23 +08:00
},
},
{ key: "z" },
],
2025-01-28 13:14:53 +08:00
step: this.getStep(node, key),
value: propertyValue,
});
if (info) {
return make(info);
}
2025-01-08 11:54:37 +08:00
info = this._buildVecData({
// @ts-ignore
ctor: cc.Quat,
path: path,
data: new Vec4Data(),
2025-01-29 13:33:23 +08:00
keys: [{ key: "x" }, { key: "y" }, { key: "z" }, { key: "w" }],
2025-01-08 11:54:37 +08:00
value: propertyValue,
});
if (info) {
return make(info);
}
info = this._buildVecData({
// @ts-ignore
ctor: cc.Vec2,
path: path,
data: new Vec2Data(),
2025-01-28 13:14:53 +08:00
step: this.getStep(node, key),
2025-01-29 13:33:23 +08:00
keys: [
{
key: "x",
disabled: (v: string, item: Info) => {
return this.getDisabled(node, `${key}.${v}`, item);
2025-01-29 13:33:23 +08:00
},
},
{
key: "y",
disabled: (v: string, item: Info) => {
return this.getDisabled(node, `${key}.${v}`, item);
2025-01-29 13:33:23 +08:00
},
},
],
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
2025-01-07 18:28:15 +08:00
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;
2024-12-27 14:14:38 +08:00
}
_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) {
2024-12-27 14:14:38 +08:00
let keys = Object.keys(value);
keys = this.filterKeys(keys);
2024-12-27 14:14:38 +08:00
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);
2024-12-27 14:14:38 +08:00
if (itemData) {
data.add(new Property(key, itemData));
2024-12-27 14:14:38 +08:00
}
}
return data;
2024-12-27 14:14:38 +08:00
}
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;
2024-12-27 14:14:38 +08:00
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();
2024-12-27 14:14:38 +08:00
}
// 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);
}
}
2024-12-27 14:14:38 +08:00
}
const data: NodeInfoData = new NodeInfoData(uuid, groupData);
this.sendMsgToContent(Msg.ResponseNodeInfo, data as ResponseNodeInfoData);
2024-12-27 14:14:38 +08:00
} else {
2025-01-12 17:57:02 +08:00
const data: NodeInfoData = new NodeInfoData(uuid, []);
this.sendMsgToContent(Msg.ResponseNodeInfo, data as ResponseNodeInfoData);
2024-12-27 14:14:38 +08:00
}
}
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<string>, 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;
}
}