create-threejs-game 1.0.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.
Files changed (39) hide show
  1. package/README.md +97 -0
  2. package/bin/cli.js +370 -0
  3. package/package.json +29 -0
  4. package/template/.claude/skills/threejs-animation/SKILL.md +552 -0
  5. package/template/.claude/skills/threejs-fundamentals/SKILL.md +488 -0
  6. package/template/.claude/skills/threejs-geometry/SKILL.md +548 -0
  7. package/template/.claude/skills/threejs-interaction/SKILL.md +660 -0
  8. package/template/.claude/skills/threejs-lighting/SKILL.md +481 -0
  9. package/template/.claude/skills/threejs-loaders/SKILL.md +623 -0
  10. package/template/.claude/skills/threejs-materials/SKILL.md +520 -0
  11. package/template/.claude/skills/threejs-postprocessing/SKILL.md +602 -0
  12. package/template/.claude/skills/threejs-shaders/SKILL.md +642 -0
  13. package/template/.claude/skills/threejs-textures/SKILL.md +628 -0
  14. package/template/.codex/skills/threejs-animation/SKILL.md +552 -0
  15. package/template/.codex/skills/threejs-fundamentals/SKILL.md +488 -0
  16. package/template/.codex/skills/threejs-geometry/SKILL.md +548 -0
  17. package/template/.codex/skills/threejs-interaction/SKILL.md +660 -0
  18. package/template/.codex/skills/threejs-lighting/SKILL.md +481 -0
  19. package/template/.codex/skills/threejs-loaders/SKILL.md +623 -0
  20. package/template/.codex/skills/threejs-materials/SKILL.md +520 -0
  21. package/template/.codex/skills/threejs-postprocessing/SKILL.md +602 -0
  22. package/template/.codex/skills/threejs-shaders/SKILL.md +642 -0
  23. package/template/.codex/skills/threejs-textures/SKILL.md +628 -0
  24. package/template/README.md +352 -0
  25. package/template/docs/.gitkeep +0 -0
  26. package/template/plans/.gitkeep +0 -0
  27. package/template/prompts/01-mockup-generation.md +44 -0
  28. package/template/prompts/02-prd-generation.md +119 -0
  29. package/template/prompts/03-tdd-generation.md +127 -0
  30. package/template/prompts/04-execution-plan.md +89 -0
  31. package/template/prompts/05-implementation.md +61 -0
  32. package/template/public/assets/.gitkeep +0 -0
  33. package/template/scripts/config.example.json +12 -0
  34. package/template/scripts/generate-assets-json.js +149 -0
  35. package/template/scripts/generate-mockup.js +197 -0
  36. package/template/scripts/generate-plan.js +295 -0
  37. package/template/scripts/generate-prd.js +297 -0
  38. package/template/scripts/generate-tdd.js +283 -0
  39. package/template/scripts/pipeline.js +229 -0
@@ -0,0 +1,552 @@
1
+ ---
2
+ name: threejs-animation
3
+ description: Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations.
4
+ ---
5
+
6
+ # Three.js Animation
7
+
8
+ ## Quick Start
9
+
10
+ ```javascript
11
+ import * as THREE from "three";
12
+
13
+ // Simple procedural animation
14
+ const clock = new THREE.Clock();
15
+
16
+ function animate() {
17
+ const delta = clock.getDelta();
18
+ const elapsed = clock.getElapsedTime();
19
+
20
+ mesh.rotation.y += delta;
21
+ mesh.position.y = Math.sin(elapsed) * 0.5;
22
+
23
+ requestAnimationFrame(animate);
24
+ renderer.render(scene, camera);
25
+ }
26
+ animate();
27
+ ```
28
+
29
+ ## Animation System Overview
30
+
31
+ Three.js animation system has three main components:
32
+
33
+ 1. **AnimationClip** - Container for keyframe data
34
+ 2. **AnimationMixer** - Plays animations on a root object
35
+ 3. **AnimationAction** - Controls playback of a clip
36
+
37
+ ## AnimationClip
38
+
39
+ Stores keyframe animation data.
40
+
41
+ ```javascript
42
+ // Create animation clip
43
+ const times = [0, 1, 2]; // Keyframe times (seconds)
44
+ const values = [0, 1, 0]; // Values at each keyframe
45
+
46
+ const track = new THREE.NumberKeyframeTrack(
47
+ ".position[y]", // Property path
48
+ times,
49
+ values,
50
+ );
51
+
52
+ const clip = new THREE.AnimationClip("bounce", 2, [track]);
53
+ ```
54
+
55
+ ### KeyframeTrack Types
56
+
57
+ ```javascript
58
+ // Number track (single value)
59
+ new THREE.NumberKeyframeTrack(".opacity", times, [1, 0]);
60
+ new THREE.NumberKeyframeTrack(".material.opacity", times, [1, 0]);
61
+
62
+ // Vector track (position, scale)
63
+ new THREE.VectorKeyframeTrack(".position", times, [
64
+ 0,
65
+ 0,
66
+ 0, // t=0
67
+ 1,
68
+ 2,
69
+ 0, // t=1
70
+ 0,
71
+ 0,
72
+ 0, // t=2
73
+ ]);
74
+
75
+ // Quaternion track (rotation)
76
+ const q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0));
77
+ const q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0));
78
+ new THREE.QuaternionKeyframeTrack(
79
+ ".quaternion",
80
+ [0, 1],
81
+ [q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w],
82
+ );
83
+
84
+ // Color track
85
+ new THREE.ColorKeyframeTrack(".material.color", times, [
86
+ 1,
87
+ 0,
88
+ 0, // red
89
+ 0,
90
+ 1,
91
+ 0, // green
92
+ 0,
93
+ 0,
94
+ 1, // blue
95
+ ]);
96
+
97
+ // Boolean track
98
+ new THREE.BooleanKeyframeTrack(".visible", [0, 0.5, 1], [true, false, true]);
99
+
100
+ // String track (for morph targets)
101
+ new THREE.StringKeyframeTrack(
102
+ ".morphTargetInfluences[smile]",
103
+ [0, 1],
104
+ ["0", "1"],
105
+ );
106
+ ```
107
+
108
+ ### Interpolation Modes
109
+
110
+ ```javascript
111
+ const track = new THREE.VectorKeyframeTrack(".position", times, values);
112
+
113
+ // Interpolation
114
+ track.setInterpolation(THREE.InterpolateLinear); // Default
115
+ track.setInterpolation(THREE.InterpolateSmooth); // Cubic spline
116
+ track.setInterpolation(THREE.InterpolateDiscrete); // Step function
117
+ ```
118
+
119
+ ## AnimationMixer
120
+
121
+ Plays animations on an object and its descendants.
122
+
123
+ ```javascript
124
+ const mixer = new THREE.AnimationMixer(model);
125
+
126
+ // Create action from clip
127
+ const action = mixer.clipAction(clip);
128
+ action.play();
129
+
130
+ // Update in animation loop
131
+ function animate() {
132
+ const delta = clock.getDelta();
133
+ mixer.update(delta); // Required!
134
+
135
+ requestAnimationFrame(animate);
136
+ renderer.render(scene, camera);
137
+ }
138
+ ```
139
+
140
+ ### Mixer Events
141
+
142
+ ```javascript
143
+ mixer.addEventListener("finished", (e) => {
144
+ console.log("Animation finished:", e.action.getClip().name);
145
+ });
146
+
147
+ mixer.addEventListener("loop", (e) => {
148
+ console.log("Animation looped:", e.action.getClip().name);
149
+ });
150
+ ```
151
+
152
+ ## AnimationAction
153
+
154
+ Controls playback of an animation clip.
155
+
156
+ ```javascript
157
+ const action = mixer.clipAction(clip);
158
+
159
+ // Playback control
160
+ action.play();
161
+ action.stop();
162
+ action.reset();
163
+ action.halt(fadeOutDuration);
164
+
165
+ // Playback state
166
+ action.isRunning();
167
+ action.isScheduled();
168
+
169
+ // Time control
170
+ action.time = 0.5; // Current time
171
+ action.timeScale = 1; // Playback speed (negative = reverse)
172
+ action.paused = false;
173
+
174
+ // Weight (for blending)
175
+ action.weight = 1; // 0-1, contribution to final pose
176
+ action.setEffectiveWeight(1);
177
+
178
+ // Loop modes
179
+ action.loop = THREE.LoopRepeat; // Default: loop forever
180
+ action.loop = THREE.LoopOnce; // Play once and stop
181
+ action.loop = THREE.LoopPingPong; // Alternate forward/backward
182
+ action.repetitions = 3; // Number of loops (Infinity default)
183
+
184
+ // Clamping
185
+ action.clampWhenFinished = true; // Hold last frame when done
186
+
187
+ // Blending
188
+ action.blendMode = THREE.NormalAnimationBlendMode;
189
+ action.blendMode = THREE.AdditiveAnimationBlendMode;
190
+ ```
191
+
192
+ ### Fade In/Out
193
+
194
+ ```javascript
195
+ // Fade in
196
+ action.reset().fadeIn(0.5).play();
197
+
198
+ // Fade out
199
+ action.fadeOut(0.5);
200
+
201
+ // Crossfade between animations
202
+ const action1 = mixer.clipAction(clip1);
203
+ const action2 = mixer.clipAction(clip2);
204
+
205
+ action1.play();
206
+
207
+ // Later, crossfade to action2
208
+ action1.crossFadeTo(action2, 0.5, true);
209
+ action2.play();
210
+ ```
211
+
212
+ ## Loading GLTF Animations
213
+
214
+ Most common source of skeletal animations.
215
+
216
+ ```javascript
217
+ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
218
+
219
+ const loader = new GLTFLoader();
220
+ loader.load("model.glb", (gltf) => {
221
+ const model = gltf.scene;
222
+ scene.add(model);
223
+
224
+ // Create mixer
225
+ const mixer = new THREE.AnimationMixer(model);
226
+
227
+ // Get all clips
228
+ const clips = gltf.animations;
229
+ console.log(
230
+ "Available animations:",
231
+ clips.map((c) => c.name),
232
+ );
233
+
234
+ // Play first animation
235
+ if (clips.length > 0) {
236
+ const action = mixer.clipAction(clips[0]);
237
+ action.play();
238
+ }
239
+
240
+ // Play specific animation by name
241
+ const walkClip = THREE.AnimationClip.findByName(clips, "Walk");
242
+ if (walkClip) {
243
+ mixer.clipAction(walkClip).play();
244
+ }
245
+
246
+ // Store mixer for update loop
247
+ window.mixer = mixer;
248
+ });
249
+
250
+ // Animation loop
251
+ function animate() {
252
+ const delta = clock.getDelta();
253
+ if (window.mixer) window.mixer.update(delta);
254
+
255
+ requestAnimationFrame(animate);
256
+ renderer.render(scene, camera);
257
+ }
258
+ ```
259
+
260
+ ## Skeletal Animation
261
+
262
+ ### Skeleton and Bones
263
+
264
+ ```javascript
265
+ // Access skeleton from skinned mesh
266
+ const skinnedMesh = model.getObjectByProperty("type", "SkinnedMesh");
267
+ const skeleton = skinnedMesh.skeleton;
268
+
269
+ // Access bones
270
+ skeleton.bones.forEach((bone) => {
271
+ console.log(bone.name, bone.position, bone.rotation);
272
+ });
273
+
274
+ // Find specific bone by name
275
+ const headBone = skeleton.bones.find((b) => b.name === "Head");
276
+ if (headBone) headBone.rotation.y = Math.PI / 4; // Turn head
277
+
278
+ // Skeleton helper
279
+ const helper = new THREE.SkeletonHelper(model);
280
+ scene.add(helper);
281
+ ```
282
+
283
+ ### Programmatic Bone Animation
284
+
285
+ ```javascript
286
+ function animate() {
287
+ const time = clock.getElapsedTime();
288
+
289
+ // Animate bone
290
+ const headBone = skeleton.bones.find((b) => b.name === "Head");
291
+ if (headBone) {
292
+ headBone.rotation.y = Math.sin(time) * 0.3;
293
+ }
294
+
295
+ // Update mixer if also playing clips
296
+ mixer.update(clock.getDelta());
297
+ }
298
+ ```
299
+
300
+ ### Bone Attachments
301
+
302
+ ```javascript
303
+ // Attach object to bone
304
+ const weapon = new THREE.Mesh(weaponGeometry, weaponMaterial);
305
+ const handBone = skeleton.bones.find((b) => b.name === "RightHand");
306
+ if (handBone) handBone.add(weapon);
307
+
308
+ // Offset attachment
309
+ weapon.position.set(0, 0, 0.5);
310
+ weapon.rotation.set(0, Math.PI / 2, 0);
311
+ ```
312
+
313
+ ## Morph Targets
314
+
315
+ Blend between different mesh shapes.
316
+
317
+ ```javascript
318
+ // Morph targets are stored in geometry
319
+ const geometry = mesh.geometry;
320
+ console.log("Morph attributes:", Object.keys(geometry.morphAttributes));
321
+
322
+ // Access morph target influences
323
+ mesh.morphTargetInfluences; // Array of weights
324
+ mesh.morphTargetDictionary; // Name -> index mapping
325
+
326
+ // Set morph target by index
327
+ mesh.morphTargetInfluences[0] = 0.5;
328
+
329
+ // Set by name
330
+ const smileIndex = mesh.morphTargetDictionary["smile"];
331
+ mesh.morphTargetInfluences[smileIndex] = 1;
332
+ ```
333
+
334
+ ### Animating Morph Targets
335
+
336
+ ```javascript
337
+ // Procedural
338
+ function animate() {
339
+ const t = clock.getElapsedTime();
340
+ mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) / 2;
341
+ }
342
+
343
+ // With keyframe animation
344
+ const track = new THREE.NumberKeyframeTrack(
345
+ ".morphTargetInfluences[smile]",
346
+ [0, 0.5, 1],
347
+ [0, 1, 0],
348
+ );
349
+ const clip = new THREE.AnimationClip("smile", 1, [track]);
350
+ mixer.clipAction(clip).play();
351
+ ```
352
+
353
+ ## Animation Blending
354
+
355
+ Mix multiple animations together.
356
+
357
+ ```javascript
358
+ // Setup actions
359
+ const idleAction = mixer.clipAction(idleClip);
360
+ const walkAction = mixer.clipAction(walkClip);
361
+ const runAction = mixer.clipAction(runClip);
362
+
363
+ // Play all with different weights
364
+ idleAction.play();
365
+ walkAction.play();
366
+ runAction.play();
367
+
368
+ // Set initial weights
369
+ idleAction.setEffectiveWeight(1);
370
+ walkAction.setEffectiveWeight(0);
371
+ runAction.setEffectiveWeight(0);
372
+
373
+ // Blend based on speed
374
+ function updateAnimations(speed) {
375
+ if (speed < 0.1) {
376
+ idleAction.setEffectiveWeight(1);
377
+ walkAction.setEffectiveWeight(0);
378
+ runAction.setEffectiveWeight(0);
379
+ } else if (speed < 5) {
380
+ const t = speed / 5;
381
+ idleAction.setEffectiveWeight(1 - t);
382
+ walkAction.setEffectiveWeight(t);
383
+ runAction.setEffectiveWeight(0);
384
+ } else {
385
+ const t = Math.min((speed - 5) / 5, 1);
386
+ idleAction.setEffectiveWeight(0);
387
+ walkAction.setEffectiveWeight(1 - t);
388
+ runAction.setEffectiveWeight(t);
389
+ }
390
+ }
391
+ ```
392
+
393
+ ### Additive Blending
394
+
395
+ ```javascript
396
+ // Base pose
397
+ const baseAction = mixer.clipAction(baseClip);
398
+ baseAction.play();
399
+
400
+ // Additive layer (e.g., breathing)
401
+ const additiveAction = mixer.clipAction(additiveClip);
402
+ additiveAction.blendMode = THREE.AdditiveAnimationBlendMode;
403
+ additiveAction.play();
404
+
405
+ // Convert clip to additive
406
+ THREE.AnimationUtils.makeClipAdditive(additiveClip);
407
+ ```
408
+
409
+ ## Animation Utilities
410
+
411
+ ```javascript
412
+ import * as THREE from "three";
413
+
414
+ // Find clip by name
415
+ const clip = THREE.AnimationClip.findByName(clips, "Walk");
416
+
417
+ // Create subclip
418
+ const subclip = THREE.AnimationUtils.subclip(clip, "subclip", 0, 30, 30);
419
+
420
+ // Convert to additive
421
+ THREE.AnimationUtils.makeClipAdditive(clip);
422
+ THREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);
423
+
424
+ // Clone clip
425
+ const clone = clip.clone();
426
+
427
+ // Get clip duration
428
+ clip.duration;
429
+
430
+ // Optimize clip (remove redundant keyframes)
431
+ clip.optimize();
432
+
433
+ // Reset clip to first frame
434
+ clip.resetDuration();
435
+ ```
436
+
437
+ ## Procedural Animation Patterns
438
+
439
+ ### Smooth Damping
440
+
441
+ ```javascript
442
+ // Smooth follow/lerp
443
+ const target = new THREE.Vector3();
444
+ const current = new THREE.Vector3();
445
+ const velocity = new THREE.Vector3();
446
+
447
+ function smoothDamp(current, target, velocity, smoothTime, deltaTime) {
448
+ const omega = 2 / smoothTime;
449
+ const x = omega * deltaTime;
450
+ const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
451
+ const change = current.clone().sub(target);
452
+ const temp = velocity
453
+ .clone()
454
+ .add(change.clone().multiplyScalar(omega))
455
+ .multiplyScalar(deltaTime);
456
+ velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp);
457
+ return target.clone().add(change.add(temp).multiplyScalar(exp));
458
+ }
459
+
460
+ function animate() {
461
+ current.copy(smoothDamp(current, target, velocity, 0.3, delta));
462
+ mesh.position.copy(current);
463
+ }
464
+ ```
465
+
466
+ ### Spring Physics
467
+
468
+ ```javascript
469
+ class Spring {
470
+ constructor(stiffness = 100, damping = 10) {
471
+ this.stiffness = stiffness;
472
+ this.damping = damping;
473
+ this.position = 0;
474
+ this.velocity = 0;
475
+ this.target = 0;
476
+ }
477
+
478
+ update(dt) {
479
+ const force = -this.stiffness * (this.position - this.target);
480
+ const dampingForce = -this.damping * this.velocity;
481
+ this.velocity += (force + dampingForce) * dt;
482
+ this.position += this.velocity * dt;
483
+ return this.position;
484
+ }
485
+ }
486
+
487
+ const spring = new Spring(100, 10);
488
+ spring.target = 1;
489
+
490
+ function animate() {
491
+ mesh.position.y = spring.update(delta);
492
+ }
493
+ ```
494
+
495
+ ### Oscillation
496
+
497
+ ```javascript
498
+ function animate() {
499
+ const t = clock.getElapsedTime();
500
+
501
+ // Sine wave
502
+ mesh.position.y = Math.sin(t * 2) * 0.5;
503
+
504
+ // Bouncing
505
+ mesh.position.y = Math.abs(Math.sin(t * 3)) * 2;
506
+
507
+ // Circular motion
508
+ mesh.position.x = Math.cos(t) * 2;
509
+ mesh.position.z = Math.sin(t) * 2;
510
+
511
+ // Figure 8
512
+ mesh.position.x = Math.sin(t) * 2;
513
+ mesh.position.z = Math.sin(t * 2) * 1;
514
+ }
515
+ ```
516
+
517
+ ## Performance Tips
518
+
519
+ 1. **Share clips**: Same AnimationClip can be used on multiple mixers
520
+ 2. **Optimize clips**: Call `clip.optimize()` to remove redundant keyframes
521
+ 3. **Disable when off-screen**: Stop mixer updates for invisible objects
522
+ 4. **Use LOD for animations**: Simpler rigs for distant characters
523
+ 5. **Limit active mixers**: Each mixer.update() has a cost
524
+
525
+ ```javascript
526
+ // Pause animation when not visible
527
+ mesh.onBeforeRender = () => {
528
+ action.paused = false;
529
+ };
530
+
531
+ mesh.onAfterRender = () => {
532
+ // Check if will be visible next frame
533
+ if (!isInFrustum(mesh)) {
534
+ action.paused = true;
535
+ }
536
+ };
537
+
538
+ // Cache clips
539
+ const clipCache = new Map();
540
+ function getClip(name) {
541
+ if (!clipCache.has(name)) {
542
+ clipCache.set(name, loadClip(name));
543
+ }
544
+ return clipCache.get(name);
545
+ }
546
+ ```
547
+
548
+ ## See Also
549
+
550
+ - `threejs-loaders` - Loading animated GLTF models
551
+ - `threejs-fundamentals` - Clock and animation loop
552
+ - `threejs-shaders` - Vertex animation in shaders