├── README.MD ├── emoji.jpg ├── emoji2.jpg ├── main.js ├── package.json ├── panel ├── ignore.json ├── less.css ├── panel.html ├── panel.js ├── style │ └── index.less └── utils.js ├── use1.png ├── use2.png └── use3.png /README.MD: -------------------------------------------------------------------------------- 1 | # 支持我 2 | 3 | 插件永久免费,请我喝饮料可以扫底下 4 | 5 | ![image](https://github.com/shpz/AttackArea/blob/master/2AF460741A4A4ED737FBA59267614D7C.png?raw=true) 6 | 7 | # 写在前边 8 | 9 | 插件前身是一段扫描指定目录图片资源的代码,用于项目瘦身 10 | 11 | 现在还记得当初手动删无用图片资源(1000+) 12 | 13 | 一上午就在回想资源有没用,删资源,删错了回退![image](https://raw.githubusercontent.com/shpz/CreatorClean/master/emoji2.jpg) 14 | 15 | 后来索性做成了一个Creator插件一劳永逸 16 | 17 | (世界就是我们懒人创造的,哼) 18 | 19 | # 插件功能 20 | 21 | ![image](http://imgcdn.store.cocos.com/uploads/launcher/source/user/1397846/icon/store_cocos_com_wrench_229939_crop_1515922221.png!175x175) 22 | 23 | 资源整理工厂 24 | 25 | 自动扫描Creator工程 assets目录下所有没有用到的图片 26 | 27 | 插件现在支持扫描: 28 | 29 | AnimationClip(.anim)中用到的图片 30 | 31 | 场景(.fire)上Component、Button和Sprite挂载的图片 32 | 33 | Prefab(.prefab)上Component、Button和Sprite挂载的图片 34 | 35 | 如发现支持其他支持可以告诉我一声... : ) 36 | 37 | 定位资源,统计未使用资源数量,可忽略resources目录, 可自定义忽略目录或字段 38 | 39 | **以及毁天灭地的一键清除功能(!!!慎用)** 40 | 41 | # 插件安装 42 | 43 | ### 通过拓展商店安装(推荐) 44 | 45 | 打开Creator 46 | 47 | 拓展 -> 拓展商店 找到资源整理工厂,下载后选择安装目录,全局目录可以所有项目通用,项目目录则只有该项目能用 48 | 49 | ![image](https://raw.githubusercontent.com/shpz/CreatorClean/master/use1.png) 50 | 51 | ### 通过Github拉取 52 | 53 | git clone https://github.com/shpz/CreatorClean.git 54 | 55 | 在项目工程里有个packages,在这打开git命令行,把上边这段命令复制到命令行里,回车 56 | 57 | ![image](https://raw.githubusercontent.com/shpz/CreatorClean/master/use2.png) 58 | 59 | # 插件使用 60 | 61 | ![image](https://raw.githubusercontent.com/shpz/CreatorClean/master/use3.png) 62 | 63 | 插件的制作过程蕴含着我无数的心血和爱,所以插件非常容易上手,爱!![image](https://raw.githubusercontent.com/shpz/CreatorClean/master/emoji.jpg) 64 | 65 | 插件左上从左到右分别是刷新,一键删除和无用资源统计,插件加载完成默认刷新一次 66 | 67 | 往下是忽略resources目录开关,因为resources里一般是动态加载的资源,默认勾选,不需要的自行取消 68 | 69 | 输入框可以输入自定义忽略的目录或字段,图片资源URL包含字段的话将不会出现在未使用资源清单里 70 | 71 | 目录为URL形式,如果不清楚文件夹或资源的URL可以在Creator里右键目标资源选显示uuid和目录(其实就是在项目工程里的目录) 72 | 73 | 输入框内用英文逗号 “,” 分割,单个字段请尽量详细 74 | 75 | 然后是资源清单区,会显示所有未使用图片资源的路径,操作下边的按钮可以对单个资源定位和删除 76 | 77 | 左下角是可爱的作者的github链接 78 | 79 | # 还有一点点 80 | 81 | 插件用到的css和html使用了部分官方的另一个插件的资源,表示感谢 82 | 83 | @Jare 和 @C姐 给我了我不少帮助,表示感谢 84 | 85 | 关于插件的反馈请提交到github或[论坛讨论帖](http://forum.cocos.com/t/topic/55439/17),表示感谢 86 | 87 | 插件完全由作者自己开发,并保证持续更新和永久免费,对支持我的用户表示感谢 88 | -------------------------------------------------------------------------------- /emoji.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpz/CreatorClean/48a5eca299198f3ec0dc6e2cc3bf67a07d1e1980/emoji.jpg -------------------------------------------------------------------------------- /emoji2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpz/CreatorClean/48a5eca299198f3ec0dc6e2cc3bf67a07d1e1980/emoji2.jpg -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.load = function () { 4 | 5 | }; 6 | 7 | exports.unload = function () { 8 | 9 | }; 10 | 11 | exports.messages = { 12 | open () { 13 | Editor.Panel.open('clean'); 14 | } 15 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clean", 3 | "version": "0.0.1", 4 | "description": "Creator Plugin", 5 | "author": "Shpoz", 6 | "main": "main.js", 7 | "main-menu": { 8 | "工具/Clean": { 9 | "message": "clean:open" 10 | } 11 | }, 12 | "panel": { 13 | "main": "panel/panel.js", 14 | "type": "dockable", 15 | "title": "Clean Tools", 16 | "width": 500, 17 | "height": 300, 18 | "messages": [] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /panel/ignore.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefab": [ 3 | "db://internal/prefab/button.prefab", 4 | "db://internal/prefab/canvas.prefab", 5 | "db://internal/prefab/editbox.prefab", 6 | "db://internal/prefab/label.prefab", 7 | "db://internal/prefab/layout.prefab", 8 | "db://internal/prefab/particlesystem.prefab", 9 | "db://internal/prefab/pageview.prefab", 10 | "db://internal/prefab/progressBar.prefab", 11 | "db://internal/prefab/richtext.prefab", 12 | "db://internal/prefab/scrollview.prefab", 13 | "db://internal/prefab/slider.prefab", 14 | "db://internal/prefab/sprite_splash.prefab", 15 | "db://internal/prefab/tiledmap.prefab", 16 | "db://internal/prefab/sprite.prefab", 17 | "db://internal/prefab/toggle.prefab", 18 | "db://internal/prefab/toggleGroup.prefab", 19 | "db://internal/prefab/webview.prefab", 20 | "db://internal/prefab/videoplayer.prefab" 21 | ], 22 | "scene": [] 23 | } 24 | -------------------------------------------------------------------------------- /panel/less.css: -------------------------------------------------------------------------------- 1 | @import url('app://bower_components/fontawesome/css/font-awesome.min.css'); 2 | :host { 3 | display: flex; 4 | } 5 | #warp { 6 | margin: 10px; 7 | display: flex; 8 | flex: 1; 9 | flex-direction: column; 10 | overflow: hidden; 11 | } 12 | 13 | header { 14 | display: flex; 15 | height: 50px; 16 | } 17 | 18 | header i.fa { 19 | margin: 6px 2px; 20 | padding: 12px 6px; 21 | cursor: pointer; 22 | font-size: 14px; 23 | } 24 | 25 | section.res { 26 | margin: 6px 2px; 27 | padding: 6px 2px; 28 | font-size: 14px; 29 | } 30 | 31 | section.form { 32 | flex: 1; 33 | border-radius: 4px; 34 | border: 1px solid #666; 35 | padding: 10px; 36 | overflow: auto; 37 | } 38 | ul { 39 | margin: 0; 40 | padding: 0; 41 | } 42 | ul li { 43 | padding: 4px 10px; 44 | list-style: none; 45 | display: flex; 46 | border-bottom: 1px solid #666666; 47 | } 48 | ul li:last-child { 49 | border-bottom: none; 50 | } 51 | ul li > div { 52 | padding: 4px 6px; 53 | overflow: hidden; 54 | text-overflow: ellipsis; 55 | white-space: nowrap; 56 | } 57 | ul li .scene { 58 | width: 120px; 59 | } 60 | ul li .path { 61 | flex: 1; 62 | } 63 | ul li .operating { 64 | margin: 0px -50px; 65 | padding: 4px 6px; 66 | } 67 | ul li .controller { 68 | width: 46px; 69 | } 70 | ul li .controller i { 71 | margin: 0 2px; 72 | padding: 0 4px; 73 | cursor: pointer; 74 | } 75 | -------------------------------------------------------------------------------- /panel/panel.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | {{items.length}} 6 |
7 | 8 |
9 | 10 | resources 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 | 35 |
36 |
37 | 38 |
39 | 支持我 40 |
41 | https://github.com/shpz/CreatorClean 42 |
43 | 44 |
-------------------------------------------------------------------------------- /panel/panel.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | const Fs = require('fs'); 5 | const FFs = require('fire-fs'); 6 | const Path = require('path'); 7 | const cp = require('child_process'); 8 | 9 | var PATH = { 10 | html: Editor.url('packages://Clean/panel/panel.html'), 11 | style: Editor.url('packages://Clean/panel/less.css'), 12 | ignore: Editor.url('packages://Clean/panel/ignore.json') 13 | }; 14 | 15 | var createVM = function (elem) { 16 | return new Vue({ 17 | el: elem, 18 | data: { 19 | resources: true, 20 | input: "", 21 | items: [], 22 | ignore: null, 23 | type: ['sprite-frame'], 24 | }, 25 | watch: { 26 | resources() { 27 | this.refresh(); 28 | }, 29 | }, 30 | methods: { 31 | 32 | refresh() { 33 | let adb = Editor.assetdb; 34 | let customIgnore = this.splitInput(this.input); 35 | 36 | this.items.length = 0; 37 | this.items = []; 38 | 39 | let callback = (objs, results) => { 40 | objs.forEach( 41 | (obj) => { 42 | if (this.ignore.prefab.indexOf(obj.url) != -1) { 43 | console.log('Creator\'s prefab.'); 44 | return; 45 | } 46 | 47 | let text = FFs.readFileSync(obj.path, 'utf-8'); 48 | results.forEach(function (result) { 49 | if (result.url.indexOf('/default_') !== -1) { 50 | result.contain = true; 51 | return; 52 | } 53 | 54 | for (let i = 0; i < customIgnore.length; i++) { 55 | if (result.url.indexOf(customIgnore[i]) !== -1) { 56 | result.contain = true; 57 | return; 58 | } 59 | } 60 | 61 | if ( 62 | this.resources && 63 | result.url.indexOf('db://assets/resources') !== -1 64 | ) { 65 | result.contain = true; 66 | return; 67 | } 68 | 69 | if ( 70 | (typeof text) === 'string' && 71 | this.searchBf(text, result.url) 72 | ) { 73 | result.contain = true; 74 | return; 75 | } 76 | 77 | if ( 78 | text['__type__'] === 'cc.AnimationClip' && 79 | this.searchClip(text, result.uuid) 80 | ) { 81 | result.contain = true; 82 | return; 83 | } 84 | 85 | 86 | result.contain = 87 | result.contain ? 88 | true : 89 | this.search(text, result.uuid); 90 | }); 91 | }); 92 | 93 | results.forEach(function (result) { 94 | result.contain == true ? '' : this.items.push({ 95 | url: result.url, 96 | uuid: result.uuid 97 | }); 98 | }); 99 | }; 100 | 101 | adb.queryAssets( 102 | null, 103 | ['scene', 'prefab', 'animation-clip', 'bitmap-font'], 104 | function (err, objs) { 105 | adb.queryAssets( 106 | null, 107 | this.type, 108 | function (err, results) { 109 | callback(objs, results); 110 | } 111 | ); 112 | } 113 | ); 114 | }, 115 | 116 | /** 117 | * Recursive 118 | * @argument {JSON | Array} json 119 | * @argument {String} uuid 120 | */ 121 | search(json, uuid) { 122 | 123 | if (json instanceof Array) { 124 | for (let i = 0; i < json.length; i++) { 125 | if (this.search(json[i], uuid)) { 126 | return true; 127 | } 128 | } 129 | } 130 | else if (json instanceof Object) { 131 | if (json['__type__'] === 'cc.Sprite' && json._spriteFrame) { 132 | return json._spriteFrame.__uuid__ == uuid; 133 | } 134 | else if (json['__type__'] === 'cc.Button') { 135 | return this.searchButton(json, uuid); 136 | } 137 | else if (json['__type__'] && json['__type__'].length > 20) { 138 | if (Editor.Utils.UuidUtils.isUuid( 139 | Editor.Utils.UuidUtils.decompressUuid(json['__type__']) 140 | )) { 141 | return this.searchScript(json, uuid); 142 | } 143 | } 144 | } 145 | }, 146 | 147 | searchButton(json, uuid) { 148 | return (json.pressedSprite && json.pressedSprite.__uuid__ == uuid) || 149 | (json.hoverSprite && json.hoverSprite.__uuid__ == uuid) || 150 | (json._N$normalSprite && json._N$normalSprite.__uuid__ == uuid) || 151 | (json._N$disabledSprite && json._N$disabledSprite.__uuid__ == uuid); 152 | }, 153 | 154 | /** 155 | * Recursive 156 | * 157 | * Search for the script (cc.Class) in the scene file (.fire). 158 | * 159 | * @argument {JSON} json cc.Class 160 | * @argument {String} uuid target.uuid 161 | */ 162 | searchScript(json, uuid) { 163 | let result = []; 164 | 165 | for (let i in json) { 166 | if (json[i] && json[i].__uuid__ && json[i].__uuid__ == uuid) { 167 | return true; 168 | } 169 | } 170 | 171 | return false; 172 | }, 173 | 174 | /** 175 | * Recursive 176 | * 177 | * Search for the animation clip (cc.Animation). 178 | * 179 | * @argument {JSON} json cc.Animation 180 | * @argument {String} uuid target.uuid 181 | */ 182 | searchClip(json, uuid) { 183 | 184 | let spriteFrame = []; 185 | let paths = this.getValue(json, 'paths'); 186 | if (paths) { 187 | for (let i in paths) { 188 | spriteFrame = this.getValue(paths[i], 'spriteFrame'); 189 | if (spriteFrame) { 190 | for (let i = 0; i < spriteFrame.length; i++) { 191 | if (spriteFrame[i] && spriteFrame[i].value && spriteFrame[i].value.__uuid__ && spriteFrame[i].value.__uuid__ === uuid) { 192 | return true; 193 | } 194 | } 195 | } 196 | } 197 | } 198 | else { 199 | spriteFrame = this.getValue(json, 'spriteFrame'); 200 | if (spriteFrame) { 201 | for (let i = 0; i < spriteFrame.length; i++) { 202 | if (spriteFrame[i].value && spriteFrame[i].value.__uuid__ === uuid) { 203 | return true; 204 | } 205 | } 206 | } 207 | } 208 | 209 | return false; 210 | }, 211 | 212 | searchBf(str, url) { 213 | let start = url.lastIndexOf('/') + 1; 214 | let textureName = url.slice(start, url.length); 215 | 216 | if (str.indexOf(textureName) == -1) { 217 | return false; 218 | } 219 | 220 | return true; 221 | }, 222 | 223 | /** 224 | * .. 225 | * @param {JSON} json 226 | * @param {String} key 227 | * @param {Boolean} pan 泛查询开关,因为这样叫比较酷 228 | */ 229 | getValue(json, key, pan) { 230 | key = key ? key : 'spriteFrame'; 231 | if (typeof json !== 'object') { 232 | return null; 233 | } 234 | 235 | for (let i in json) { 236 | if (i === key) { 237 | return json[i]; 238 | } 239 | else { 240 | let value = this.getValue(json[i], key); 241 | if (value) { 242 | return value; 243 | } 244 | } 245 | 246 | } 247 | return null; 248 | }, 249 | 250 | jumpRes(uuid) { 251 | Editor.Ipc.sendToAll('assets:hint', uuid); 252 | Editor.Selection.select('asset', uuid, true); 253 | }, 254 | 255 | onDeleteClick(url) { 256 | let picUrl = this.getPicUrl(url); 257 | this.deleteRes([picUrl], this.items); 258 | }, 259 | 260 | onDeleteAllClick() { 261 | let urlArr = []; 262 | for (let i = 0; i < this.items.length; i++) { 263 | let picUrl = this.getPicUrl(this.items[i].url); 264 | Editor.assetdb.remote.exists(picUrl) ? urlArr.push(picUrl) : ''; 265 | } 266 | this.deleteRes(urlArr, this.items); 267 | Editor.log("删除全部成功!"); 268 | }, 269 | 270 | getPicUrl(url) { 271 | let adb = Editor.assetdb; 272 | let meta = adb.remote.loadMeta(url); 273 | let picUrl = adb.remote.uuidToUrl(meta.rawTextureUuid); 274 | return picUrl; 275 | }, 276 | 277 | /** 278 | * 279 | * @param {String} str 280 | */ 281 | splitInput(str) { 282 | if (!str) { 283 | return []; 284 | } 285 | return str.split(','); 286 | }, 287 | 288 | goHub() { 289 | cp.exec('start https://github.com/shpz/CreatorClean/blob/master/README.MD'); 290 | }, 291 | 292 | deleteRes(url, items) { 293 | 294 | let adb = Editor.assetdb; 295 | if (url.length > 1) { 296 | this.refresh(); 297 | } 298 | else { 299 | let index = items.findIndex(function (item, index, array) { 300 | return this.getPicUrl(item.url) == url[0]; 301 | }); 302 | index == -1 ? '' : items.splice(index, 1); 303 | } 304 | adb.delete(url); 305 | // this.refresh(); 306 | }, 307 | } 308 | }); 309 | }; 310 | 311 | Editor.Panel.extend({ 312 | template: Fs.readFileSync(PATH.html, 'utf-8'), 313 | style: Fs.readFileSync(PATH.style, 'utf-8'), 314 | 315 | $: { 316 | 'warp': '#warp' 317 | }, 318 | 319 | ready() { 320 | this.vm = createVM(this.$warp); 321 | this.vm.ignore = FFs.readJsonSync(PATH.ignore); 322 | this.vm.refresh(); 323 | }, 324 | 325 | // ipc 326 | messages: { 327 | 'scene:ready'() { 328 | } 329 | } 330 | }); -------------------------------------------------------------------------------- /panel/style/index.less: -------------------------------------------------------------------------------- 1 | @import url('app://bower_components/fontawesome/css/font-awesome.min.css'); 2 | 3 | :host { 4 | display: flex; 5 | } 6 | 7 | #warp { 8 | margin: 10px; 9 | display: flex; 10 | flex: 1; 11 | flex-direction: column; 12 | overflow: hidden; 13 | } 14 | 15 | header { 16 | display: flex; 17 | height: 50px; 18 | 19 | ui-prop { 20 | display: flex; 21 | flex: 1; 22 | 23 | ui-asset { 24 | flex: 1; 25 | } 26 | } 27 | 28 | i.fa { 29 | margin: 6px 2px; 30 | padding: 12px 6px; 31 | cursor: pointer; 32 | font-size: 14px; 33 | } 34 | } 35 | 36 | 37 | section { 38 | flex: 1; 39 | border-radius: 4px; 40 | border: 1px solid #666; 41 | padding: 10px; 42 | overflow: auto; 43 | } 44 | 45 | ul { 46 | margin: 0; 47 | padding: 0; 48 | 49 | li { 50 | padding: 4px 10px; 51 | list-style: none; 52 | display: flex; 53 | 54 | border-bottom: 1px solid #666666; 55 | 56 | &:last-child { 57 | border-bottom: none; 58 | } 59 | 60 | & > div { 61 | padding: 4px 6px; 62 | overflow: hidden; 63 | text-overflow: ellipsis; 64 | white-space: nowrap; 65 | } 66 | 67 | .scene { 68 | width: 120px; 69 | } 70 | 71 | .path { 72 | flex: 1; 73 | } 74 | 75 | .controller { 76 | width: 46px; 77 | 78 | i { 79 | margin: 0 2px; 80 | padding: 0 4px; 81 | cursor: pointer; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /panel/utils.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const ffs = require("fire-fs") 4 | 5 | let utils = { 6 | 7 | /** 8 | * @param {string} fileName 9 | * @param {string} lookingForString 10 | * @param {Array} items 11 | * @returns {void} 12 | */ 13 | recursiveReadFile(fileName, lookingForString, items) { 14 | if (!fs.existsSync(fileName)) { 15 | return; 16 | } 17 | 18 | if (utils.isFile(fileName)) { 19 | utils.check(fileName, lookingForString, items); 20 | } 21 | 22 | if (utils.isDirectory(fileName)) { 23 | let files = fs.readdirSync(fileName); 24 | files.forEach(function (val, key) { 25 | let temp = path.join(fileName, val); 26 | if (utils.isDirectory(temp)) { 27 | utils.recursiveReadFile(temp, lookingForString, items); 28 | } 29 | 30 | if (utils.isFile(temp)) { 31 | utils.check(temp, lookingForString, items); 32 | } 33 | }) 34 | } 35 | }, 36 | 37 | /** 38 | * @param {string} fileName 39 | * @param {string} lookingForString 40 | * @returns {void} 41 | */ 42 | check(fileName, lookingForString) { 43 | let data = utils.readFile(fileName); 44 | let exc = new RegExp(lookingForString); 45 | 46 | if (exc.test(data)) { 47 | return true; 48 | } 49 | 50 | return false; 51 | }, 52 | 53 | /** 54 | * @param {string} fileName 55 | * @returns {boolean} 56 | */ 57 | isDirectory(fileName) { 58 | if (fs.existsSync(fileName)) { 59 | return fs.statSync(fileName).isDirectory(); 60 | } 61 | 62 | return false; 63 | }, 64 | 65 | /** 66 | * @param {string} fileName 67 | * @returns {boolean} 68 | */ 69 | isFile(fileName) { 70 | if (fs.existsSync(fileName)) { 71 | return fs.statSync(fileName).isFile(); 72 | } 73 | 74 | return false; 75 | }, 76 | 77 | /** 78 | * 使用 uuid 获取 json 79 | * @param {string} uuid 80 | * @returns {Object} 81 | */ 82 | getJsonByUuid(uuid) { 83 | let path = Editor.assetdb.remote.uuidToFspath(uuid); 84 | 85 | return ffs.readJsonSync(path); 86 | }, 87 | 88 | /** 89 | * @param {string} fileName 90 | * @returns {boolean} 91 | */ 92 | readFile(fileName) { 93 | if (fs.existsSync(fileName)) { 94 | return fs.readFileSync(fileName, "utf-8"); 95 | } 96 | 97 | return false; 98 | }, 99 | 100 | /** 101 | * 查询重复的键 102 | * @param {any} zh 103 | * @returns {Array} 104 | */ 105 | queryDuplicatesKey(zh) { 106 | let array = Object.keys(zh); 107 | let result = [] 108 | 109 | while (array.length > 0) { 110 | let key = array.pop(); 111 | 112 | while (array.indexOf(key) != -1) { 113 | result.push({ key: key, value: zh[key] }); 114 | array.splice(array.indexOf(key), 1) 115 | } 116 | 117 | } 118 | 119 | return result; 120 | 121 | }, 122 | 123 | }; 124 | 125 | module.exports = utils; -------------------------------------------------------------------------------- /use1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpz/CreatorClean/48a5eca299198f3ec0dc6e2cc3bf67a07d1e1980/use1.png -------------------------------------------------------------------------------- /use2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpz/CreatorClean/48a5eca299198f3ec0dc6e2cc3bf67a07d1e1980/use2.png -------------------------------------------------------------------------------- /use3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shpz/CreatorClean/48a5eca299198f3ec0dc6e2cc3bf67a07d1e1980/use3.png --------------------------------------------------------------------------------