Skip to content
Development
Skill

/threejs-scene-setup

Stand up a three.js scene: import maps and the three/addons path, the Scene/PerspectiveCamera/WebGLRenderer trio, the setAnimationLoop render loop, responsive resize, OrbitControls, and opting into WebGPURenderer. Use when starting or debugging a three.js app — when the user

From plugin
awesome-gamedev-agent-skills
6.3k77 skills

Claude Code

$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill threejs-scene-setup --agent claude-code

SKILL.md

threejs-scene-setup.SKILL.md
name: threejs-scene-setup
description: >
  Sets up and debugs a plain three.js r187 game or 3D scene: import maps, Scene, PerspectiveCamera
  and WebGLRenderer, setAnimationLoop with Timer, resize, OrbitControls and WebGPURenderer. Use on
  "Failed to resolve module specifier three", a black canvas or nothing animating. Not for GLB
  loading (`threejs-gltf-loading`), materials (`threejs-materials-lighting`) or React Three Fiber.

three.js scene setup

Builds the base every three.js game needs: module loading, the scene, camera and renderer, one render loop, resize handling, and a resolution budget. Patterns target **r187**. Read the installed `three` version first; addons, defaults and deprecations move every release.

When to use

  • Bootstrapping a three.js scene, fixing a black or blank canvas, making the canvas

responsive, setting up the loop, adding `OrbitControls`, or choosing between `WebGLRenderer` and `WebGPURenderer`.

  • `package.json` depends on `three`, or code does `import * as THREE from 'three'`.
  • Not for models and animation (`threejs-gltf-loading`), surface look, lights and shadows

(`threejs-materials-lighting`), or React Three Fiber (`@react-three/fiber`) components, whose `<Canvas>` owns the renderer and loop. For 2D, use `pixijs-rendering` or Phaser.

Workflow

1. **Read `three` from `package.json` or the import map.** An existing project keeps its release unless the user asks to upgrade. 2. **Resolve `three` and `three/addons/`** with a bundler or an import map; addons live under `three/addons/...`. 3. **Create the scene, camera and renderer**, with the camera moved back from the origin and the pixel ratio capped (Pattern 2). 4. **Prove the pipeline with an unlit mesh first** (`MeshNormalMaterial`). Only then switch to lit materials and add lights. 5. **Drive everything from `renderer.setAnimationLoop`** with a `Timer` and a clamped delta. Keep game state in plain objects and copy it onto meshes each frame. Meshes then stay a view, and state can be saved and tested without a GPU. 6. **Handle resize** (aspect, projection matrix, renderer size) and add `OrbitControls` while developing. 7. **Verify** with [Verify](#verify).

Patterns

1. Import map (no bundler)

<canvas id="c"></canvas>
<script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@0.187.0/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.187.0/examples/jsm/"
  }
}
</script>
<script type="module" src="./main.js"></script>

With Vite or webpack, `npm i three` and the same imports resolve without a map.

2. Scene, camera and renderer

import * as THREE from 'three';

const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); // starting budget; profile on target hardware
renderer.setSize(window.innerWidth, window.innerHeight);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x101018);

const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(3, 2, 5);
camera.lookAt(0, 0, 0);

const cube = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial());
scene.add(cube);

Object.assign(window, { renderer, scene }); // for the smoke check and console debugging

3. Render loop with Timer

const timer = new THREE.Timer();        // replaces the deprecated THREE.Clock (r183+)
timer.connect(document);                // optional: no huge delta after a hidden tab

renderer.setAnimationLoop(() => {
  timer.update();                      // use performance.now(), like visibility reset
  const dt = Math.min(Math.max(timer.getDelta(), 0), 0.1); // seconds; bound stalls and negative deltas
  cube.rotation.x += dt;                // frame-rate independent
  cube.rotation.y += dt * 0.7;
  renderer.render(scene, camera);
});
// renderer.setAnimationLoop(null); // stop the loop
// timer.dispose();                 // remove visibility listener when tearing down

Call `update()` with no argument when the timer is connected. Visibility reset samples `performance.now()`, so passing the loop's timestamp can produce a negative delta right after a hidden tab returns. The clamp also protects a fixed-step accumulator; the 0.1 s upper bound drops time after a stall on purpose, so tune it to the game's catch-up policy.

4. Resize

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();      // required after changing aspect
  renderer.setSize(window.innerWidth, window.innerHeight);
});

5. OrbitControls

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
// In the loop, before render: controls.update(); (required every frame with damping)

6. WebGPURenderer, only when needed

Stay on `WebGLRenderer` by default. Switch when the project needs TSL node materials or compute shaders; `WebGPURenderer` falls back to a WebGL 2 backend where WebGPU is missing.

// Official WebGPU examples map both "three" and "three/webgpu" to
// build/three.webgpu.js, plus "three/tsl" to build/three.tsl.js.
import * as THREE from 'three/webgpu';

const renderer = new THREE.WebGPURenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); // starting budget; profile on target hardware
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
await renderer.init();                  // required before direct render()/compute()

renderer.setAnimationLoop(() => renderer.render(scene, camera));

Scenes, cameras, built-in `Mesh*Material`s and most addons carry over. Custom GLSL (`Shader

Read more
@gamedev-skills
Ships withawesome-gamedev-agent-skills

<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.

Stars on GitHub

Momentum

stars in total
6.3k
Star history of gamedev-skills/awesome-gamedev-agent-skills on GitHub, a chart by star-history.com

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about threejs-scene-setup.

Which coding agents can use threejs-scene-setup?
threejs-scene-setup installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.