Skip to main content
A player waits for every byte that loads before the first playable frame. Aim to get that download under 50 MiB without making models, textures or sounds worse.

Before you start

  • A build that runs locally, and a browser with developer tools.
  • Node.js, for npx.
  • KTX-Software’s ktx tool on your path, if you compress textures to KTX2.

Measure what loads first

Open the game with the browser’s network panel open and the cache disabled. Note the bytes transferred when the first playable frame appears, then compare them with these sizes. The playtest run is the test browser OpenGame opens your game in when you submit it. Players stop waiting long before a build gets near the upload limits.

Compress models

Ship GLB, compressed. One way is glTF Transform:
--texture-compress ktx2 needs the ktx tool. Three.js reads the result with GLTFLoader, MeshoptDecoder and KTX2Loader. The KTX2 transcoder is fetched while the game runs. Copy it into your build output so the game can load it from its own files:
Vite copies public/ into the output. With another bundler, use the folder it copies through unchanged.
loaders.js
Before you compress:
  • Merge static meshes that share a material, to cut draw calls.
  • Budget triangles by how close the camera gets. A crate in the background does not need a hero character’s mesh.
  • Strip unused animation clips and morph targets.

Compress textures

Texture memory use can make a game stutter even when the files are small. A 4096 by 4096 PNG takes 64 MiB on the GPU however small the file is, and about 85 MiB once mipmaps are generated, which most engines do by default.
  • Use KTX2 (.ktx2): UASTC for base colour and normal maps, where artefacts show, and ETC1S for everything else.
  • Size each texture to how large it appears on screen. Most props never need more than 1024.
  • Load each texture URL once and share the result.
The image check cannot read .heic, .heif, .jxl, .psd. A build that contains one fails until you convert it. KTX1 (.ktx) is supported for BC1 to BC5, BC7, ASTC LDR and plain pixels. Convert other KTX1 formats to KTX2 or a common web format.

Compress audio

  • Stream music with an <audio> element or a media source. Decoding a whole track into memory costs several times its file size.
  • Encode short effects as Opus or AAC at the lowest bitrate where you cannot hear a difference, usually 96 to 128 kbps.
  • Delete sounds that no code plays. They are the cheapest bytes to cut.

Load the rest after the first frame

Load the menu, the first level and the player, call og.ready(), then fetch everything else while the player is busy:
main.js
Without og.ready(), time to playable is estimated from when the frame stops changing, which counts a loading screen against you.

Pack small files

We scan and hash each file on its own. Thousands of tiny JSON or sprite files are slower to check and slower to load. Pack them into one file, such as a sprite atlas or one JSON document, and split it in the game.

Test the smaller build

Measure the new build the same way, and check it before you deploy a new preview.
  1. Rebuild the game.
  2. Repeat the network measurement with the cache disabled, and compare the bytes before the first playable frame with your first number.
  3. Play through and confirm that models, textures and sounds still look and sound right.
  4. Run the local check:

Fix a problem

The image check cannot read .heic, .heif, .jxl, .psd. Convert the file to KTX2 or a common web format, update the path that loads it, and run opengame check . --build again.
The transcoder or the model is missing from the upload, or its path does not match. Check that basis/ is in your build output, that the loader path is relative, and that the letter case matches the file on disk. Packaging covers missing files and runtime requests.
The unpacked build is over 50 MiB, which is a warning, not a failure. opengame check adds up every file, so only a smaller build clears it. Convert textures to KTX2, compress models and remove files the game never loads. Moving loads after og.ready() leaves this warning in place, but it lowers the download before the first playable frame, which is the number the playtest run at submit measures.
Look at texture memory first. Resize textures to their on-screen size and convert them to KTX2, which stays compressed on the GPU.

Where next

Package your game

Check the folder, manifest, runtime requests and upload limits.

Update a live game

Replace a published release with a smaller one.