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 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/ # Asset pack 1 (with Preview.jpg)
21
- ├── buildings/ # Asset pack 2 (with Preview.jpg)
22
- └── environment/ # Asset pack 3 (with Preview.jpg)
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 auto-detects this structure and combines all preview images into a single grid.
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
- // Detect if directory is a multi-pack (has subdirs with previews)
116
- function detectMultiPack(sourceDir) {
117
- const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
118
- const subdirs = entries.filter(e => e.isDirectory());
119
-
120
- if (subdirs.length === 0) return null;
121
-
122
- const packsWithPreviews = [];
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
- for (const subdir of subdirs) {
125
- const subdirPath = path.join(sourceDir, subdir.name);
126
- const preview = findPreview(subdirPath);
127
- if (preview) {
128
- packsWithPreviews.push({
129
- name: subdir.name,
130
- path: subdirPath,
131
- preview: preview
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
- // Consider it a multi-pack if at least 2 subdirs have previews
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-threejs-game",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Scaffold a Three.js game project with AI-assisted design documents",
5
5
  "bin": {
6
6
  "create-threejs-game": "./bin/cli.js"
@@ -23,29 +23,47 @@ try {
23
23
  }
24
24
 
25
25
  /**
26
- * Find all preview images in subdirectories
26
+ * Find preview image in a directory
27
27
  */
28
- function findPreviews(sourceDir) {
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 subdir = path.join(sourceDir, entry.name);
36
- const previewNames = ['Preview.jpg', 'Preview.png', 'preview.jpg', 'preview.png',
37
- 'Preview.jpeg', 'preview.jpeg'];
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
- assets.push({
111
+ const asset = {
100
112
  name: item.name,
101
- path: `public/assets/${gameName}/${relativePath.replace(/\\/g, '/')}`,
102
- relativePath: relativePath.replace(/\\/g, '/'),
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) => a.name.localeCompare(b.name));
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
+ }