您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
这是一个展示cocos节点树的工具
// ==UserScript== // @name cocos Viewer // @namespace http://tampermonkey.net/ // @version 2024-02-18 // @description 这是一个展示cocos节点树的工具 // @author 放学我走了 // @include *://172.16.16.125/* // @icon https://www.google.com/s2/favicons?sz=64&domain=16.124 // @require http://172.16.16.125/ccc-devtools/libs/js/vue.min.js // @require http://172.16.16.125/ccc-devtools/libs/js/vuetify4.js // @resource customCSS1 http://172.16.16.125/ccc-devtools/libs/css/materialdesignicons.min4.css // @resource customCSS2 http://172.16.16.125/ccc-devtools/libs/css/vuetify.min.css // @resource customCSS3 http://172.16.16.125/ccc-devtools/libs/css/style4.css // @grant GM_addStyle // @grant GM_getResourceText // @license MIT // ==/UserScript== (function() { 'use strict'; // Your code here... const initConsoleUtil = function () { try { if (cc) { }else { return; } } catch (error) { return; } if (cc.tree) return; cc.tree = function (key) { let index = key || 0; let treeNode = function (node) { let nameStyle = `color: ${node.parent === null || node.activeInHierarchy ? 'green' : 'grey'}; font-size: 14px;font-weight:bold`; let propStyle = `color: black; background: lightgrey;margin-left: 5px;border-radius:3px;padding: 0 3px;font-size: 10px;font-weight:bold`; let indexStyle = `color: orange; background: black;margin-left: 5px;border-radius:3px;padding:0 3px;fonrt-size: 10px;font-weight:bold;` let nameValue = `%c${node.name}`; let propValue = `%c${node.x.toFixed(0) + ',' + node.y.toFixed(0) + ',' + node.width.toFixed(0) + ',' + node.height.toFixed(0) + ',' + node.scale.toFixed(1)}` let indexValue = `%c${index++}`; if (node.childrenCount > 0) { console.groupCollapsed(nameValue + propValue + indexValue, nameStyle, propStyle, indexStyle); for (let i = 0; i < node.childrenCount; i++) { treeNode(node.children[i]); } console.groupEnd(); } else { console.log(nameValue + propValue + indexValue, nameStyle, propStyle, indexStyle); } } if (key) { let node = cc.cat(key); index = node['tempIndex']; treeNode(node); } else { let scene = cc.director.getScene(); treeNode(scene); } return '属性依次为x,y,width,height,scale.使用cc.cat(id)查看详细属性.'; } cc.cat = function (key) { let index = 0; let target; let sortId = function (node) { if (target) return; if (cc.js.isNumber(key)) { if (key === index++) { target = node; return; } } else { if (key.toLowerCase() === node.name.toLowerCase()) { target = node; return; } else { index++; } } if (node.childrenCount > 0) { for (let i = 0; i < node.childrenCount; i++) { sortId(node.children[i]); } } } let scene = cc.director.getScene(); sortId(scene); target['tempIndex'] = cc.js.isNumber(key) ? key : index; return target; } cc.list = function (key) { let targets = []; let step = function (node) { if (node.name.toLowerCase().indexOf(key.toLowerCase()) > -1) { targets.push(node); } if (node.childrenCount > 0) { for (let i = 0; i < node.childrenCount; i++) { step(node.children[i]); } } } let scene = cc.director.getScene(); step(scene); if (targets.length === 1) { return targets[0]; } else { return targets; } } cc.where = function (key) { let target = key.name ? key : cc.cat(key); if (!target) { return null; } let rect = target.getBoundingBoxToWorld(); let bgNode = new cc.Node(); let graphics = bgNode.addComponent(cc.Graphics); let scene = cc.director.getScene(); scene.addChild(bgNode); bgNode.position = rect.center; bgNode.group = target.group; bgNode.zIndex = cc.macro.MAX_ZINDEX; let isZeroSize = rect.width === 0 || rect.height === 0; if (isZeroSize) { graphics.circle(0, 0, 100); graphics.fillColor = cc.Color.GREEN; graphics.fill(); } else { bgNode.width = rect.width; bgNode.height = rect.height; graphics.rect(-bgNode.width / 2, -bgNode.height / 2, bgNode.width, bgNode.height); graphics.fillColor = new cc.Color().fromHEX('#E91E6390'); graphics.fill(); } setTimeout(() => { if (cc.isValid(bgNode)) { bgNode.destroy(); } }, 2000); return target; } cc.cache = function () { if(cc.ENGINE_VERSION.indexOf("2.4")==-1){ return cc.cache2(); } let rawCacheData = cc.assetManager.assets._map; let cacheData = []; let totalTextureSize = 0; for (let k in rawCacheData) { let item = rawCacheData[k]; if (item.type !== 'js' && item.type !== 'json') { let itemName = '_'; let preview = ''; let content = item.__classname__; let formatSize = -1; if (item.type === 'png' || item.type === 'jpg') { let texture = rawCacheData[k.replace('.' + item.type, '.json')]; if (texture && texture._owner && texture._owner._name) { itemName = texture._owner._name; preview = texture.content.url; } } else { if (item.name) { itemName = item.name; } else if (item._owner) { itemName = (item._owner && item._owner.name) || '_'; } if (content === 'cc.Texture2D') { preview = item.nativeUrl; let textureSize = item.width * item.height * ((item._native === '.jpg' ? 3 : 4) / 1024 / 1024); totalTextureSize += textureSize; // sizeStr = textureSize.toFixed(3) + 'M'; formatSize = Math.round(textureSize * 1000) / 1000; } else if (content === 'cc.SpriteFrame') { preview = item._texture.nativeUrl; } } cacheData.push({ queueId: item.queueId, type: content, name: itemName, preview: preview, id: item._uuid, size: formatSize }); } } let cacheTitle = `缓存 [文件总数:${cacheData.length}][纹理缓存:${totalTextureSize.toFixed(2) + 'M'}]`; return [cacheData, cacheTitle]; } cc.cache2=function(){ let rawCacheData = cc.loader._cache; let cacheData = []; let totalTextureSize = 0; for (let k in rawCacheData) { let item = rawCacheData[k]; let type=item.type; if (item.type !== 'js' && item.type !== 'json') { let itemName = '_'; let preview = ''; let content = (item.content && item.content.__classname__) ? item.content.__classname__ : item.type; let formatSize = -1; if (item.type === 'png' || item.type === 'jpg') { let texture = rawCacheData[k.replace('.' + item.type, '.json')]; if (texture && texture._owner && texture._owner._name) { itemName = texture._owner._name; preview = texture.content.url; } } else { if (item.content.name && item.content.name.length > 0) { itemName = item.content.name; } else if (item._owner) { itemName = (item._owner && item._owner.name) || '_'; } if (content === 'cc.Texture2D') { type=content; let texture = item.content; preview = texture.url; let textureSize = texture.width * texture.height * ((texture._native === '.jpg' ? 3 : 4) / 1024 / 1024); totalTextureSize += textureSize; // sizeStr = textureSize.toFixed(3) + 'M'; formatSize = Math.round(textureSize * 1000) / 1000; } else if (content === 'cc.SpriteFrame') { preview = item.content._texture.url; } } cacheData.push({ queueId: item.queueId, type: type, name: itemName, preview: preview, id: item.id, content: content, size: formatSize }); } } let cacheTitle = `缓存 [文件总数:${cacheData.length}][纹理缓存:${totalTextureSize.toFixed(2) + 'M'}]`; return [cacheData, cacheTitle]; } } const NEX_CONFIG = { nodeSchema: { node2d: { title: 'Node', key: 'cc.Node', rows: [ { name: 'Name', key: 'name', type: 'text' }, { name: 'X', key: 'x', type: 'number' }, { name: 'Y', key: 'y', type: 'number' }, { name: 'Width', key: 'width', type: 'number' }, { name: 'Height', key: 'height', type: 'number' }, { name: 'Angle', key: 'angle', type: 'number' }, { name: 'ScaleX', key: 'scaleX', type: 'number' }, { name: 'ScaleY', key: 'scaleY', type: 'number' }, { name: 'Opacity', key: 'opacity', type: 'number' }, { name: 'Color', key: 'hex_color', type: 'color' }, { name: 'Group', key: 'group', type: 'text' }, ] }, node3d: { title: 'Node', key: 'cc.Node', rows: [ // TODO: ] }, }, componentsSchema: { 'cc.Camera': { title: 'cc.Camera', key: 'cc.Camera', rows: [ { name: 'Zoom Ratio', key: 'zoomRatio', type: 'number' }, { name: 'Depth', key: 'depth', type: 'number' }, { name: 'Bacground Color', key: 'hex_backgroundColor', rawKey: 'backgroundColor', type: 'color' }, { name: 'Align with Screen', key: 'alignWithScreen', type: 'bool' }, ] }, 'cc.Sprite': { key: 'cc.Sprite', title: 'cc.Sprite', rows: [] }, 'cc.Label': { title: 'cc.Label', key: 'cc.Label', rows: [ { name: 'String', key: 'string', type: 'textarea' }, { name: 'Font Size', key: 'fontSize', type: 'number' }, { name: 'Line Height', key: 'lineHeight', type: 'number' }, ] } } } function isCocos() { return new Promise((re, rj) => { let inter = setInterval(() => { try { if (cc) { window.clearInterval(inter); re(cc) } } catch (error) { } }, 100) }); } function createApp(){ // 创建一个div元素 // 创建一个新的 div 元素 var newDivElement = document.createElement('div'); // 设置新 div 的一些属性和样式 newDivElement.textContent = '这是新的 div'; //newDivElement.style.zIndex="8888"; newDivElement.style.width = '100%'; newDivElement.style.height = '100%'; //newDivElement.style.display = 'flex'; //newDivElement.style.flexDirection = 'column'; //newDivElement.style.justifyContent = 'center'; // 获取父元素的引用 var parentElement = document.getElementById('GameCanvas'); // // 在父元素的前面插入新的 div 元素 parentElement.parentNode.insertBefore(newDivElement, parentElement); newDivElement.innerHTML=getHtml(); let inter = setInterval(() => { try { var elementToMove = document.getElementById('Cocos2dGameContainer'); var targetElement = document.getElementById('GameDiv'); // 移动元素到目标元素的内部 if (elementToMove&&targetElement) { window.clearInterval(inter); targetElement.appendChild(elementToMove); setInterval(()=>{shipei(); },1000) } } catch (error) { } }, 100) } function shipei(){ cc.director.getScene().getChildByName("Canvas").scale=0.45; return; var elementToMove = document.getElementById('Cocos2dGameContainer'); var myDiv = document.getElementById('GameDiv'); // 替换为实际的 <div> 的 id var rect = myDiv.getBoundingClientRect(); var w=rect.width; var h=rect.height; let ws= w/1136; let hs= h/640; if(ws<hs){ h=640*ws; //h=h*ws; }else { w=1136*hs; //w=w*hs; } myDiv.style.width = w+'px%'; myDiv.style.height = h+'px%'; console.log(`==============${w}===${h}====`); var resizeEvent = new UIEvent('resize', { bubbles: true, cancelable: true }); window.dispatchEvent(resizeEvent); } async function appMain(){ const css1 = GM_getResourceText("customCSS1"); GM_addStyle(css1); const css2 = GM_getResourceText("customCSS2"); GM_addStyle(css2); const css3 = GM_getResourceText("customCSS3"); GM_addStyle(css3); await isCocos(); console.log("=========createTreeButton=========") createApp(); initVue(); initConsoleUtil(); } function initVue(){ const app = new Vue({ el: '#app', vuetify: new Vuetify({ theme: { dark: true } }), data: { isShowTop: true, drawer: false, cacheDialog: false, cacheTitle: '', cacheHeaders: [ { text: 'Type', value: 'type' }, { text: 'Name', value: 'name' }, { text: 'Preivew', value: 'preview' }, { text: 'ID', value: 'id' }, { text: 'Size', value: 'size' }, ], cacheRawData: [], cacheData: [], cacheSearchText: null, cacheOnlyTexture: true, treeData: [], selectedNodes: [], intervalId: -1, treeSearchText: null, nodeSchema: {}, componentsSchema: [], }, created() { if (this.isShowTop) { this.startUpdateTree(); } this.waitCCInit().then(() => { initConsoleUtil(); }); }, watch: { cacheOnlyTexture() { this.updateCacheData(); } }, computed: { treeFilter() { return (item, search, textKey) => item[textKey].indexOf(search) > -1; }, selectedNode() { if (!this.selectedNodes.length) return undefined let node = getNodeById(this.selectedNodes[0]); if (node) { if (!node.hex_color) { cc.js.getset(node, 'hex_color', () => { return '#' + node.color.toHEX('#rrggbb'); }, (hex) => { node.color = new cc.Color().fromHEX(hex); }, false, true); } let superPreLoad = node._onPreDestroy; node._onPreDestroy = () => { superPreLoad.apply(node); if (this.selectedNodes.length > 0 && this.selectedNodes[0] === node._id) { this.selectedNodes.pop(); } } this.nodeSchema = NEX_CONFIG.nodeSchema.node2d; let componentsSchema = []; for (let component of node._components) { let schema = NEX_CONFIG.componentsSchema[component.__classname__]; if (schema) { node[schema.key] = node.getComponent(schema.key); for (let i = 0; i < schema.rows.length; i++) { if (schema.rows[i].type === 'color') { if (!node[schema.key][schema.rows[i].key]) { cc.js.getset(node[schema.key], schema.rows[i].key, () => { return '#' + node.getComponent(schema.key)[schema.rows[i].rawKey].toHEX('#rrggbb'); }, (hex) => { node.getComponent(schema.key)[schema.rows[i].rawKey] = new cc.Color().fromHEX(hex); }, false, true); } } } } else { schema = { title: component.__classname__, key: component.__classname__ }; node[schema.key] = node.getComponent(schema.key); } componentsSchema.push(schema); } this.componentsSchema = componentsSchema; } return node; }, }, methods: { waitCCInit() { return new Promise((resolve, reject) => { resolve(); }); }, refreshTree: function () { if (!this.$data.drawer || !cc.director.getScene() || !cc.director.getScene().children) return; this.$data.treeData = getChildren(cc.director.getScene()); }, startUpdateTree: function () { this.$data.intervalId = setInterval(() => { this.refreshTree(); }, 200); }, stopUpdateTree: function () { clearInterval(this.$data.intervalId); }, outputNodeHandler(id) { let i = 1; while (window['temp' + i] !== undefined) { i++; } window['temp' + i] = this.selectedNode; console.log('temp' + i); console.log(window['temp' + i]); }, outputComponentHandler(component) { let i = 1; while (window['temp' + i] !== undefined) { i++; } window['temp' + i] = this.selectedNode.getComponent(component); console.log('temp' + i); console.log(window['temp' + i]); }, drawNodeRect() { cc.where(this.selectedNode); }, updateCacheData() { if (this.$data.cacheOnlyTexture) { this.$data.cacheData = this.$data.cacheRawData.filter(item => item.type === 'cc.Texture2D'); } else { this.$data.cacheData = this.$data.cacheRawData; } }, openCacheDialog() { [this.$data.cacheRawData, this.$data.cacheTitle] = cc.cache(); this.updateCacheData(); this.$data.cacheDialog = true; }, openGithub() { window.open('https://github.com/potato47/ccc-devtools'); }, openCocosForum() { window.open('https://forum.cocos.com/'); }, openCocosDocs() { window.open('https://docs.cocos.com/'); } } }); return app; } function getChildren(node) { return node.children.map(child => { let children = (child.children && child.children.length > 0) ? getChildren(child) : []; return { id: child._id, name: child.name, active: child.activeInHierarchy, children }; }); } function getNodeById(id) { let target; const search = function (node) { if (node._id === id) { target = node; return; } if (node.childrenCount) { for (let i = 0; i < node.childrenCount; i++) { if (!target) { search(node.children[i]); } } } } const scene = cc.director.getScene(); search(scene); return target; } function getHtml(){ let y=window.location.href.substring(window.location.protocol.length+2,35) y=y.substring(0,y.lastIndexOf("/")); let html=` <v-app id="app"> <v-app-bar app clipped-left color="gray" dense v-if="isShowTop"> <v-app-bar-nav-icon @click.stop="drawer = !drawer"></v-app-bar-nav-icon> <div id="recompiling"><span>Recompiling...</span></div> <v-spacer></v-spacer> <div class="toolbar"> <div class="item"> <v-btn id="btn-show-fps" small height="25"><span style="color: #aaa;">Show FPS</span></v-btn> </div> <div class="item"> <v-btn id="btn-show-fps" small height="25" @click="openCacheDialog" ><span style="color: #aaa;">显示缓存资源</span></v-btn> </div> </div> </v-app-bar> <v-navigation-drawer v-model="drawer" app clipped fixed width="512" v-if="isShowTop"> <v-container style="height: 50%;overflow: auto;"> <v-text-field v-model="treeSearchText" dense label="Search Node or Component" dark flat solo-inverted hide-details clearable clear-icon="mdi-close-circle-outline"></v-text-field> <v-treeview :items="treeData" item-key="id" dense activatable :search="treeSearchText" :active.sync="selectedNodes"> <template v-slot:label="{ item, active }"> <label v-if="item.active" style="color: white;">{{ item.name }}</label> <label v-else style="color: gray;">{{ item.name }}</label> </template> </v-treeview> </v-container> <v-container style="border-top: 2px solid darkgray;height: 50%;overflow-y: auto;"> <template v-if="selectedNode"> <!-- Node --> <table style="width: 100%;color: white;" border="1"> <thead> <tr> <th colspan="2" style="text-align: left; padding: 10px;"> <div class="float-left" style="display:inline-flex;"> <v-simple-checkbox v-model="selectedNode.active"></v-simple-checkbox> <span style="margin-left: 10px;">{{ nodeSchema.title }}</span> </div> <div class="float-right"> <v-icon style="margin-left: 10px;margin-right: 10px;" @click="drawNodeRect()"> mdi-adjust</v-icon> <v-icon @click="outputNodeHandler()">mdi-send</v-icon> </div> </th> </tr> </thead> <tbody> <tr v-for="row in nodeSchema.rows" :key="row.key"> <td style="padding: 10px;width: 40%;">{{ row.name }}</td> <td style="width: 60%;"> <v-color-picker v-if="row.type == 'color'" class="ma-2" canvas-height="80" width="259" v-model="selectedNode[row.key]"></v-color-picker> <v-simple-checkbox v-else-if="row.type == 'bool'" v-model="selectedNode[row.key]" style="padding: 10px;width: 100%;"></v-simple-checkbox> <input v-else :type="row.type" v-model="selectedNode[row.key]" style="padding: 10px;width: 100%;"></input> </td> </tr> </tbody> </table> <!-- Components --> <table v-for="component in componentsSchema" style="width: 100%;color: white;" border="1"> <thead> <tr> <th colspan="2" style="text-align: left; padding: 10px;"> <div class="float-left" style="display:inline-flex;"> <v-simple-checkbox v-model="selectedNode[component.key].enabled"> </v-simple-checkbox> <span style="margin-left: 10px;">{{ component.title }}</span> </div> <div class="float-right"> <v-icon @click="outputComponentHandler(component.key)">mdi-send</v-icon> </div> </th> </tr> </thead> <tbody> <tr v-for="row in component.rows" :key="row.key"> <td style="padding: 10px;width: 40%;">{{ row.name }}</td> <td style="width: 60%;"> <v-color-picker v-if="row.type == 'color'" class="ma-2" canvas-height="80" width="259" v-model="selectedNode[component.key][row.key]"></v-color-picker> <textarea v-else-if="row.type == 'textarea'" rows="1" v-model="selectedNode[component.key][row.key]" style="padding: 10px;width: 100%;"> </textarea> <v-simple-checkbox v-else-if="row.type == 'bool'" v-model="selectedNode[component.key][row.key]" style="padding: 10px;width: 100%;"> </v-simple-checkbox> <input v-else :type="row.type" v-model="selectedNode[component.key][row.key]" style="padding: 10px;width: 100%;"></input> </td> </tr> </tbody> </table> </template> </v-container> </v-navigation-drawer> <v-content> <v-container fill-height> <div id="content" class="content"> <div class="contentWrap"> <div id="GameDiv" class="wrapper"> </div> </div> </div> </v-container> </v-content> <v-dialog v-model="cacheDialog" persistent scrollable> <v-card> <v-card-title> {{ cacheTitle }} <v-spacer></v-spacer> <v-text-field v-model="cacheSearchText" append-icon="mdi-magnify" label="Search" single-line hide-details> </v-text-field> </v-card-title> <v-divider></v-divider> <v-card-text> <v-data-table :headers="cacheHeaders" :items="cacheData" :search="cacheSearchText" :sort-by="['size']" :sort-desc="[true]" :footer-props="{ showFirstLastPage: true, firstIcon: 'mdi-chevron-double-left', lastIcon: 'mdi-chevron-double-right', }"> <template v-slot:item.size="{ item }"> {{ item.size == -1 ? '_' : (item.size +'MB') }} </template> <template v-slot:item.preview="{ item }"> <div style="height: 60px;display: flex;align-items: center;"> <img :src="window.location.protocol + '//${y}/' + item.preview" style="max-height: 60px;max-width: 120px;" v-if="item.preview"> <template v-else>_</template> </div> </template> </v-data-table> </v-card-text> <v-divider></v-divider> <v-card-actions> <v-btn color="blue darken-1" text @click="cacheDialog = false">Close</v-btn> <v-spacer></v-spacer> <v-switch v-model="cacheOnlyTexture" label="只显示纹理"></v-switch> </v-card-actions> </v-card> </v-dialog> </v-app> ` return html; } appMain(); })();