@remvst/asset-catalog 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +1 -0
- package/.husky/pre-push +1 -0
- package/.idea/asset-catalog.iml +12 -0
- package/.idea/copilot.data.migration.agent.xml +6 -0
- package/.idea/copilot.data.migration.ask2agent.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierignore +5 -0
- package/.prettierrc +7 -0
- package/lib/generate-file-catalog.d.ts +2 -0
- package/lib/generate-file-catalog.js +135 -0
- package/lib/generate-image-catalog.js +22 -17
- package/lib/generate-sound-catalog.js +18 -19
- package/lib/tree.js +1 -1
- package/lib/utils.js +7 -4
- package/package.json +42 -31
- package/src/generate-file-catalog.ts +145 -0
- package/src/generate-image-catalog.ts +66 -46
- package/src/generate-sound-catalog.ts +61 -57
- package/src/tree.ts +3 -3
- package/src/utils.ts +7 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx lint-staged
|
package/.husky/pre-push
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm test
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
+
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
+
</content>
|
|
9
|
+
<orderEntry type="inheritedJdk" />
|
|
10
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
+
</component>
|
|
12
|
+
</module>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/asset-catalog.iml" filepath="$PROJECT_DIR$/.idea/asset-catalog.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
package/.idea/vcs.xml
ADDED
package/.prettierignore
ADDED
package/.prettierrc
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const yargs_1 = __importDefault(require("yargs/yargs"));
|
|
11
|
+
const helpers_1 = require("yargs/helpers");
|
|
12
|
+
const tree_1 = require("./tree");
|
|
13
|
+
function importName(assetDir, file) {
|
|
14
|
+
let importName = file;
|
|
15
|
+
importName = (0, path_1.resolve)(file);
|
|
16
|
+
const prefix = (0, path_1.resolve)(assetDir);
|
|
17
|
+
const prefixIndex = importName.indexOf(prefix);
|
|
18
|
+
if (prefixIndex >= 0) {
|
|
19
|
+
importName = importName.slice(prefixIndex + prefix.length);
|
|
20
|
+
}
|
|
21
|
+
importName = (0, utils_1.sanitize)(importName);
|
|
22
|
+
return importName;
|
|
23
|
+
}
|
|
24
|
+
function generatedTemplateInterface(tree, indent = '') {
|
|
25
|
+
let generated = '{\n';
|
|
26
|
+
for (const [subname, item] of tree.entries()) {
|
|
27
|
+
if (item instanceof Map) {
|
|
28
|
+
const generatedSub = generatedTemplateInterface(item, indent + ' ');
|
|
29
|
+
generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: ${generatedSub}`;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const name = (0, path_1.basename)(subname);
|
|
33
|
+
generated += indent + ` ${(0, utils_1.lowerCamelize)(name)}: T,\n`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
generated += indent + '}\n';
|
|
37
|
+
return generated;
|
|
38
|
+
}
|
|
39
|
+
async function generatedCreateCatalogFunction(assetDir, tree) {
|
|
40
|
+
async function rec(tree, indent = '') {
|
|
41
|
+
let generated = '{\n';
|
|
42
|
+
for (const [subname, item] of tree.entries()) {
|
|
43
|
+
if (item instanceof Map) {
|
|
44
|
+
const generatedSub = await rec(item, indent + ' ');
|
|
45
|
+
generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: ${generatedSub},\n`;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const stats = await fs_1.promises.stat(item);
|
|
49
|
+
const name = (0, path_1.basename)(subname);
|
|
50
|
+
generated +=
|
|
51
|
+
indent +
|
|
52
|
+
` ${(0, utils_1.lowerCamelize)(name)}: createItem(expand(
|
|
53
|
+
${importName(assetDir, item)},
|
|
54
|
+
${stats.size},
|
|
55
|
+
'${item}',
|
|
56
|
+
)),\n`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
generated += indent + '}';
|
|
60
|
+
return generated;
|
|
61
|
+
}
|
|
62
|
+
let generated = '';
|
|
63
|
+
generated +=
|
|
64
|
+
'export function createFileCatalog<T>(createItem: (opts: CreateItemOptions) => T): FileCatalog<T> {\n';
|
|
65
|
+
generated += ` return ${await rec(tree, ' ')};\n`;
|
|
66
|
+
generated += '}\n';
|
|
67
|
+
return generated;
|
|
68
|
+
}
|
|
69
|
+
function generateExpandFunction() {
|
|
70
|
+
let generated = '';
|
|
71
|
+
generated +=
|
|
72
|
+
'function expand(path: string, size: number, originalPath: string): CreateItemOptions {\n';
|
|
73
|
+
generated += ` return { path, size, originalPath };\n`;
|
|
74
|
+
generated += '}\n';
|
|
75
|
+
return generated;
|
|
76
|
+
}
|
|
77
|
+
function generateResolveFunction() {
|
|
78
|
+
let generated = '';
|
|
79
|
+
generated +=
|
|
80
|
+
'export function resolveFromCatalog<T>(catalog: FileCatalog<T>, path: string[]): T {\n';
|
|
81
|
+
generated += ' let current: any = catalog;\n';
|
|
82
|
+
generated += ` for (const component of path) {\n`;
|
|
83
|
+
generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
|
|
84
|
+
generated += ` current = current[component];\n`;
|
|
85
|
+
generated += ` }\n`;
|
|
86
|
+
generated += ` return current as T;\n`;
|
|
87
|
+
generated += '}\n';
|
|
88
|
+
return generated;
|
|
89
|
+
}
|
|
90
|
+
async function main() {
|
|
91
|
+
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).options({
|
|
92
|
+
outFile: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
default: 'files.ts',
|
|
95
|
+
alias: 'o',
|
|
96
|
+
describe: 'Directory to generate the files into',
|
|
97
|
+
},
|
|
98
|
+
dir: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
default: '.',
|
|
101
|
+
alias: 'd',
|
|
102
|
+
describe: 'Asset directory where all the PNGs are located',
|
|
103
|
+
},
|
|
104
|
+
}).argv;
|
|
105
|
+
const filesRoot = argv.dir;
|
|
106
|
+
const generatedTs = argv.outFile;
|
|
107
|
+
try {
|
|
108
|
+
await fs_1.promises.rm(generatedTs, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
catch (e) { }
|
|
111
|
+
const files = await (0, utils_1.allFiles)(filesRoot);
|
|
112
|
+
const imports = [];
|
|
113
|
+
const tree = await (0, tree_1.generateTree)(argv.dir, files);
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
const importPath = (0, path_1.relative)((0, path_1.dirname)(argv.outFile), file).replace(/\\/g, '/');
|
|
116
|
+
imports.push(`import ${importName(argv.dir, file)} from './${importPath}';`);
|
|
117
|
+
}
|
|
118
|
+
let generatedFileContent = '';
|
|
119
|
+
generatedFileContent += imports.join('\n');
|
|
120
|
+
generatedFileContent += '\n\n';
|
|
121
|
+
generatedFileContent += `export interface CreateItemOptions {
|
|
122
|
+
path: string;
|
|
123
|
+
size: number;
|
|
124
|
+
originalPath: string;
|
|
125
|
+
}\n\n`;
|
|
126
|
+
generatedFileContent += 'export type FileCatalog<T> = ' + generatedTemplateInterface(tree);
|
|
127
|
+
generatedFileContent += '\n';
|
|
128
|
+
generatedFileContent += generateExpandFunction();
|
|
129
|
+
generatedFileContent += '\n';
|
|
130
|
+
generatedFileContent += generateResolveFunction();
|
|
131
|
+
generatedFileContent += '\n';
|
|
132
|
+
generatedFileContent += await generatedCreateCatalogFunction(argv.dir, tree);
|
|
133
|
+
await fs_1.promises.writeFile(generatedTs, generatedFileContent);
|
|
134
|
+
}
|
|
135
|
+
main();
|
|
@@ -56,7 +56,9 @@ async function generatedCreateCatalogFunction(assetDir, tree, spritesheet) {
|
|
|
56
56
|
if (spriteData) {
|
|
57
57
|
spriteDataArr = `expandSpriteData(SpriteSheetPng, ${spriteData.x}, ${spriteData.y}, ${spriteData.width}, ${spriteData.height})`;
|
|
58
58
|
}
|
|
59
|
-
generated +=
|
|
59
|
+
generated +=
|
|
60
|
+
indent +
|
|
61
|
+
` ${(0, utils_1.lowerCamelize)(withoutExt)}: createItem(expand(
|
|
60
62
|
${importName(assetDir, item)},
|
|
61
63
|
${dimensions.width},
|
|
62
64
|
${dimensions.height},
|
|
@@ -69,24 +71,28 @@ async function generatedCreateCatalogFunction(assetDir, tree, spritesheet) {
|
|
|
69
71
|
return generated;
|
|
70
72
|
}
|
|
71
73
|
let generated = '\n';
|
|
72
|
-
generated +=
|
|
74
|
+
generated +=
|
|
75
|
+
'export function createTextureCatalog<T>(createItem: (opts: CreateItemOptions) => T): TextureCatalog<T> {\n';
|
|
73
76
|
generated += ` return ${await rec(tree, ' ')};\n`;
|
|
74
77
|
generated += '}\n';
|
|
75
78
|
return generated;
|
|
76
79
|
}
|
|
77
80
|
function generateExpandFunction() {
|
|
78
81
|
let generated = '\n';
|
|
79
|
-
generated +=
|
|
82
|
+
generated +=
|
|
83
|
+
'function expandSpriteData(sheet: string, x: number, y: number, width: number, height: number): SpriteData {\n';
|
|
80
84
|
generated += ` return { sheet, frame: { x, y, width, height } };\n`;
|
|
81
85
|
generated += '}\n\n';
|
|
82
|
-
generated +=
|
|
86
|
+
generated +=
|
|
87
|
+
'function expand(path: string, width: number, height: number, size: number, spriteData: SpriteData | null = null): CreateItemOptions {\n';
|
|
83
88
|
generated += ` return { path, width, height, size, spriteData };\n`;
|
|
84
89
|
generated += '}\n';
|
|
85
90
|
return generated;
|
|
86
91
|
}
|
|
87
92
|
function generateResolveFunction() {
|
|
88
93
|
let generated = '\n';
|
|
89
|
-
generated +=
|
|
94
|
+
generated +=
|
|
95
|
+
'export function resolveFromCatalog<T>(catalog: TextureCatalog<T>, path: string[]): T {\n';
|
|
90
96
|
generated += ' let current = catalog;\n';
|
|
91
97
|
generated += ` for (const component of path) {\n`;
|
|
92
98
|
generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
|
|
@@ -127,7 +133,7 @@ async function createSpritesheet(tree, outFile, excludes) {
|
|
|
127
133
|
const image = await (0, canvas_1.loadImage)(item.item.path);
|
|
128
134
|
ctx.drawImage(image, item.x + padding, item.y + padding);
|
|
129
135
|
}
|
|
130
|
-
const buffer = canvas.toBuffer(
|
|
136
|
+
const buffer = canvas.toBuffer('image/png');
|
|
131
137
|
await fs_1.promises.writeFile(outFile, buffer);
|
|
132
138
|
const resultMap = new Map();
|
|
133
139
|
for (const item of packed.items) {
|
|
@@ -141,43 +147,41 @@ async function createSpritesheet(tree, outFile, excludes) {
|
|
|
141
147
|
return resultMap;
|
|
142
148
|
}
|
|
143
149
|
async function main() {
|
|
144
|
-
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
145
|
-
|
|
146
|
-
'outFile': {
|
|
150
|
+
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).options({
|
|
151
|
+
outFile: {
|
|
147
152
|
type: 'string',
|
|
148
153
|
default: 'textures.ts',
|
|
149
154
|
alias: 'o',
|
|
150
155
|
describe: 'Directory to generate the files into',
|
|
151
156
|
},
|
|
152
|
-
|
|
157
|
+
assetDir: {
|
|
153
158
|
type: 'string',
|
|
154
159
|
default: '.',
|
|
155
160
|
alias: 'a',
|
|
156
161
|
describe: 'Asset directory where all the PNGs are located',
|
|
157
162
|
},
|
|
158
|
-
|
|
163
|
+
outSpritesheet: {
|
|
159
164
|
type: 'string',
|
|
160
165
|
required: false,
|
|
161
166
|
alias: 's',
|
|
162
167
|
describe: 'Path to the generated spritesheet',
|
|
163
168
|
},
|
|
164
|
-
|
|
169
|
+
spritesheetExclude: {
|
|
165
170
|
type: 'string',
|
|
166
171
|
array: true,
|
|
167
172
|
required: false,
|
|
168
173
|
alias: 'x',
|
|
169
174
|
describe: 'Exclude certain paths from the spritesheet',
|
|
170
175
|
},
|
|
171
|
-
})
|
|
172
|
-
.argv;
|
|
176
|
+
}).argv;
|
|
173
177
|
const texturesRoot = argv.assetDir;
|
|
174
178
|
const generatedTs = argv.outFile;
|
|
175
179
|
try {
|
|
176
|
-
await fs_1.promises.rm(generatedTs, {
|
|
180
|
+
await fs_1.promises.rm(generatedTs, { recursive: true });
|
|
177
181
|
}
|
|
178
182
|
catch (e) { }
|
|
179
183
|
const files = await (0, utils_1.allFiles)(texturesRoot);
|
|
180
|
-
const pngs = files.filter(file => (0, path_1.extname)(file) === '.png');
|
|
184
|
+
const pngs = files.filter((file) => (0, path_1.extname)(file) === '.png');
|
|
181
185
|
const imports = [];
|
|
182
186
|
const tree = await (0, tree_1.generateTree)(argv.assetDir, pngs);
|
|
183
187
|
for (const png of pngs) {
|
|
@@ -210,7 +214,8 @@ async function main() {
|
|
|
210
214
|
size: number;
|
|
211
215
|
spriteData: SpriteData | null;
|
|
212
216
|
}\n\n`;
|
|
213
|
-
generatedFileContent +=
|
|
217
|
+
generatedFileContent +=
|
|
218
|
+
'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
|
|
214
219
|
generatedFileContent += '\n\n';
|
|
215
220
|
generatedFileContent += generateExpandFunction();
|
|
216
221
|
generatedFileContent += generateResolveFunction();
|
|
@@ -11,41 +11,40 @@ const helpers_1 = require("yargs/helpers");
|
|
|
11
11
|
const path_1 = require("path");
|
|
12
12
|
const audiosprite_1 = __importDefault(require("audiosprite"));
|
|
13
13
|
async function main() {
|
|
14
|
-
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
|
|
15
|
-
|
|
16
|
-
'outFile': {
|
|
14
|
+
const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).options({
|
|
15
|
+
outFile: {
|
|
17
16
|
type: 'string',
|
|
18
17
|
default: '.',
|
|
19
18
|
alias: 'o',
|
|
20
19
|
describe: 'Directory to generate the files into',
|
|
21
20
|
},
|
|
22
|
-
|
|
21
|
+
assetDir: {
|
|
23
22
|
type: 'string',
|
|
24
23
|
default: '.',
|
|
25
24
|
alias: 'a',
|
|
26
25
|
describe: 'Asset directory where all the PNGs are located',
|
|
27
26
|
},
|
|
28
|
-
|
|
27
|
+
wav: {
|
|
29
28
|
type: 'boolean',
|
|
30
29
|
default: false,
|
|
31
30
|
describe: 'Include .wav files',
|
|
32
31
|
},
|
|
33
|
-
|
|
32
|
+
ogg: {
|
|
34
33
|
type: 'boolean',
|
|
35
34
|
default: true,
|
|
36
35
|
describe: 'Include .ogg files',
|
|
37
36
|
},
|
|
38
|
-
|
|
37
|
+
mp3: {
|
|
39
38
|
type: 'boolean',
|
|
40
39
|
default: true,
|
|
41
40
|
describe: 'Include .mp3 files',
|
|
42
41
|
},
|
|
43
|
-
|
|
42
|
+
outSpritesheet: {
|
|
44
43
|
type: 'string',
|
|
45
44
|
required: false,
|
|
46
45
|
describe: 'Path to the exported spritesheet (without the extension)',
|
|
47
46
|
},
|
|
48
|
-
|
|
47
|
+
spritesheetExcludeCategory: {
|
|
49
48
|
type: 'string',
|
|
50
49
|
array: true,
|
|
51
50
|
required: false,
|
|
@@ -53,8 +52,7 @@ async function main() {
|
|
|
53
52
|
default: [],
|
|
54
53
|
describe: 'Exclude certain categories from the spritesheet',
|
|
55
54
|
},
|
|
56
|
-
})
|
|
57
|
-
.argv;
|
|
55
|
+
}).argv;
|
|
58
56
|
const extensions = [];
|
|
59
57
|
if (argv.mp3)
|
|
60
58
|
extensions.push('.mp3');
|
|
@@ -64,12 +62,13 @@ async function main() {
|
|
|
64
62
|
extensions.push('.wav');
|
|
65
63
|
const generatedTs = argv.outFile;
|
|
66
64
|
try {
|
|
67
|
-
await fs_1.promises.rm(generatedTs, {
|
|
65
|
+
await fs_1.promises.rm(generatedTs, { recursive: true });
|
|
68
66
|
}
|
|
69
67
|
catch (e) { }
|
|
70
68
|
const files = await (0, utils_1.allFiles)(argv.assetDir);
|
|
71
|
-
const sounds = files
|
|
72
|
-
.filter(file =>
|
|
69
|
+
const sounds = files
|
|
70
|
+
.filter((file) => extensions.indexOf((0, path_1.extname)(file)) >= 0)
|
|
71
|
+
.filter((file) => (0, path_1.basename)(file, (0, path_1.extname)(file)) !== 'sprites');
|
|
73
72
|
const defs = new Map();
|
|
74
73
|
for (const sound of sounds) {
|
|
75
74
|
const category = (0, utils_1.categoryPath)(argv.assetDir, sound).join('_');
|
|
@@ -100,9 +99,9 @@ async function main() {
|
|
|
100
99
|
for (const filenameWithoutExt of defs.get(category).keys()) {
|
|
101
100
|
const fileSet = defs.get(category).get(filenameWithoutExt);
|
|
102
101
|
const files = Array.from(fileSet);
|
|
103
|
-
const pickedFile = files.find(file => (0, path_1.extname)(file) === '.wav') ||
|
|
104
|
-
files.find(file => (0, path_1.extname)(file) === '.ogg') ||
|
|
105
|
-
files.find(file => (0, path_1.extname)(file) === '.mp3');
|
|
102
|
+
const pickedFile = files.find((file) => (0, path_1.extname)(file) === '.wav') ||
|
|
103
|
+
files.find((file) => (0, path_1.extname)(file) === '.ogg') ||
|
|
104
|
+
files.find((file) => (0, path_1.extname)(file) === '.mp3');
|
|
106
105
|
if (!pickedFile) {
|
|
107
106
|
throw new Error('Unable to pick file for sprite in ' + category);
|
|
108
107
|
}
|
|
@@ -113,7 +112,7 @@ async function main() {
|
|
|
113
112
|
const options = {
|
|
114
113
|
output: argv.outSpritesheet,
|
|
115
114
|
format: 'howler',
|
|
116
|
-
export: extensions.map(ext => ext.slice(1)).join(','),
|
|
115
|
+
export: extensions.map((ext) => ext.slice(1)).join(','),
|
|
117
116
|
};
|
|
118
117
|
(0, audiosprite_1.default)(Array.from(spriteSounds.values()), options, (err, res) => {
|
|
119
118
|
if (err) {
|
|
@@ -140,7 +139,7 @@ async function main() {
|
|
|
140
139
|
definitions.push(soundDefinition);
|
|
141
140
|
if (argv.outSpritesheet) {
|
|
142
141
|
const modifiedSpritesheetJson = { ...spritesheetJson };
|
|
143
|
-
modifiedSpritesheetJson.urls = extensions.map(ext => argv.outSpritesheet + ext);
|
|
142
|
+
modifiedSpritesheetJson.urls = extensions.map((ext) => argv.outSpritesheet + ext);
|
|
144
143
|
let spriteSheetFunc = 'export function sound_spritesheet() {\n';
|
|
145
144
|
spriteSheetFunc += ' return {\n';
|
|
146
145
|
spriteSheetFunc += ' urls: [\n';
|
package/lib/tree.js
CHANGED
package/lib/utils.js
CHANGED
|
@@ -26,11 +26,14 @@ function sanitize(string) {
|
|
|
26
26
|
}
|
|
27
27
|
exports.sanitize = sanitize;
|
|
28
28
|
function camelize(str) {
|
|
29
|
-
return str
|
|
29
|
+
return str
|
|
30
|
+
.split(/[^a-z0-9]/gi)
|
|
31
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
32
|
+
.join('');
|
|
30
33
|
}
|
|
31
34
|
exports.camelize = camelize;
|
|
32
35
|
function lowerCamelize(str) {
|
|
33
|
-
const camelized =
|
|
36
|
+
const camelized = camelize(str);
|
|
34
37
|
return camelized.slice(0, 1).toLowerCase() + camelized.slice(1);
|
|
35
38
|
}
|
|
36
39
|
exports.lowerCamelize = lowerCamelize;
|
|
@@ -39,7 +42,7 @@ function categoryPath(assetDir, filepath) {
|
|
|
39
42
|
const trimmedDir = (0, path_1.relative)(assetDir, dir);
|
|
40
43
|
return trimmedDir
|
|
41
44
|
.split(/[\/\\]/g)
|
|
42
|
-
.map(component => lowerCamelize(component))
|
|
43
|
-
.filter(component => component.length > 0);
|
|
45
|
+
.map((component) => lowerCamelize(component))
|
|
46
|
+
.filter((component) => component.length > 0);
|
|
44
47
|
}
|
|
45
48
|
exports.categoryPath = categoryPath;
|
package/package.json
CHANGED
|
@@ -1,33 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
2
|
+
"name": "@remvst/asset-catalog",
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"bin": {
|
|
6
|
+
"generate-image-catalog": "./lib/generate-image-catalog.js",
|
|
7
|
+
"generate-sound-catalog": "./lib/generate-sound-catalog.js",
|
|
8
|
+
"generate-file-catalog": "./lib/generate-file-catalog.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rm -rf lib && tsc && chmod +x lib/generate-image-catalog.js lib/generate-sound-catalog.js",
|
|
12
|
+
"test:images": "ts-node src/generate-image-catalog.ts --assetDir=./testData --outFile=testOut/images.ts --outSpritesheet=testOut/spritesheet.png",
|
|
13
|
+
"test:sounds": "ts-node src/generate-sound-catalog.ts --assetDir=./testData/sounds --outFile=testOut/sounds.ts --outSpritesheet=testData/sounds/sprites --spritesheetExcludeCategory=jump",
|
|
14
|
+
"test:files": "ts-node src/generate-file-catalog.ts --dir=./testData --outFile=testOut/files.ts",
|
|
15
|
+
"prettier:check": "prettier --check .",
|
|
16
|
+
"prettier:write": "prettier --write .",
|
|
17
|
+
"test": "npm run prettier:check && npm run test:images && npm run test:sounds && npm run test:files",
|
|
18
|
+
"prepublishOnly": "npm i && npm run build",
|
|
19
|
+
"prepare": "husky"
|
|
20
|
+
},
|
|
21
|
+
"author": "Rémi Vansteelandt",
|
|
22
|
+
"license": "UNLICENSED",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"audiosprite": "^0.7.2",
|
|
25
|
+
"bin-pack": "^1.0.2",
|
|
26
|
+
"canvas": "^2.11.2",
|
|
27
|
+
"image-size": "^1.0.2",
|
|
28
|
+
"yargs": "^17.7.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/audiosprite": "^0.7.3",
|
|
32
|
+
"@types/bin-pack": "^1.0.3",
|
|
33
|
+
"@types/node": "^18.11.5",
|
|
34
|
+
"@types/yargs": "^17.0.32",
|
|
35
|
+
"husky": "^9.1.7",
|
|
36
|
+
"lint-staged": "^16.3.0",
|
|
37
|
+
"prettier": "^3.8.1",
|
|
38
|
+
"ts-node": "^10.9.1",
|
|
39
|
+
"typescript": "^5.2.2"
|
|
40
|
+
},
|
|
41
|
+
"lint-staged": {
|
|
42
|
+
"*.{js,ts,json,md}": "prettier --write"
|
|
43
|
+
}
|
|
33
44
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from 'fs';
|
|
4
|
+
import { allFiles, lowerCamelize, sanitize } from './utils';
|
|
5
|
+
import { basename, dirname, extname, relative, resolve } from 'path';
|
|
6
|
+
import yargs from 'yargs/yargs';
|
|
7
|
+
import { hideBin } from 'yargs/helpers';
|
|
8
|
+
import { generateTree, Tree } from './tree';
|
|
9
|
+
|
|
10
|
+
function importName(assetDir: string, file: string): string {
|
|
11
|
+
let importName = file;
|
|
12
|
+
importName = resolve(file);
|
|
13
|
+
|
|
14
|
+
const prefix = resolve(assetDir);
|
|
15
|
+
const prefixIndex = importName.indexOf(prefix);
|
|
16
|
+
if (prefixIndex >= 0) {
|
|
17
|
+
importName = importName.slice(prefixIndex + prefix.length);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
importName = sanitize(importName);
|
|
21
|
+
return importName;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function generatedTemplateInterface(tree: Tree, indent: string = ''): string {
|
|
25
|
+
let generated = '{\n';
|
|
26
|
+
for (const [subname, item] of tree.entries()) {
|
|
27
|
+
if (item instanceof Map) {
|
|
28
|
+
const generatedSub = generatedTemplateInterface(item, indent + ' ');
|
|
29
|
+
generated += indent + ` ${lowerCamelize(subname)}: ${generatedSub}`;
|
|
30
|
+
} else {
|
|
31
|
+
const name = basename(subname);
|
|
32
|
+
generated += indent + ` ${lowerCamelize(name)}: T,\n`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
generated += indent + '}\n';
|
|
36
|
+
return generated;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function generatedCreateCatalogFunction(assetDir: string, tree: Tree): Promise<string> {
|
|
40
|
+
async function rec(tree: Tree, indent: string = '') {
|
|
41
|
+
let generated = '{\n';
|
|
42
|
+
for (const [subname, item] of tree.entries()) {
|
|
43
|
+
if (item instanceof Map) {
|
|
44
|
+
const generatedSub = await rec(item, indent + ' ');
|
|
45
|
+
generated += indent + ` ${lowerCamelize(subname)}: ${generatedSub},\n`;
|
|
46
|
+
} else {
|
|
47
|
+
const stats = await fs.stat(item);
|
|
48
|
+
const name = basename(subname);
|
|
49
|
+
|
|
50
|
+
generated +=
|
|
51
|
+
indent +
|
|
52
|
+
` ${lowerCamelize(name)}: createItem(expand(
|
|
53
|
+
${importName(assetDir, item)},
|
|
54
|
+
${stats.size},
|
|
55
|
+
'${item}',
|
|
56
|
+
)),\n`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
generated += indent + '}';
|
|
60
|
+
return generated;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let generated = '';
|
|
64
|
+
generated +=
|
|
65
|
+
'export function createFileCatalog<T>(createItem: (opts: CreateItemOptions) => T): FileCatalog<T> {\n';
|
|
66
|
+
generated += ` return ${await rec(tree, ' ')};\n`;
|
|
67
|
+
generated += '}\n';
|
|
68
|
+
return generated;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function generateExpandFunction() {
|
|
72
|
+
let generated = '';
|
|
73
|
+
generated +=
|
|
74
|
+
'function expand(path: string, size: number, originalPath: string): CreateItemOptions {\n';
|
|
75
|
+
generated += ` return { path, size, originalPath };\n`;
|
|
76
|
+
generated += '}\n';
|
|
77
|
+
return generated;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function generateResolveFunction() {
|
|
81
|
+
let generated = '';
|
|
82
|
+
generated +=
|
|
83
|
+
'export function resolveFromCatalog<T>(catalog: FileCatalog<T>, path: string[]): T {\n';
|
|
84
|
+
generated += ' let current: any = catalog;\n';
|
|
85
|
+
generated += ` for (const component of path) {\n`;
|
|
86
|
+
generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
|
|
87
|
+
generated += ` current = current[component];\n`;
|
|
88
|
+
generated += ` }\n`;
|
|
89
|
+
generated += ` return current as T;\n`;
|
|
90
|
+
generated += '}\n';
|
|
91
|
+
return generated;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function main() {
|
|
95
|
+
const argv = await yargs(hideBin(process.argv)).options({
|
|
96
|
+
outFile: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
default: 'files.ts',
|
|
99
|
+
alias: 'o',
|
|
100
|
+
describe: 'Directory to generate the files into',
|
|
101
|
+
},
|
|
102
|
+
dir: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
default: '.',
|
|
105
|
+
alias: 'd',
|
|
106
|
+
describe: 'Asset directory where all the PNGs are located',
|
|
107
|
+
},
|
|
108
|
+
}).argv;
|
|
109
|
+
|
|
110
|
+
const filesRoot = argv.dir;
|
|
111
|
+
const generatedTs = argv.outFile;
|
|
112
|
+
try {
|
|
113
|
+
await fs.rm(generatedTs, { recursive: true });
|
|
114
|
+
} catch (e) {}
|
|
115
|
+
|
|
116
|
+
const files = await allFiles(filesRoot);
|
|
117
|
+
|
|
118
|
+
const imports = [];
|
|
119
|
+
const tree = await generateTree(argv.dir, files);
|
|
120
|
+
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
const importPath = relative(dirname(argv.outFile), file).replace(/\\/g, '/');
|
|
123
|
+
imports.push(`import ${importName(argv.dir, file)} from './${importPath}';`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let generatedFileContent = '';
|
|
127
|
+
generatedFileContent += imports.join('\n');
|
|
128
|
+
generatedFileContent += '\n\n';
|
|
129
|
+
generatedFileContent += `export interface CreateItemOptions {
|
|
130
|
+
path: string;
|
|
131
|
+
size: number;
|
|
132
|
+
originalPath: string;
|
|
133
|
+
}\n\n`;
|
|
134
|
+
generatedFileContent += 'export type FileCatalog<T> = ' + generatedTemplateInterface(tree);
|
|
135
|
+
generatedFileContent += '\n';
|
|
136
|
+
generatedFileContent += generateExpandFunction();
|
|
137
|
+
generatedFileContent += '\n';
|
|
138
|
+
generatedFileContent += generateResolveFunction();
|
|
139
|
+
generatedFileContent += '\n';
|
|
140
|
+
generatedFileContent += await generatedCreateCatalogFunction(argv.dir, tree);
|
|
141
|
+
|
|
142
|
+
await fs.writeFile(generatedTs, generatedFileContent);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main();
|
|
@@ -10,7 +10,7 @@ import { Tree, generateTree } from './tree';
|
|
|
10
10
|
import pack from 'bin-pack';
|
|
11
11
|
import { createCanvas, loadImage } from 'canvas';
|
|
12
12
|
|
|
13
|
-
type Rectangle = {x: number
|
|
13
|
+
type Rectangle = { x: number; y: number; width: number; height: number };
|
|
14
14
|
type SpritesheetResult = Map<string, Rectangle>;
|
|
15
15
|
|
|
16
16
|
function importName(assetDir: string, png: string): string {
|
|
@@ -42,7 +42,11 @@ function generatedTemplateInterface(tree: Tree, name: string, indent: string = '
|
|
|
42
42
|
return generated;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
async function generatedCreateCatalogFunction(
|
|
45
|
+
async function generatedCreateCatalogFunction(
|
|
46
|
+
assetDir: string,
|
|
47
|
+
tree: Tree,
|
|
48
|
+
spritesheet: SpritesheetResult | null,
|
|
49
|
+
): Promise<string> {
|
|
46
50
|
async function rec(tree: Tree, indent: string = '') {
|
|
47
51
|
let generated = '{\n';
|
|
48
52
|
for (const [subname, item] of tree.entries()) {
|
|
@@ -55,11 +59,13 @@ async function generatedCreateCatalogFunction(assetDir: string, tree: Tree, spri
|
|
|
55
59
|
const withoutExt = basename(subname, extname(subname));
|
|
56
60
|
const spriteData = spritesheet?.get(resolve(item)) || null;
|
|
57
61
|
let spriteDataArr: string | null = null;
|
|
58
|
-
if (spriteData)
|
|
62
|
+
if (spriteData) {
|
|
59
63
|
spriteDataArr = `expandSpriteData(SpriteSheetPng, ${spriteData.x}, ${spriteData.y}, ${spriteData.width}, ${spriteData.height})`;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
generated +=
|
|
66
|
+
generated +=
|
|
67
|
+
indent +
|
|
68
|
+
` ${lowerCamelize(withoutExt)}: createItem(expand(
|
|
63
69
|
${importName(assetDir, item)},
|
|
64
70
|
${dimensions.width},
|
|
65
71
|
${dimensions.height},
|
|
@@ -73,7 +79,8 @@ async function generatedCreateCatalogFunction(assetDir: string, tree: Tree, spri
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
let generated = '\n';
|
|
76
|
-
generated +=
|
|
82
|
+
generated +=
|
|
83
|
+
'export function createTextureCatalog<T>(createItem: (opts: CreateItemOptions) => T): TextureCatalog<T> {\n';
|
|
77
84
|
generated += ` return ${await rec(tree, ' ')};\n`;
|
|
78
85
|
generated += '}\n';
|
|
79
86
|
return generated;
|
|
@@ -81,10 +88,12 @@ async function generatedCreateCatalogFunction(assetDir: string, tree: Tree, spri
|
|
|
81
88
|
|
|
82
89
|
function generateExpandFunction() {
|
|
83
90
|
let generated = '\n';
|
|
84
|
-
generated +=
|
|
91
|
+
generated +=
|
|
92
|
+
'function expandSpriteData(sheet: string, x: number, y: number, width: number, height: number): SpriteData {\n';
|
|
85
93
|
generated += ` return { sheet, frame: { x, y, width, height } };\n`;
|
|
86
94
|
generated += '}\n\n';
|
|
87
|
-
generated +=
|
|
95
|
+
generated +=
|
|
96
|
+
'function expand(path: string, width: number, height: number, size: number, spriteData: SpriteData | null = null): CreateItemOptions {\n';
|
|
88
97
|
generated += ` return { path, width, height, size, spriteData };\n`;
|
|
89
98
|
generated += '}\n';
|
|
90
99
|
return generated;
|
|
@@ -92,7 +101,8 @@ function generateExpandFunction() {
|
|
|
92
101
|
|
|
93
102
|
function generateResolveFunction() {
|
|
94
103
|
let generated = '\n';
|
|
95
|
-
generated +=
|
|
104
|
+
generated +=
|
|
105
|
+
'export function resolveFromCatalog<T>(catalog: TextureCatalog<T>, path: string[]): T {\n';
|
|
96
106
|
generated += ' let current = catalog;\n';
|
|
97
107
|
generated += ` for (const component of path) {\n`;
|
|
98
108
|
generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
|
|
@@ -103,8 +113,12 @@ function generateResolveFunction() {
|
|
|
103
113
|
return generated;
|
|
104
114
|
}
|
|
105
115
|
|
|
106
|
-
async function createSpritesheet(
|
|
107
|
-
|
|
116
|
+
async function createSpritesheet(
|
|
117
|
+
tree: Tree,
|
|
118
|
+
outFile: string,
|
|
119
|
+
excludes: string[],
|
|
120
|
+
): Promise<SpritesheetResult> {
|
|
121
|
+
const bins: (pack.Bin & { path: string })[] = [];
|
|
108
122
|
|
|
109
123
|
const padding = 1;
|
|
110
124
|
|
|
@@ -141,10 +155,10 @@ async function createSpritesheet(tree: Tree, outFile: string, excludes: string[]
|
|
|
141
155
|
ctx.drawImage(image, item.x + padding, item.y + padding);
|
|
142
156
|
}
|
|
143
157
|
|
|
144
|
-
const buffer = canvas.toBuffer(
|
|
158
|
+
const buffer = canvas.toBuffer('image/png');
|
|
145
159
|
await fs.writeFile(outFile, buffer);
|
|
146
160
|
|
|
147
|
-
const resultMap = new Map<string, {x: number
|
|
161
|
+
const resultMap = new Map<string, { x: number; y: number; width: number; height: number }>();
|
|
148
162
|
for (const item of packed.items) {
|
|
149
163
|
resultMap.set(item.item.path, {
|
|
150
164
|
x: item.x + padding,
|
|
@@ -157,44 +171,42 @@ async function createSpritesheet(tree: Tree, outFile: string, excludes: string[]
|
|
|
157
171
|
}
|
|
158
172
|
|
|
159
173
|
async function main() {
|
|
160
|
-
const argv = await yargs(hideBin(process.argv))
|
|
161
|
-
|
|
162
|
-
'
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
'
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
'
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
})
|
|
188
|
-
.argv;
|
|
174
|
+
const argv = await yargs(hideBin(process.argv)).options({
|
|
175
|
+
outFile: {
|
|
176
|
+
type: 'string',
|
|
177
|
+
default: 'textures.ts',
|
|
178
|
+
alias: 'o',
|
|
179
|
+
describe: 'Directory to generate the files into',
|
|
180
|
+
},
|
|
181
|
+
assetDir: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
default: '.',
|
|
184
|
+
alias: 'a',
|
|
185
|
+
describe: 'Asset directory where all the PNGs are located',
|
|
186
|
+
},
|
|
187
|
+
outSpritesheet: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
required: false,
|
|
190
|
+
alias: 's',
|
|
191
|
+
describe: 'Path to the generated spritesheet',
|
|
192
|
+
},
|
|
193
|
+
spritesheetExclude: {
|
|
194
|
+
type: 'string',
|
|
195
|
+
array: true,
|
|
196
|
+
required: false,
|
|
197
|
+
alias: 'x',
|
|
198
|
+
describe: 'Exclude certain paths from the spritesheet',
|
|
199
|
+
},
|
|
200
|
+
}).argv;
|
|
189
201
|
|
|
190
202
|
const texturesRoot = argv.assetDir;
|
|
191
203
|
const generatedTs = argv.outFile;
|
|
192
204
|
try {
|
|
193
|
-
await fs.rm(generatedTs, {
|
|
205
|
+
await fs.rm(generatedTs, { recursive: true });
|
|
194
206
|
} catch (e) {}
|
|
195
207
|
|
|
196
208
|
const files = await allFiles(texturesRoot);
|
|
197
|
-
const pngs = files.filter(file => extname(file) === '.png');
|
|
209
|
+
const pngs = files.filter((file) => extname(file) === '.png');
|
|
198
210
|
|
|
199
211
|
const imports = [];
|
|
200
212
|
const tree = await generateTree(argv.assetDir, pngs);
|
|
@@ -206,9 +218,16 @@ async function main() {
|
|
|
206
218
|
|
|
207
219
|
let spritesheet: SpritesheetResult | null = null;
|
|
208
220
|
if (argv.outSpritesheet) {
|
|
209
|
-
spritesheet = await createSpritesheet(
|
|
221
|
+
spritesheet = await createSpritesheet(
|
|
222
|
+
tree,
|
|
223
|
+
argv.outSpritesheet,
|
|
224
|
+
argv.spritesheetExclude || [],
|
|
225
|
+
);
|
|
210
226
|
|
|
211
|
-
const importPath = relative(dirname(argv.outFile), resolve(argv.outSpritesheet)).replace(
|
|
227
|
+
const importPath = relative(dirname(argv.outFile), resolve(argv.outSpritesheet)).replace(
|
|
228
|
+
/\\/g,
|
|
229
|
+
'/',
|
|
230
|
+
);
|
|
212
231
|
imports.push(`import SpriteSheetPng from './${importPath}';`);
|
|
213
232
|
}
|
|
214
233
|
|
|
@@ -232,7 +251,8 @@ async function main() {
|
|
|
232
251
|
size: number;
|
|
233
252
|
spriteData: SpriteData | null;
|
|
234
253
|
}\n\n`;
|
|
235
|
-
generatedFileContent +=
|
|
254
|
+
generatedFileContent +=
|
|
255
|
+
'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
|
|
236
256
|
generatedFileContent += '\n\n';
|
|
237
257
|
generatedFileContent += generateExpandFunction();
|
|
238
258
|
generatedFileContent += generateResolveFunction();
|
|
@@ -8,50 +8,48 @@ import { dirname, extname, basename, relative, parse } from 'path';
|
|
|
8
8
|
import audiosprite from 'audiosprite';
|
|
9
9
|
|
|
10
10
|
async function main() {
|
|
11
|
-
const argv = await yargs(hideBin(process.argv))
|
|
12
|
-
|
|
13
|
-
'
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
54
|
-
.argv;
|
|
11
|
+
const argv = await yargs(hideBin(process.argv)).options({
|
|
12
|
+
outFile: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
default: '.',
|
|
15
|
+
alias: 'o',
|
|
16
|
+
describe: 'Directory to generate the files into',
|
|
17
|
+
},
|
|
18
|
+
assetDir: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
default: '.',
|
|
21
|
+
alias: 'a',
|
|
22
|
+
describe: 'Asset directory where all the PNGs are located',
|
|
23
|
+
},
|
|
24
|
+
wav: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: false,
|
|
27
|
+
describe: 'Include .wav files',
|
|
28
|
+
},
|
|
29
|
+
ogg: {
|
|
30
|
+
type: 'boolean',
|
|
31
|
+
default: true,
|
|
32
|
+
describe: 'Include .ogg files',
|
|
33
|
+
},
|
|
34
|
+
mp3: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
default: true,
|
|
37
|
+
describe: 'Include .mp3 files',
|
|
38
|
+
},
|
|
39
|
+
outSpritesheet: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
required: false,
|
|
42
|
+
describe: 'Path to the exported spritesheet (without the extension)',
|
|
43
|
+
},
|
|
44
|
+
spritesheetExcludeCategory: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
array: true,
|
|
47
|
+
required: false,
|
|
48
|
+
alias: 'x',
|
|
49
|
+
default: [] as string[],
|
|
50
|
+
describe: 'Exclude certain categories from the spritesheet',
|
|
51
|
+
},
|
|
52
|
+
}).argv;
|
|
55
53
|
|
|
56
54
|
const extensions: string[] = [];
|
|
57
55
|
if (argv.mp3) extensions.push('.mp3');
|
|
@@ -60,13 +58,13 @@ async function main() {
|
|
|
60
58
|
|
|
61
59
|
const generatedTs = argv.outFile;
|
|
62
60
|
try {
|
|
63
|
-
await fs.rm(generatedTs, {
|
|
61
|
+
await fs.rm(generatedTs, { recursive: true });
|
|
64
62
|
} catch (e) {}
|
|
65
63
|
|
|
66
|
-
|
|
67
64
|
const files = await allFiles(argv.assetDir);
|
|
68
|
-
const sounds = files
|
|
69
|
-
.filter(file =>
|
|
65
|
+
const sounds = files
|
|
66
|
+
.filter((file) => extensions.indexOf(extname(file)) >= 0)
|
|
67
|
+
.filter((file) => basename(file, extname(file)) !== 'sprites');
|
|
70
68
|
|
|
71
69
|
const defs = new Map<string, Map<string, Set<string>>>();
|
|
72
70
|
|
|
@@ -106,9 +104,10 @@ async function main() {
|
|
|
106
104
|
const fileSet = defs.get(category)!.get(filenameWithoutExt)!;
|
|
107
105
|
|
|
108
106
|
const files = Array.from(fileSet);
|
|
109
|
-
const pickedFile =
|
|
110
|
-
files.find(file => extname(file) === '.
|
|
111
|
-
files.find(file => extname(file) === '.
|
|
107
|
+
const pickedFile =
|
|
108
|
+
files.find((file) => extname(file) === '.wav') ||
|
|
109
|
+
files.find((file) => extname(file) === '.ogg') ||
|
|
110
|
+
files.find((file) => extname(file) === '.mp3');
|
|
112
111
|
if (!pickedFile) {
|
|
113
112
|
throw new Error('Unable to pick file for sprite in ' + category);
|
|
114
113
|
}
|
|
@@ -120,7 +119,7 @@ async function main() {
|
|
|
120
119
|
const options: audiosprite.Option = {
|
|
121
120
|
output: argv.outSpritesheet,
|
|
122
121
|
format: 'howler',
|
|
123
|
-
export: extensions.map(ext => ext.slice(1)).join(','),
|
|
122
|
+
export: extensions.map((ext) => ext.slice(1)).join(','),
|
|
124
123
|
};
|
|
125
124
|
|
|
126
125
|
audiosprite(Array.from(spriteSounds.values()), options, (err, res) => {
|
|
@@ -150,18 +149,21 @@ async function main() {
|
|
|
150
149
|
|
|
151
150
|
if (argv.outSpritesheet) {
|
|
152
151
|
const modifiedSpritesheetJson = { ...spritesheetJson };
|
|
153
|
-
modifiedSpritesheetJson.urls = extensions.map(ext => argv.outSpritesheet + ext);
|
|
152
|
+
modifiedSpritesheetJson.urls = extensions.map((ext) => argv.outSpritesheet + ext);
|
|
154
153
|
|
|
155
154
|
let spriteSheetFunc = 'export function sound_spritesheet() {\n';
|
|
156
155
|
spriteSheetFunc += ' return {\n';
|
|
157
156
|
spriteSheetFunc += ' urls: [\n';
|
|
158
157
|
|
|
159
|
-
|
|
160
158
|
const idealOrder = ['.ogg', '.mp3', '.wav'];
|
|
161
|
-
const sortedExtensions = extensions.sort(
|
|
159
|
+
const sortedExtensions = extensions.sort(
|
|
160
|
+
(a, b) => idealOrder.indexOf(a) - idealOrder.indexOf(b),
|
|
161
|
+
);
|
|
162
162
|
for (const extension of sortedExtensions) {
|
|
163
163
|
const importName = 'sprites_' + extension.slice(1);
|
|
164
|
-
imports.push(
|
|
164
|
+
imports.push(
|
|
165
|
+
`import ${importName} from '${relative(dirname(argv.outFile), argv.outSpritesheet + extension).replace(/\\/g, '/')}';`,
|
|
166
|
+
);
|
|
165
167
|
|
|
166
168
|
spriteSheetFunc += ` ${importName},\n`;
|
|
167
169
|
}
|
|
@@ -201,7 +203,9 @@ async function main() {
|
|
|
201
203
|
let totalFileSize = 0;
|
|
202
204
|
for (const soundFile of files) {
|
|
203
205
|
const importName = sanitize(soundFile);
|
|
204
|
-
imports.push(
|
|
206
|
+
imports.push(
|
|
207
|
+
`import ${importName} from '${relative(dirname(argv.outFile), soundFile).replace(/\\/g, '/')}';`,
|
|
208
|
+
);
|
|
205
209
|
func += ` ${importName},\n`;
|
|
206
210
|
const stats = await fs.stat(soundFile);
|
|
207
211
|
totalFileSize += stats.size;
|
package/src/tree.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { basename } from
|
|
2
|
-
import { categoryPath } from
|
|
1
|
+
import { basename } from 'path';
|
|
2
|
+
import { categoryPath } from './utils';
|
|
3
3
|
|
|
4
4
|
export type Tree = Map<string, TreeOrPath>;
|
|
5
5
|
export type TreeOrPath = Tree | string;
|
|
@@ -16,7 +16,7 @@ export async function generateTree(dir: string, files: string[]): Promise<Tree>
|
|
|
16
16
|
subtree = subtree.get(category);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
subtree.set(basename(file)
|
|
19
|
+
subtree.set(basename(file), file);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
return tree;
|
package/src/utils.ts
CHANGED
|
@@ -26,11 +26,14 @@ export function sanitize(string: string): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function camelize(str: string) {
|
|
29
|
-
return str
|
|
29
|
+
return str
|
|
30
|
+
.split(/[^a-z0-9]/gi)
|
|
31
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
32
|
+
.join('');
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export function lowerCamelize(str: string): string {
|
|
33
|
-
const camelized =
|
|
36
|
+
const camelized = camelize(str);
|
|
34
37
|
return camelized.slice(0, 1).toLowerCase() + camelized.slice(1);
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -39,6 +42,6 @@ export function categoryPath(assetDir: string, filepath: string): string[] {
|
|
|
39
42
|
const trimmedDir = relative(assetDir, dir);
|
|
40
43
|
return trimmedDir
|
|
41
44
|
.split(/[\/\\]/g)
|
|
42
|
-
.map(component => lowerCamelize(component))
|
|
43
|
-
.filter(component => component.length > 0);
|
|
45
|
+
.map((component) => lowerCamelize(component))
|
|
46
|
+
.filter((component) => component.length > 0);
|
|
44
47
|
}
|