├── 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 | 
6 |
7 | # 写在前边
8 |
9 | 插件前身是一段扫描指定目录图片资源的代码,用于项目瘦身
10 |
11 | 现在还记得当初手动删无用图片资源(1000+)
12 |
13 | 一上午就在回想资源有没用,删资源,删错了回退
14 |
15 | 后来索性做成了一个Creator插件一劳永逸
16 |
17 | (世界就是我们懒人创造的,哼)
18 |
19 | # 插件功能
20 |
21 | 
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 | 
50 |
51 | ### 通过Github拉取
52 |
53 | git clone https://github.com/shpz/CreatorClean.git
54 |
55 | 在项目工程里有个packages,在这打开git命令行,把上边这段命令复制到命令行里,回车
56 |
57 | 
58 |
59 | # 插件使用
60 |
61 | 
62 |
63 | 插件的制作过程蕴含着我无数的心血和爱,所以插件非常容易上手,爱!
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 |
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
--------------------------------------------------------------------------------