ENGINE GUIDE

Use ImgSearch game assets in Three.js

Load low-poly GLB props with GLTFLoader and play OGG or WAV sound effects with THREE.Audio or Howler.js. The models are flat-shaded, in metres and sit on the origin, so they drop straight into a Three.js scene.

What’s in a model download

Each model downloads as a ZIP with the same prop in glb/, fbx/ and obj/ folders. For Three.js, use the GLB: one mesh with one material, the kit’s palette textures packed inside, and nothing else to load.

  • Metres, +Y up. A 0.6 m crate is 0.6 units tall, so props from different kits share one scale.
  • Base at the origin. The lowest point sits on y = 0 with the footprint centred, so position.y = 0 puts a prop on the ground.
  • Flat shading is baked in. The faces already have flat normals, so you don’t need flatShading: true.
  • Glowing parts such as lamp glass use an emissive texture, which GLTFLoader reads. Add a bloom pass if you want them to bloom.

Load a model with an import map

This page loads three r170 from a CDN, no build step needed. Put the GLB in a models/ folder next to it. Browsers block loading files from a file:// page, so serve the folder with a local web server, for example python3 -m http.server.

index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Prop viewer</title>
  <style>body { margin: 0; }</style>
  <script type="importmap">
    {
      "imports": {
        "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
        "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
      }
    }
  </script>
</head>
<body>
  <script type="module">
    import * as THREE from 'three';
    import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0xdfe8e4);
    scene.add(new THREE.HemisphereLight(0xffffff, 0x667766, 2));
    const sun = new THREE.DirectionalLight(0xffffff, 2.5);
    sun.position.set(3, 6, 4);
    scene.add(sun);

    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.05, 100);
    camera.position.set(1.6, 1.2, 2.2);
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.target.set(0, 0.3, 0);
    controls.update();

    // Metres, +Y up, base at the origin: the crate stands on y = 0.
    new GLTFLoader().load('models/wooden-crate.glb', (gltf) => {
      scene.add(gltf.scene);
    });

    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });
    renderer.setAnimationLoop(() => renderer.render(scene, camera));
  </script>
</body>
</html>

With a bundler

Install three with npm and import GLTFLoader from three/addons. With Vite, files in public/ are served from the site root, so public/models/wooden-crate.glb loads as /models/wooden-crate.glb. Load each GLB once and clone it for every copy you place: clones share the geometry and the palette material.

props.js
// npm install three@0.170.0
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();

export function placeProps(scene) {
  loader.load('/models/wooden-crate.glb', (gltf) => {
    const crate = gltf.scene;
    crate.traverse((child) => {
      if (child.isMesh) {
        child.castShadow = true;
        child.receiveShadow = true;
      }
    });

    // Clones share the geometry and the palette material, so many copies stay cheap.
    for (let i = 0; i < 5; i++) {
      const copy = crate.clone();
      copy.position.set(i * 0.9, 0, 0);
      copy.rotation.y = Math.random() * Math.PI * 2;
      scene.add(copy);
    }
  });
}

Shadows also need renderer.shadowMap.enabled = true and a light with castShadow = true.

Play sounds

Every sound pack has WAV masters and OGG copies. THREE.Audio plays them through the Web Audio API; use THREE.PositionalAudio instead, added to a prop, for sound that comes from a place in the scene.

audio.js
const listener = new THREE.AudioListener();
camera.add(listener);

const coin = new THREE.Audio(listener);
new THREE.AudioLoader().load('sounds/coin.ogg', (buffer) => {
  coin.setBuffer(buffer);
  coin.setVolume(0.8);
});

// Browsers keep audio suspended until the player clicks, taps or presses a key.
window.addEventListener('pointerdown', () => {
  listener.context.resume();
  if (coin.buffer) {
    if (coin.isPlaying) {
      coin.stop();
    }
    coin.play();
  }
});

Some Safari versions can’t play OGG. If you don’t want to handle that yourself, Howler.js takes both files and plays the first one the browser supports:

howler.js
// npm install howler
import { Howl } from 'howler';

// Howler plays the first format the browser supports.
const coin = new Howl({ src: ['sounds/coin.ogg', 'sounds/coin.wav'], volume: 0.8 });

document.querySelector('#collect').addEventListener('click', () => coin.play());
All game assets