create-threejs-game 1.1.0 → 1.1.2
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/README.md +9 -5
- package/bin/cli.js +32 -19
- package/package.json +1 -1
- package/template/scripts/combine-previews.js +35 -17
- package/template/scripts/generate-assets-json.js +54 -10
package/README.md
CHANGED
|
@@ -14,14 +14,18 @@ Download GLTF asset packs from:
|
|
|
14
14
|
|
|
15
15
|
The CLI will ask for the path to your downloaded assets folder and validate it contains `.gltf` or `.glb` files.
|
|
16
16
|
|
|
17
|
-
**Multi-Pack Support:** You can combine multiple asset packs by organizing them in subdirectories:
|
|
17
|
+
**Multi-Pack Support:** You can combine multiple asset packs by organizing them in subdirectories (nested to any depth):
|
|
18
18
|
```
|
|
19
19
|
my-assets/
|
|
20
|
-
├── characters/
|
|
21
|
-
├──
|
|
22
|
-
└──
|
|
20
|
+
├── characters/
|
|
21
|
+
│ ├── humans/ # Has Preview.jpg
|
|
22
|
+
│ └── monsters/ # Has Preview.jpg
|
|
23
|
+
├── buildings/ # Has Preview.jpg
|
|
24
|
+
└── environment/
|
|
25
|
+
├── trees/ # Has Preview.jpg
|
|
26
|
+
└── rocks/ # Has Preview.jpg
|
|
23
27
|
```
|
|
24
|
-
The CLI
|
|
28
|
+
The CLI recursively finds all directories with previews and combines them into a single grid.
|
|
25
29
|
|
|
26
30
|
### 2. Preview Image (Auto-generated for multi-packs)
|
|
27
31
|
Most asset packs include a `Preview.jpg`. For multi-pack directories, the CLI automatically combines all subdirectory previews into one image. For single packs without a preview, take a screenshot of your assets.
|
package/bin/cli.js
CHANGED
|
@@ -112,28 +112,41 @@ function findPreview(dir) {
|
|
|
112
112
|
return null;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
//
|
|
116
|
-
function
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
if
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
// Recursively find all directories with preview images
|
|
116
|
+
function findPacksWithPreviews(dir, basePath = dir) {
|
|
117
|
+
const packs = [];
|
|
118
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
119
|
+
|
|
120
|
+
// Check if this directory has a preview
|
|
121
|
+
const preview = findPreview(dir);
|
|
122
|
+
if (preview && dir !== basePath) {
|
|
123
|
+
// Use relative path from base as the name
|
|
124
|
+
const relativePath = path.relative(basePath, dir);
|
|
125
|
+
packs.push({
|
|
126
|
+
name: relativePath,
|
|
127
|
+
path: dir,
|
|
128
|
+
preview: preview
|
|
129
|
+
});
|
|
130
|
+
}
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
133
|
-
}
|
|
132
|
+
// Recursively check subdirectories
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (!entry.isDirectory()) continue;
|
|
135
|
+
if (entry.name.startsWith('.')) continue; // Skip hidden dirs
|
|
136
|
+
|
|
137
|
+
const subdirPath = path.join(dir, entry.name);
|
|
138
|
+
const subPacks = findPacksWithPreviews(subdirPath, basePath);
|
|
139
|
+
packs.push(...subPacks);
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
|
|
142
|
+
return packs;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Detect if directory is a multi-pack (has subdirs with previews)
|
|
146
|
+
function detectMultiPack(sourceDir) {
|
|
147
|
+
const packsWithPreviews = findPacksWithPreviews(sourceDir, sourceDir);
|
|
148
|
+
|
|
149
|
+
// Consider it a multi-pack if at least 2 directories have previews
|
|
137
150
|
if (packsWithPreviews.length >= 2) {
|
|
138
151
|
return packsWithPreviews;
|
|
139
152
|
}
|
package/package.json
CHANGED
|
@@ -23,29 +23,47 @@ try {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Find
|
|
26
|
+
* Find preview image in a directory
|
|
27
27
|
*/
|
|
28
|
-
function
|
|
28
|
+
function findPreview(dir) {
|
|
29
|
+
const previewNames = ['Preview.jpg', 'Preview.png', 'preview.jpg', 'preview.png',
|
|
30
|
+
'Preview.jpeg', 'preview.jpeg'];
|
|
31
|
+
for (const name of previewNames) {
|
|
32
|
+
const previewPath = path.join(dir, name);
|
|
33
|
+
if (fs.existsSync(previewPath)) {
|
|
34
|
+
return previewPath;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Recursively find all preview images in subdirectories (any depth)
|
|
42
|
+
*/
|
|
43
|
+
function findPreviews(sourceDir, basePath = sourceDir) {
|
|
29
44
|
const previews = [];
|
|
30
45
|
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
|
31
46
|
|
|
47
|
+
// Check if this directory has a preview (skip the root)
|
|
48
|
+
if (sourceDir !== basePath) {
|
|
49
|
+
const preview = findPreview(sourceDir);
|
|
50
|
+
if (preview) {
|
|
51
|
+
const relativePath = path.relative(basePath, sourceDir);
|
|
52
|
+
previews.push({
|
|
53
|
+
path: preview,
|
|
54
|
+
name: relativePath
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Recursively check subdirectories
|
|
32
60
|
for (const entry of entries) {
|
|
33
61
|
if (!entry.isDirectory()) continue;
|
|
62
|
+
if (entry.name.startsWith('.')) continue; // Skip hidden dirs
|
|
34
63
|
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
for (const name of previewNames) {
|
|
40
|
-
const previewPath = path.join(subdir, name);
|
|
41
|
-
if (fs.existsSync(previewPath)) {
|
|
42
|
-
previews.push({
|
|
43
|
-
path: previewPath,
|
|
44
|
-
name: entry.name
|
|
45
|
-
});
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
64
|
+
const subdirPath = path.join(sourceDir, entry.name);
|
|
65
|
+
const subPreviews = findPreviews(subdirPath, basePath);
|
|
66
|
+
previews.push(...subPreviews);
|
|
49
67
|
}
|
|
50
68
|
|
|
51
69
|
return previews;
|
|
@@ -182,7 +200,7 @@ async function main() {
|
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
// Export for use as module
|
|
185
|
-
module.exports = { findPreviews, combineImages, calculateGrid };
|
|
203
|
+
module.exports = { findPreview, findPreviews, combineImages, calculateGrid };
|
|
186
204
|
|
|
187
205
|
// Run if called directly
|
|
188
206
|
if (require.main === module) {
|
|
@@ -63,8 +63,17 @@ function getCategory(ext) {
|
|
|
63
63
|
return 'Other';
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// Get pack name from relative path (first directory component)
|
|
67
|
+
function getPackName(relativePath) {
|
|
68
|
+
const parts = relativePath.split('/');
|
|
69
|
+
if (parts.length > 1) {
|
|
70
|
+
return parts[0];
|
|
71
|
+
}
|
|
72
|
+
return null; // Asset is at root level
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
// Recursively scan directory for assets
|
|
67
|
-
function scanDirectory(dir, relativeTo) {
|
|
76
|
+
function scanDirectory(dir, relativeTo, rootDir = relativeTo) {
|
|
68
77
|
const assets = [];
|
|
69
78
|
|
|
70
79
|
if (!fs.existsSync(dir)) {
|
|
@@ -77,8 +86,10 @@ function scanDirectory(dir, relativeTo) {
|
|
|
77
86
|
const fullPath = path.join(dir, item.name);
|
|
78
87
|
|
|
79
88
|
if (item.isDirectory()) {
|
|
89
|
+
// Skip hidden directories
|
|
90
|
+
if (item.name.startsWith('.')) continue;
|
|
80
91
|
// Recursively scan subdirectories
|
|
81
|
-
assets.push(...scanDirectory(fullPath, relativeTo));
|
|
92
|
+
assets.push(...scanDirectory(fullPath, relativeTo, rootDir));
|
|
82
93
|
} else if (item.isFile()) {
|
|
83
94
|
const ext = path.extname(item.name).toLowerCase();
|
|
84
95
|
|
|
@@ -93,17 +104,25 @@ function scanDirectory(dir, relativeTo) {
|
|
|
93
104
|
|
|
94
105
|
if (!allExtensions.includes(ext)) continue;
|
|
95
106
|
|
|
96
|
-
const relativePath = path.relative(relativeTo, fullPath);
|
|
107
|
+
const relativePath = path.relative(relativeTo, fullPath).replace(/\\/g, '/');
|
|
97
108
|
const category = getCategory(ext);
|
|
109
|
+
const pack = getPackName(relativePath);
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
const asset = {
|
|
100
112
|
name: item.name,
|
|
101
|
-
path: `public/assets/${gameName}/${relativePath
|
|
102
|
-
relativePath: relativePath
|
|
113
|
+
path: `public/assets/${gameName}/${relativePath}`,
|
|
114
|
+
relativePath: relativePath,
|
|
103
115
|
category: category,
|
|
104
116
|
extension: ext,
|
|
105
117
|
focusGlTF: ext === '.gltf' || ext === '.glb'
|
|
106
|
-
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Add pack field if asset is in a subdirectory
|
|
121
|
+
if (pack) {
|
|
122
|
+
asset.pack = pack;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
assets.push(asset);
|
|
107
126
|
}
|
|
108
127
|
}
|
|
109
128
|
|
|
@@ -115,8 +134,18 @@ console.log(`Scanning assets in: ${assetsDir}`);
|
|
|
115
134
|
|
|
116
135
|
const assets = scanDirectory(assetsDir, assetsDir);
|
|
117
136
|
|
|
118
|
-
// Sort assets by name
|
|
119
|
-
assets.sort((a, b) =>
|
|
137
|
+
// Sort assets by pack then name
|
|
138
|
+
assets.sort((a, b) => {
|
|
139
|
+
if (a.pack && b.pack) {
|
|
140
|
+
const packCompare = a.pack.localeCompare(b.pack);
|
|
141
|
+
if (packCompare !== 0) return packCompare;
|
|
142
|
+
} else if (a.pack) {
|
|
143
|
+
return 1; // Assets with packs after root assets
|
|
144
|
+
} else if (b.pack) {
|
|
145
|
+
return -1;
|
|
146
|
+
}
|
|
147
|
+
return a.name.localeCompare(b.name);
|
|
148
|
+
});
|
|
120
149
|
|
|
121
150
|
// Count by category
|
|
122
151
|
const categoryCounts = {};
|
|
@@ -124,9 +153,19 @@ for (const asset of assets) {
|
|
|
124
153
|
categoryCounts[asset.category] = (categoryCounts[asset.category] || 0) + 1;
|
|
125
154
|
}
|
|
126
155
|
|
|
156
|
+
// Count by pack
|
|
157
|
+
const packCounts = {};
|
|
158
|
+
for (const asset of assets) {
|
|
159
|
+
const pack = asset.pack || '(root)';
|
|
160
|
+
packCounts[pack] = (packCounts[pack] || 0) + 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
127
163
|
// Count glTF assets
|
|
128
164
|
const glTFCount = assets.filter(a => a.focusGlTF).length;
|
|
129
165
|
|
|
166
|
+
// Get unique packs
|
|
167
|
+
const packs = [...new Set(assets.map(a => a.pack).filter(Boolean))];
|
|
168
|
+
|
|
130
169
|
// Build output
|
|
131
170
|
const output = {
|
|
132
171
|
metadata: {
|
|
@@ -134,7 +173,9 @@ const output = {
|
|
|
134
173
|
root: `public/assets/${gameName}`,
|
|
135
174
|
totalAssets: assets.length,
|
|
136
175
|
glTFAssetCount: glTFCount,
|
|
137
|
-
categories: categoryCounts
|
|
176
|
+
categories: categoryCounts,
|
|
177
|
+
packs: packs.length > 0 ? packs : undefined,
|
|
178
|
+
packCounts: packs.length > 0 ? packCounts : undefined
|
|
138
179
|
},
|
|
139
180
|
assets: assets
|
|
140
181
|
};
|
|
@@ -147,3 +188,6 @@ console.log(`\nSummary:`);
|
|
|
147
188
|
console.log(` Total assets: ${assets.length}`);
|
|
148
189
|
console.log(` glTF/GLB models: ${glTFCount}`);
|
|
149
190
|
console.log(` Categories:`, categoryCounts);
|
|
191
|
+
if (packs.length > 0) {
|
|
192
|
+
console.log(` Packs (${packs.length}):`, packCounts);
|
|
193
|
+
}
|