Compare commits

...

14 Commits

Author SHA1 Message Date
potato47
7c78fc9e57
Merge pull request from hiboy51/fix
fix: 修正计时器未正确关闭的问题;适配3.8.x版本api
2025-03-15 14:09:00 +08:00
KinnonZ
d50d90d130 fix: 修正计时器未正确关闭的问题;适配3.8.x版本api 2024-10-30 17:07:06 +08:00
next
44836565c0 fix:适配3.6.2,修复键盘事件失效问题 2022-12-11 16:07:41 +08:00
next
9a232daab8 fix:适配3.6.2,修复键盘事件失效问题 2022-12-11 16:05:14 +08:00
next
f7a300c3e6 fix:修复3.6.2版本控制台报错 2022-12-11 15:42:26 +08:00
potato47
bb7f564d19
Update README.md 2022-12-10 22:55:35 +08:00
nextfu
016809617e update version 2022-08-08 10:52:06 +08:00
nextfu
617a14c0f2 fix:修复cc报错 2022-08-05 15:22:51 +08:00
nextfu
c36ccaf21e 更新预览 2022-08-04 16:30:12 +08:00
nextfu
b65bd645a0 update 2022-08-04 16:24:52 +08:00
nextfu
77073f20d2 update 2022-08-03 18:00:31 +08:00
nextfu
90baadd0aa 添加开关 2022-08-02 21:12:45 +08:00
nextfu
6aeca4583e test 2022-07-26 20:05:42 +08:00
Next
65d90e20b9 测试 2022-07-26 10:16:56 +08:00
20 changed files with 403 additions and 167 deletions

@ -1,10 +1,85 @@
> 主干分支适用 Cocos Creator 3.4+ 版本,其他版本查看其他分支
# ccc-devtools
Cocos Creator 网页调试工具,运行时查看、修改节点树,实时更新节点属性。
![](screenshots/preview.png)
## 简介
# 使用
ccc-devtools 是一款用于 Cocos Creator 网页端预览的调试工具,可以实时显示场景的节点树,并对节点属性进行同步更改。
将 release 下的 preview-template.zip 解压放到项目目录下,浏览器预览项目即可使用。
![p1](https://user-images.githubusercontent.com/21299133/206861290-0bf8c74a-e8c6-435c-b17c-9949e4db6d55.gif)
适用于 Cocos Creator 3.x 版本。
## 使用
下载打包好的 [preview-template.zip](https://github.com/potato47/ccc-devtools/raw/master/release/preview-template.zip) 文件(release目录下),解压到 Cocos Creator 项目目录下,刷新预览时的浏览器即可。
节点属性修改不赘述,重点介绍几个独特的小功能:
- 输出节点、组件引用到控制台,配合调试比较常用
![image](https://user-images.githubusercontent.com/21299133/206860999-bd0a3184-a692-45fe-ac5e-4e7361fa091c.png)
- 标记UI节点在场景中的位置
![image](https://user-images.githubusercontent.com/21299133/206854782-f74e8b3c-d804-4919-afb7-bef559719933.png)
- 调试信息独立显示,再也不怕浅背景看不清 FPS 了
![image](https://user-images.githubusercontent.com/21299133/206854791-3dcb52eb-5fa3-4157-b4dd-2a2d83932f5a.png)
## 开发
项目依据文档中自定义预览模板一节进行开发,未接触过相关概念可以先阅读一下官方文档
>自定义预览模板
预览支持自定义模板方便用户自定义需要的预览效果自定义的预览模板可以放置在项目目录的 preview-template 文件夹中。或者点击编辑器主菜单中的 项目 -> 生成预览模板 就可以在项目目录下创建一个最新的预览模板。编辑器中的预览也是使用模板来注入最新的项目数据,预览时将会查找该目录下的 index 文件,如果存在就是要该文件作为预览的模板。
preview-template 文件夹的结构类似
project-folder
|--assets
|--build
|--preview-template
    // 必须的入口文件
    |--index.ejs
    // 其他文件可根据想要实现的预览效果进行添加
本项目主要修改index.ejs注入一段 Vue 绑定的自定义html核心修改见下图
![image](https://user-images.githubusercontent.com/21299133/206854643-41038621-1414-4518-a799-3c54d54e3e75.png)
在浏览器环境中 cc 是一个全局变量,可以通过 cc.director.getScene().children 获取场景中的节点,知道这点就可以开发了,剩下的就是节点数据如何展示出来的问题了。
技术栈为 Vue3 + ElementPlus + TypeScript + Vite熟悉前端的朋友欢迎来仓库贡献。
项目结构如下:
![image](https://user-images.githubusercontent.com/21299133/206854626-03d127c8-6b26-4ae6-a1fa-1793e46b66e8.png)
项目开发需配合本地已有的 Cocos Creator 3.x 项目,将 ccc-devtools 克隆到本地后打开scripts/setup.js将 projectTemplatePath 改为你的本地测试 Creator 项目路径。
![image](https://user-images.githubusercontent.com/21299133/206854670-7e95c7bc-e5b9-4ca9-8feb-4470d5a81e2e.png)
开发流程:
- 安装依赖
`yarn`
- 修改代码
- 构建项目
`yarn build`
- 安装构建产物到项目
`yarn setup`
- 刷新浏览器查看效果
## 插件商店版
[插件版](https://store.cocos.com/app/detail/3922)自带了一份工具代码,提供了自动安装功能。适合多个项目快速安装、卸载工具。
![image](https://user-images.githubusercontent.com/21299133/206855136-cc8f2018-6844-4ae1-b2b4-5dbf016488dc.png)
## 备注
- 源码地址https://github.com/potato47/ccc-devtools
- 打包文件https://github.com/potato47/ccc-devtools/raw/master/release/preview-template.zip
- UI 组件库https://element-plus.org
- 自定义网页预览文档https://docs.cocos.com/creator/manual/zh/editor/preview/browser.html
- 插件地址https://store.cocos.com/app/detail/3922

3
components.d.ts vendored

@ -10,12 +10,15 @@ declare module '@vue/runtime-core' {
CCComponent: typeof import('./src/components/CCComponent.vue')['default']
CCNode: typeof import('./src/components/CCNode.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElLink: typeof import('element-plus/es')['ElLink']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElTreeV2: typeof import('element-plus/es')['ElTreeV2']
ProfilerPanel: typeof import('./src/components/ProfilerPanel.vue')['default']
PropItem: typeof import('./src/components/PropItem.vue')['default']
TreePanel: typeof import('./src/components/TreePanel.vue')['default']
UserComponent: typeof import('./src/components/UserComponent.vue')['default']

@ -1,2 +1,2 @@
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<div id="dev-app" style="width: 400px;height: 100%;display: flex;flex-direction: column;justify-content: center;"></div>
<script type="module" src="/src/main.ts"></script>

@ -7,7 +7,8 @@
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"package": "node scripts/package.js"
"package": "node scripts/package.js",
"setup": "node scripts/setup.js"
},
"dependencies": {
"element-plus": "^2.2.6",
@ -17,6 +18,7 @@
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.3",
"adm-zip": "^0.5.9",
"fs-extra": "^10.1.0",
"typescript": "^4.5.4",
"unplugin-auto-import": "^0.9.3",
"unplugin-vue-components": "^0.21.1",

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,3 +1,4 @@
<script type="module" crossorigin src="/dist/assets/index.bec476d0.js"></script>
<link rel="stylesheet" href="/dist/assets/index.1d01bced.css">
<div id="app"></div>
<script type="module" crossorigin src="/dist/assets/index.95bf25f5.js"></script>
<link rel="stylesheet" href="/dist/assets/index.741f95c0.css">
<div id="dev-app" style="width: 400px;height: 100%;display: flex;flex-direction: column;justify-content: center;"></div>

@ -19,29 +19,57 @@
</head>
<body>
<%- include(cocosToolBar, {config: config}) %>
<div id="content" class="content">
<div class="contentWrap">
<div id="GameDiv" class="wrapper">
<div id="Cocos3dGameContainer">
<canvas id="GameCanvas"></canvas>
</div>
<div id="splash">
<div class="progress-bar stripes"><span></span></div>
</div>
<div id="bulletin">
<div id="sceneIsEmpty" class="inner"><%=tip_sceneIsEmpty%></div>
</div>
<div class="error" id="error">
<div class="title">Error <i>(Please open the console to see detailed errors)</i></div>
<div class="error-main"></div>
<div style="display: flex;flex: auto;align-items: center;">
<%- include ./dist/index.html %>
<div id="content" class="content">
<div class="contentWrap">
<div id="GameDiv" class="wrapper">
<div id="Cocos3dGameContainer">
<canvas id="GameCanvas" tabindex="-1" style="background-color: '';"></canvas>
</div>
<div id="splash">
<div class="progress-bar stripes"><span></span></div>
</div>
<div id="bulletin">
<div id="sceneIsEmpty" class="inner"><%=tip_sceneIsEmpty%></div>
</div>
<div class="error" id="error">
<div class="title">Error <i>(Please open the console to see detailed errors)</i></div>
<div class="error-main"></div>
<div class="error-stack"></div>
</div>
</div>
</div>
<p class="footer">
Created with <a href="https://www.cocos.com/products" target="_blank" title="Cocos Creator">Cocos Creator</a>
</p>
</div>
<p class="footer">
<% include ./dist/index.html %>
</p>
</div>
<%- include(cocosTemplate, {}) %>
</body>
</html>
<script>
document.getElementsByClassName('toolbar')[0].insertAdjacentHTML('afterbegin', '<div><button id="btn-show-tree">Tree</button></div>');
const devtoolsBtn = document.getElementById('btn-show-tree');
let isOpen = !!localStorage.getItem('ccc_devtools_show');
toggle(isOpen);
devtoolsBtn.addEventListener('click', () => {
isOpen = !isOpen;
toggle(isOpen);
}, false);
function toggle(isOpen) {
const devApp = document.getElementById('dev-app');
window.ccdevShow = isOpen;
if (isOpen) {
devApp.style.display = 'flex';
devtoolsBtn.classList.add('checked');
localStorage.setItem('ccc_devtools_show', 1);
} else {
devApp.style.display = 'none';
devtoolsBtn.classList.remove('checked');
localStorage.removeItem('ccc_devtools_show');
}
}
</script>

@ -1 +1 @@
{"name":"ccc-devtools","version":"2022/7/17","author":"Next","repo":"https://github.com/potato47/ccc-devtools.git"}
{"name":"ccc-devtools","version":"2022/12/11","author":"Next","repo":"https://github.com/potato47/ccc-devtools.git"}

Binary file not shown.

Before

(image error) Size: 119 KiB

After

(image error) Size: 459 KiB

15
scripts/setup.js Normal file

@ -0,0 +1,15 @@
const fse = require('fs-extra');
const path = require('path');
const localTemplatePath = path.join(__dirname, '../release/');
const projectTemplatePath = '/Users/next/projects/cocos/example352/';
if (!fse.existsSync(projectTemplatePath)) {
console.error('project path not exist');
return;
}
fse.copy(localTemplatePath, projectTemplatePath).then(() => {
console.log('更新预览模板成功');
}).catch(err => {
console.error('更新预览模板失败', err);
});

@ -1,24 +1,31 @@
<script setup lang="ts">
import TreePanel from './components/TreePanel.vue';
import { ref } from 'vue';
let showTree = ref(false);
import ProfilerPanel from './components/ProfilerPanel.vue';
let showProfiler = ref(false);
window.addEventListener('showProfiler', (e: any) => {
showProfiler.value = !showProfiler.value;
});
</script>
<template>
<div>
<vue-final-modal v-model="showTree" classes="modal-container" content-class="modal-content" :hide-overlay="true"
<vue-final-modal v-model="showProfiler" classes="modal-container" content-class="modal-content" :hide-overlay="true"
:click-to-close="false" :prevent-click="true" :drag="true" :fit-parent="true" drag-selector=".modal-drag">
<TreePanel :show="showTree"></TreePanel>
<ProfilerPanel :show="showProfiler"></ProfilerPanel>
</vue-final-modal>
<el-button size="small" @click="showTree = !showTree">节点树</el-button>
</div>
<el-card :body-style="{ padding: 0 }" style="margin: 10px;">
<TreePanel :show="true"></TreePanel>
</el-card>
<el-link type="primary" href="https://github.com/potato47/ccc-devtools" target="_blank" style="position:absolute;left: 5px;bottom: 5px;">ccc-devtools</el-link>
</template>
<style scoped>
:deep(.modal-container) {
display: flex;
justify-content: start;
align-items: center;
justify-content: end;
align-items: start;
}
:deep(.modal-content) {
@ -29,8 +36,6 @@ let showTree = ref(false);
padding: 0;
border: 1px solid cadetblue;
background: #171920;
min-width: 400px;
height: 80%;
}
</style>

@ -5,8 +5,11 @@
<el-button size="small" @click="Utils.drawNodeRect(ccNode)">+</el-button>
<el-button size="small" @click="Utils.outputToConsole(ccNode)">></el-button>
</div>
<PropItem v-for="prop in NodeModel.props" :key="prop.key" :model="NodeModel" :prop-name="prop.name"
:prop-key="prop.key" :update-key="updateKey!"></PropItem>
<template v-if="ccNode!.name != 'PROFILER_NODE'">
<PropItem v-for="prop in NodeModel.props" :key="prop.key" :model="NodeModel" :prop-name="prop.name"
:prop-key="prop.key" :update-key="updateKey!"></PropItem>
</template>
<ProfilerPanel v-if="ccNode!.name == 'PROFILER_NODE'" :show="true"></ProfilerPanel>
</template>
<script setup lang="ts">

@ -0,0 +1,78 @@
<template>
<div style="width: 100%;height: 30px;background-color: #26282f;display: flex;align-items: center;justify-content: center;color: white;"
class="modal-drag">
Profiler
</div>
<div style="width: 100%;">
<div class="row" v-for="item in items" :key="item.key">
<span>{{ item.desc }}</span>
<span style="flex: 1;text-align: right;">{{ item.value }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue-demi';
const props = defineProps({
show: Boolean,
});
let items = ref<any[]>([]);
let timeoutId: number;
function refresh() {
// @ts-ignore
const cc = window['cc'];
if (!cc || !cc.profiler || !cc.profiler.stats) {
return;
}
// @ts-ignore
const stats = cc.profiler.stats;
items.value.forEach(item => {
const data = stats[item.key];
item.desc = data.desc;
if (data.isInteger) {
item.value = data.counter._value | 0;
} else {
item.value = data.counter._value.toFixed(2);
}
});
timeoutId = setTimeout(refresh, 1000);
}
function init() {
items.value = [
{ key: 'fps', desc: '', value: 0 },
{ key: 'draws', desc: '', value: 0 },
{ key: 'frame', desc: '', value: 0 },
{ key: 'instances', desc: '', value: 0 },
{ key: 'tricount', desc: '', value: 0 },
{ key: 'logic', desc: '', value: 0 },
{ key: 'physics', desc: '', value: 0 },
{ key: 'render', desc: '', value: 0 },
{ key: 'textureMemory', desc: '', value: 0 },
{ key: 'bufferMemory', desc: '', value: 0 },
];
refresh();
}
onMounted(() => {
init();
});
onUnmounted(() => {
if (!isNaN(timeoutId)) {
clearTimeout(timeoutId);
}
});
</script>
<style>
.row {
display: flex;
justify-content: center;
margin: 10px;
}
</style>

@ -1,17 +1,14 @@
<template>
<div
style="width: 100%;height: 30px;background-color: #26282f;display: flex;align-items: center;justify-content: center;color: white;"
class="modal-drag">
节点树
<div style="width: 100%;" :style="{ height: treeViewHeight }">
<el-tree-v2 ref="treeView" :props="defaultProps" empty-text="正在加载场景" :highlight-current="true"
:expand-on-click-node="false" :default-expanded-keys="expandedKeys" @current-change="handleCurrentNodeChange"
@node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse" :height="treeViewHeight">
<template #default="{ node }">
<span :class="{ 'node-hide': !node.data.active }">{{ node.label }}</span>
</template>
</el-tree-v2>
</div>
<el-tree-v2 ref="treeView" :props="defaultProps" empty-text="正在加载场景" :highlight-current="true"
:expand-on-click-node="false" :default-expanded-keys="expandedKeys" @current-change="handleCurrentNodeChange"
@node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse" :height="treeViewHeight">
<template #default="{ node }">
<span :class="{ 'node-hide': !node.data.active }">{{ node.label }}</span>
</template>
</el-tree-v2>
<div style="width: 100%;border-top: 2px solid #1d1e21;overflow: auto;flex: 1;">
<div style="width: 100%;border-top: 2px solid #414243;" :style="{ height: treeViewHeight }">
<template v-if="updateKey !== 0 && Utils.checkNodeValid(currentNode)">
<el-scrollbar>
<CCNode :cc-node="currentNode" :update-key="updateKey"></CCNode>
@ -58,7 +55,7 @@ const defaultProps = {
children: 'children',
};
const treeViewHeight = window.innerHeight * 0.4;
const treeViewHeight = (window.innerHeight - 120) / 2;
const treeView = ref(null);
onMounted(() => {
@ -111,7 +108,8 @@ function setChildren(container: TreeNode[], children: any[], path: string[]) {
}
function refreshTree() {
if (props.show) {
// @ts-ignore
if (props.show && window.ccdevShow) {
let value: TreeNode[] = [];
//@ts-ignore
setChildren(value, cc.director.getScene().children, []);

@ -3,4 +3,4 @@ import App from './App.vue';
import 'element-plus/theme-chalk/dark/css-vars.css';
import vfmPlugin from 'vue-final-modal'
createApp(App).use(vfmPlugin).mount('#app');
createApp(App).use(vfmPlugin).mount('#dev-app');

@ -502,6 +502,15 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
fs-extra@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
@ -519,6 +528,11 @@ glob-parent@^5.1.2, glob-parent@~5.1.2:
dependencies:
is-glob "^4.0.1"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.npmmirror.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@ -562,6 +576,15 @@ jsonc-parser@^3.0.0:
resolved "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.1.0.tgz#73b8f0e5c940b83d03476bc2e51a20ef0932615d"
integrity sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
local-pkg@^0.4.1, local-pkg@^0.4.2:
version "0.4.2"
resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f"
@ -787,6 +810,11 @@ unimport@^0.4.5:
strip-literal "^0.4.0"
unplugin "^0.7.2"
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
unplugin-auto-import@^0.9.3:
version "0.9.3"
resolved "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.9.3.tgz#bdcfe8f40cf74eece7bad2b37ad319ae4e019b97"