ai-behavior-trees-util…
Engine-neutral behavior tree and utility AI code for NPCs in any language: Blackboard, reactive composites, decorators and…
Loads glTF/GLB models into three.js r187 and plays their animations: GLTFLoader, DRACO, Meshopt and KTX2 decoders, AnimationMixer cross-fades and SkeletonUtils clones. Use when a model is invisible or tiny, decoder files 404, animations never play, or clones share one skeleton.
Claude Code
$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading --agent claude-code
Codex
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading -a codex -y
Cursor
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading -a cursor -y
Gemini CLI
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading -a gemini-cli -y
GitHub Copilot
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading -a github-copilot -y
OpenCode
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading -a opencode -y
Windsurf
$ npx skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-gltf-loading -a windsurf -y
name: threejs-gltf-loading description: > Loads glTF/GLB models into three.js r187 and plays their animations: GLTFLoader, DRACO, Meshopt and KTX2 decoders, AnimationMixer cross-fades and SkeletonUtils clones. Use when a model is invisible or tiny, decoder files 404, animations never play, or clones share one skeleton. Not for scene setup (`threejs-scene-setup`) or lighting (`threejs-materials-lighting`).
Loads `.gltf` / `.glb` models, including compressed geometry (DRACO, Meshopt) and textures (KTX2), and plays their animation clips. Patterns target **r187**; an existing project keeps its pinned release unless the user asks to upgrade. glTF is the runtime format to default to: OBJ has no scene graph or animation, and FBX is heavy to parse in a browser.
spawning many copies of an animated enemy, or fixing a model that does not appear.
from `three/addons/loaders/...`.
materials (`threejs-materials-lighting`), or modelling and export settings in a DCC tool.
1. **Inspect the file before writing loader code** when `gltf-transform` is available: `npx @gltf-transform/cli inspect model.glb` lists meshes, extensions (for example `KHR_draco_mesh_compression`, `EXT_meshopt_compression`, `KHR_texture_basisu`), clips and texture sizes. Each listed compression extension needs its decoder attached. 2. **Load with `GLTFLoader`** (`loadAsync` in async code). The result has `gltf.scene`, the root `Object3D`, and `gltf.animations`, an `AnimationClip[]`. 3. **Add `gltf.scene` and frame it.** glTF units are metres; log names and bounds to find the parts code will control. 4. **One `AnimationMixer` per animated root**, actions from `mixer.clipAction(clip)`, and `mixer.update(seconds)` every frame. 5. **Attach decoders** for the extensions found in step 1. Since r185 `DRACOLoader` and `KTX2Loader` find their bundled decoder files without a path. 6. **Verify** with [Verify](#verify).
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const gltf = await new GLTFLoader().loadAsync('assets/robot.glb');
const root = gltf.scene;
scene.add(root);
const box = new THREE.Box3().setFromObject(root);
console.log('size (m):', box.getSize(new THREE.Vector3()).toArray());
console.log('clips:', gltf.animations.map((c) => c.name));const timer = new THREE.Timer(); // THREE.Clock is deprecated since r183
const mixer = new THREE.AnimationMixer(gltf.scene);
const run = THREE.AnimationClip.findByName(gltf.animations, 'Run') ?? gltf.animations[0];
mixer.clipAction(run).play();
renderer.setAnimationLoop(() => {
timer.update();
mixer.update(timer.getDelta()); // seconds, not milliseconds
renderer.render(scene, camera);
});const actions = Object.fromEntries(gltf.animations.map((c) => [c.name, mixer.clipAction(c)]));
let current = actions.Idle;
current.play();
function transitionTo(name, duration = 0.3) {
const next = actions[name];
if (!next || next === current) return;
next.reset().play(); // rewind and clear an old fade
current.crossFadeTo(next, duration, false);
current = next;
}import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
const loader = new GLTFLoader()
.setDRACOLoader(new DRACOLoader().setDecoderPath(DRACO_GLTF_CONFIG)) // glTF-only WASM build (r185+)
.setKTX2Loader(new KTX2Loader().detectSupport(renderer)) // needs the renderer
.setMeshoptDecoder(MeshoptDecoder);
// Share this one loader for every model so decoders and workers are created once.On releases before r185, or when the host does not serve the bundled files, copy `examples/jsm/libs/draco/` and `libs/basis/` from the installed `three` and pass those folders to `setDecoderPath` / `setTranscoderPath`.
const wheels = [];
gltf.scene.traverse((node) => {
if (node.name.startsWith('Wheel')) wheels.push(node);
});
// In the loop: for (const w of wheels) w.rotation.x += dt * 4;# Meshopt compression, WebP textures at most 1024 px, keep named nodes and the hierarchy. npx @gltf-transform/cli optimize in.glb out.glb \ --compress meshopt --texture-compress webp --texture-size 1024 \ --flatten false --join-named false npx @gltf-transform/cli validate out.glb
`optimize` flattens the hierarchy and joins meshes, named ones included, by default. That cuts draw calls, but code that looks up `Wheel_FL` or animates a door node then finds nothing. Keep the two flags above for any model that code reaches into. The output needs `setMeshoptDecoder` (Pattern 4).
1. **File check**: `npx @gltf-transform/cli validate model.glb` for each new or changed file; fix reported errors before debugging loader code. 2. **Build**: `npm run build` (or `npx tsc --noEmit`) passes. 3. **Runtime probe**: expose `scene` and the mixer on `window` (`Object.assign(window, { scene, mixer })`). Pass this expression to Playwright's `page.evaluate` as a string, so it runs in the page and not in Node (the smoke script in `threejs-scene-setup` works), or ask the user to paste it into the browser console with `await` in front and report the output:
(async () =>
<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.
Engine-neutral behavior tree and utility AI code for NPCs in any language: Blackboard, reactive composites, decorators and…
Engine-neutral game audio technique: bus layout, volume sliders in decibels, loudness, ducking music under dialogue, adaptive…
Builds and tunes game cameras, engine-neutral with Godot and Cinemachine 3 mappings: 2D follow with deadzone and look-ahead,…
Plans, generates or sources, and validates consistent game art: style guides, sprites, pixel art, tilesets, icons and textures,…
Builds engine-neutral branching dialogue with Ink, Yarn Spinner or a small JSON graph runner: lines, choices, conditions,…
Designs engine-neutral enemy and NPC AI: picks FSM, behavior tree, utility or GOAP, and builds perception, steering and A*…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.