littlejsengine 1.19.3 → 1.21.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 (54) hide show
  1. package/FAQ.md +841 -820
  2. package/README.md +29 -25
  3. package/dist/littlejs.d.ts +2857 -955
  4. package/dist/littlejs.esm.js +33428 -20995
  5. package/dist/littlejs.esm.min.js +4 -3
  6. package/dist/littlejs.js +33386 -20996
  7. package/dist/littlejs.min.js +2 -2
  8. package/dist/littlejs.release.js +27841 -20422
  9. package/package.json +10 -5
  10. package/plugins/audioEffects.js +28 -24
  11. package/plugins/box2d.js +2516 -2073
  12. package/plugins/drawUtilities.js +206 -31
  13. package/plugins/gltf.js +786 -0
  14. package/plugins/jsconfig.json +19 -0
  15. package/plugins/lightSystem.js +719 -332
  16. package/plugins/math3d.js +148 -27
  17. package/plugins/medalSystem.js +326 -280
  18. package/plugins/newgrounds.js +400 -199
  19. package/plugins/pathFinder.js +208 -175
  20. package/plugins/pluginExport.js +25 -0
  21. package/plugins/postProcess.js +96 -24
  22. package/plugins/render3d.js +2067 -1632
  23. package/plugins/render3dDebug.js +110 -0
  24. package/plugins/render3dEditor.js +1812 -0
  25. package/plugins/render3dExtras.js +1948 -0
  26. package/plugins/render3dLevel.js +189 -0
  27. package/plugins/render3dVoxels.js +514 -0
  28. package/plugins/sceneSystem.js +58 -0
  29. package/plugins/textureSheet.js +496 -458
  30. package/plugins/threejs.js +183 -163
  31. package/plugins/tweakables.js +411 -0
  32. package/plugins/tweenSystem.js +265 -145
  33. package/plugins/uiSystem.js +2024 -1687
  34. package/plugins/zzfxm.js +159 -159
  35. package/src/engine.js +378 -97
  36. package/src/engineAudio.js +231 -127
  37. package/src/engineBuild.mjs +292 -256
  38. package/src/engineDebug.js +967 -743
  39. package/src/engineDraw.js +391 -198
  40. package/src/engineEditor.js +2452 -0
  41. package/src/engineExport.js +18 -0
  42. package/src/engineInput.js +1466 -1293
  43. package/src/engineLogo.js +19 -0
  44. package/src/engineMath.js +1274 -1252
  45. package/src/engineObject.js +197 -82
  46. package/src/engineParticles.js +583 -532
  47. package/src/engineRelease.js +79 -45
  48. package/src/engineSettings.js +115 -29
  49. package/src/engineTileLayer.js +995 -741
  50. package/src/engineUtilities.js +19 -13
  51. package/src/engineWebGL.js +239 -82
  52. package/src/jsconfig.json +17 -13
  53. package/plugins/box2d.wasm.js +0 -630
  54. package/plugins/box2d.wasm.wasm +0 -0
package/FAQ.md CHANGED
@@ -1,820 +1,841 @@
1
- # LittleJS Frequently Asked Questions
2
-
3
- Welcome to the LittleJS FAQ!
4
- This document addresses common questions and issues to help developers get started and troubleshoot their projects.
5
- If you don't find an answer here, feel free to ask the community or check the documentation.
6
-
7
- **Getting Started**
8
- - [What is LittleJS, and how is it different from other JavaScript game engines?](#what-is-littlejs-and-how-is-it-different-from-other-javascript-game-engines)
9
- - [How do I set up a basic LittleJS project?](#how-do-i-set-up-a-basic-littlejs-project)
10
- - [How do I use LittleJS as an ES module?](#how-do-i-use-littlejs-as-an-es-module)
11
- - [How do I use LittleJS with TypeScript?](#how-do-i-use-littlejs-with-typescript)
12
- - [Why do I see a blank screen when I run my game?](#why-do-i-see-a-blank-screen-when-i-run-my-game)
13
- - [Do I need a local server to run LittleJS games, and how do I set one up?](#do-i-need-a-local-server-to-run-littlejs-games-and-how-do-i-set-one-up)
14
- - [How does the camera and world coordinate systems work?](#how-does-the-camera-and-world-coordinate-systems-work)
15
- - [How do I configure engine settings?](#how-do-i-configure-engine-settings)
16
- - [What plugins ship with LittleJS?](#what-plugins-ship-with-littlejs)
17
- - [How do I use Vite with LittleJS?](#how-do-i-use-vite-with-littlejs)
18
- - [How do I use Box2D with Vite?](#how-do-i-use-box2d-with-vite)
19
- - [How do I build and publish my game?](#how-do-i-build-and-publish-my-game)
20
- - [How do I shrink my game for js13kGames?](#how-do-i-shrink-my-game-for-js13kgames)
21
-
22
- **Graphics and Sound**
23
- - [How do I load and add images to my game?](#how-do-i-load-and-add-images-to-my-game)
24
- - [What is the tile function and how do tile indexes work?](#what-is-the-tile-function-and-how-do-tile-indexes-work)
25
- - [Can I add and switch between multiple sprites for a game object?](#can-i-add-and-switch-between-multiple-sprites-for-a-game-object)
26
- - [There are thin lines around my sprites sometimes, how can I fix that?](#there-are-thin-lines-around-my-sprites-sometimes-how-can-i-fix-that)
27
- - [How do I handle animations in LittleJS?](#how-do-i-handle-animations-in-littlejs)
28
- - [How do I control the camera in LittleJS?](#how-do-i-control-the-camera-in-littlejs)
29
- - [How do I use post-processing shaders?](#how-do-i-use-post-processing-shaders)
30
- - [How do I play sounds in LittleJS?](#how-do-i-play-sounds-in-littlejs)
31
- - [How do I play music?](#how-do-i-play-music)
32
- - [Why doesn't my audio play until the user clicks something?](#why-doesnt-my-audio-play-until-the-user-clicks-something)
33
- - [If I load several images, how do I control which is used?](#if-i-load-several-images-how-do-i-control-which-is-used)
34
- - [How can I check if an object is on screen?](#how-can-i-check-if-an-object-is-on-screen)
35
-
36
- **Gameplay and Programming**
37
- - [How do I add keyboard or mouse input to my game?](#how-do-i-add-keyboard-or-mouse-input-to-my-game)
38
- - [How does touch input work on mobile?](#how-does-touch-input-work-on-mobile)
39
- - [How do I create and update game objects?](#how-do-i-create-and-update-game-objects)
40
- - [How do I use parent/child objects?](#how-do-i-use-parentchild-objects)
41
- - [How do collisions work?](#how-do-collisions-work)
42
- - [How can I load a 2D level map?](#how-can-i-load-a-2d-level-map)
43
- - [Can I use physics with LittleJS?](#can-i-use-physics-with-littlejs)
44
- - [How do I add particle effects to my game?](#how-do-i-add-particle-effects-to-my-game)
45
- - [How do I use timers?](#how-do-i-use-timers)
46
- - [How do I save and load game state?](#how-do-i-save-and-load-game-state)
47
- - [How do I use the medals (achievements) system?](#how-do-i-use-the-medals-achievements-system)
48
-
49
- **Debugging and Development**
50
- - [How do I debug my game in LittleJS?](#how-do-i-debug-my-game-in-littlejs)
51
-
52
- ---
53
-
54
- ## Getting Started
55
- ### What is LittleJS, and how is it different from other JavaScript game engines?
56
-
57
- LittleJS is a lightweight, high-performance JavaScript game engine designed for simplicity and speed.
58
- It offers a hybrid rendering system that combines the advantages of WebGL and 2D Canvas, and in our sprite-rendering benchmarks it outperforms larger engines like Phaser, Pixi, and Kaboom.
59
- Unlike those feature-heavy engines, LittleJS focuses on 2D games and providing a comprehensive set of simple, easy to use features.
60
- LittleJS is perfect for developers who want a minimal yet powerful engine to bring their 2D game ideas to life without the complexity of larger frameworks, and it's especially well suited for size-constrained competitions like js13kGames.
61
-
62
- ### How do I set up a basic LittleJS project?
63
-
64
- Download the LittleJS repository via GitHub or npm.
65
- Include one of the LittleJS builds from the dist folder.
66
- Several examples are included for you to build on.
67
- The most basic example is just an empty project.
68
-
69
- [Empty Example HTML file:](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/empty/index.html)
70
- ```html
71
- <!DOCTYPE html><head>
72
- <title>LittleJS Hello World Demo</title>
73
- <meta charset=utf-8>
74
- </head><body>
75
-
76
- <script src=../../dist/littlejs.js></script>
77
- <script src=game.js></script>
78
- </body></html>
79
- ```
80
-
81
- [Empty Example JavaScript file:](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/empty/game.js)
82
- ```javascript
83
- /*
84
- Little JS Hello World Demo
85
- - Just prints "Hello World!"
86
- - A good starting point for new projects
87
- */
88
-
89
- 'use strict';
90
-
91
- ///////////////////////////////////////////////////////////////////////////////
92
- function gameInit()
93
- {
94
- // called once after the engine starts up
95
- // setup the game
96
- }
97
-
98
- ///////////////////////////////////////////////////////////////////////////////
99
- function gameUpdate()
100
- {
101
- // called every frame at 60 frames per second
102
- // handle input and update the game state
103
- }
104
-
105
- ///////////////////////////////////////////////////////////////////////////////
106
- function gameUpdatePost()
107
- {
108
- // called after physics and objects are updated
109
- // setup camera and prepare for render
110
- }
111
-
112
- ///////////////////////////////////////////////////////////////////////////////
113
- function gameRender()
114
- {
115
- // called before objects are rendered
116
- // draw any background effects that appear behind objects
117
- }
118
-
119
- ///////////////////////////////////////////////////////////////////////////////
120
- function gameRenderPost()
121
- {
122
- // called after objects are rendered
123
- // draw effects or hud that appear above all objects
124
- drawTextScreen('Hello World!', mainCanvasSize.scale(.5), 80);
125
- }
126
-
127
- ///////////////////////////////////////////////////////////////////////////////
128
- // Startup LittleJS Engine
129
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
130
- ```
131
-
132
- This is the simplest setup — two files served by a local web server. If you'd rather start from a bundler-based project with hot reload, see [How do I use Vite with LittleJS?](#how-do-i-use-vite-with-littlejs) for the official Vite starter template.
133
-
134
- ### How do I use LittleJS as an ES module?
135
-
136
- If you're using a bundler (Vite, Rollup, webpack, etc.) or just want to use native ES modules, install via npm and import what you need:
137
-
138
- ```
139
- npm install littlejsengine
140
- ```
141
-
142
- ```javascript
143
- import { engineInit, drawText, vec2 } from 'littlejsengine';
144
-
145
- function gameInit() {}
146
- function gameUpdate() {}
147
- function gameUpdatePost() {}
148
- function gameRender() {}
149
- function gameRenderPost() {
150
- drawText('Hello!', vec2(0,0), 4);
151
- }
152
-
153
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
154
- ```
155
-
156
- You can also import everything under a namespace:
157
-
158
- ```javascript
159
- import * as LJS from 'littlejsengine';
160
- LJS.drawText('Hello!', LJS.vec2(0,0), 4);
161
- ```
162
-
163
- For a working example, see [examples/module/](examples/module/). That example imports from a relative path (`'../../dist/littlejs.esm.js'`) since it runs straight out of the repo without `npm install` — once you've installed the package, replace that with `'littlejsengine'` as shown above. The Vite section below shows the full bundler-based setup.
164
-
165
- ### How do I use LittleJS with TypeScript?
166
-
167
- LittleJS ships with type definitions in `dist/littlejs.d.ts`, which the npm package wires up automatically. Install via npm and you get type checking with no extra setup:
168
-
169
- ```
170
- npm install littlejsengine
171
- ```
172
-
173
- ```typescript
174
- import { engineInit, drawText, vec2 } from 'littlejsengine';
175
-
176
- function gameInit(): void {}
177
- function gameUpdate(): void {}
178
- function gameUpdatePost(): void {}
179
- function gameRender(): void {}
180
- function gameRenderPost(): void {
181
- drawText('Hello!', vec2(0,0), 4);
182
- }
183
-
184
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
185
- ```
186
-
187
- See [examples/typescript/](examples/typescript/) for a complete TypeScript project with `tsconfig.json`. You can also adapt the Vite starter — Vite supports TypeScript out of the box, just rename `src/main.js` to `src/main.ts` and update the `index.html` script reference.
188
-
189
- ### Why do I see a blank screen when I run my game?
190
-
191
- If you are seeing a blank screen, first try opening the dev tools console (F12 in most browsers).
192
- This will show you any errors that occur and allows stepping through code to help debug.
193
- A common issue is the image data failing to load with a message like "The image element contains cross-origin data, and may not be loaded."
194
- This is probably because the game was loaded directly without using a web server!
195
-
196
- ### Do I need a local server to run LittleJS games, and how do I set one up?
197
-
198
- Yes, this is a necessary step because web browsers just have protection from loading local files which includes images.
199
- So any JavaScript projects that load images like games must be opened from a local web server.
200
- Don't panic though, it's very easy to fix!
201
-
202
- If you are using [Visual Studio Code](https://code.visualstudio.com/) there is a [Live Preview Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) that will handle this for you automatically.
203
-
204
- Another option is to setup a simple local web server like [http-server](https://www.npmjs.com/package/http-server) via npm.
205
-
206
- ### How does the camera and world coordinate systems work?
207
-
208
- LittleJS drawing functions are all handled in world coordinates by default.
209
- The conversion from world to screen is determined by the camera position and scale as well as the canvas size in pixels.
210
- Camera scale determines how many screen pixels equals 1 world unit while the cameraPos is the offset in world units.
211
- There is also a function you can use called getCameraSize() to get the viewable camera window in world coordinates.
212
-
213
- One thing to keep in mind: in **world space**, Y increases *upward* and tile coordinates use a bottom-left origin. This often surprises developers coming from Canvas2D or other 2D libraries. **Screen space** (used when you pass `screenSpace=true` to a draw function, or when reading `mousePosScreen`) follows the Canvas2D convention instead — Y increases *downward* with a top-left origin. Pick the right one for what you're doing: world space for gameplay, screen space for HUDs and UI overlays.
214
-
215
- ### How do I configure engine settings?
216
-
217
- LittleJS exposes settings through `setX()` functions for every tunable value. Most settings need to be applied **before** `engineInit()` because they affect canvas creation, WebGL setup, or input registration:
218
-
219
- ```javascript
220
- setCanvasFixedSize(vec2(640, 480)); // lock canvas to a fixed resolution
221
- setCanvasPixelated(true); // crisp pixel-art scaling
222
- setGLEnable(false); // Canvas2D only (no WebGL)
223
- setShowSplashScreen(false); // skip the LittleJS splash
224
- setGravity(vec2(0, -.02)); // global gravity for physics
225
- setTileDefaultSize(vec2(8)); // default tile size for tile()
226
-
227
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
228
- ```
229
-
230
- A few common categories of settings:
231
-
232
- - **Canvas** — `setCanvasFixedSize`, `setCanvasMaxSize`, `setCanvasMinAspect`, `setCanvasPixelated`, `setCanvasPixelRatio`
233
- - **Rendering** — `setGLEnable`, `setTilesPixelated`, `setCanvasClearColor`
234
- - **Physics** — `setGravity`, `setObjectDefaultDamping`, `setObjectDefaultRestitution`, `setEnablePhysicsSolver`
235
- - **Audio** — `setSoundEnable`, `setSoundVolume`, `setSoundDefaultRange`
236
- - **Input** — `setTouchInputEnable`, `setTouchGamepadEnable`, `setInputWASDEmulateDirection`
237
-
238
- Camera settings (`setCameraPos`, `setCameraScale`, `setCameraAngle`) are safe to call at runtime — typically from `gameInit` or `gameUpdate`. See [src/engineSettings.js](src/engineSettings.js) for the full list with defaults.
239
-
240
- ### What plugins ship with LittleJS?
241
-
242
- Plugins are self-contained features that live alongside the engine but aren't part of it — nothing in the core engine source references them, and they only depend on the public engine API. Drop in only what you need:
243
-
244
- | Plugin | Purpose |
245
- |--------|---------|
246
- | `math3d.js` | `Vector3` and `Matrix4` for 3D games and plugins, with 3D collision and raycast helpers |
247
- | `render3d.js` | The built-in 3D renderer: meshes, lights, shadows, fog, terrain and 3D particles, into the same canvas as the 2D scene (needs `math3d.js`) |
248
- | `threejs.js` | Renders a Three.js scene behind the LittleJS canvas, an alternative to `render3d.js` |
249
- | `box2d.js` | Full Box2D physics via WebAssembly (more realistic than the built-in arcade physics) |
250
- | `lightSystem.js` | 2D dynamic lighting, lights accumulate into a lightmap that darkens the unlit scene |
251
- | `textureSheet.js` | Packs images into texture sheets as they load, and imports TexturePacker and Aseprite atlases |
252
- | `postProcess.js` | Shadertoy-style fragment shaders for screen-wide effects |
253
- | `uiSystem.js` | Lightweight in-engine UI widgets (buttons, text, tabs) |
254
- | `tweenSystem.js` | Tween any property over time with easing curves |
255
- | `pathFinder.js` | Grid-based A* pathfinding with optional path smoothing |
256
- | `medalSystem.js` | Achievement / medal tracking with toast notifications |
257
- | `newgrounds.js` | Newgrounds.io integration (scoreboards, cloud saves) |
258
- | `zzfxm.js` | Procedural chiptune music via the `ZzFXMusic` class |
259
- | `drawUtilities.js` | Higher-level drawing helpers like nine-slice and three-slice |
260
-
261
- This self-contained design means the community can also ship plugins independently of the main repo — you can write your own plugin as just a JavaScript file that uses the engine's public API.
262
-
263
- ### How do I use Vite with LittleJS?
264
-
265
- There is an official [Vite](https://vite.dev) starter template in `examples/vite-starter`. The fastest way to start a new project from it is with [degit](https://github.com/Rich-Harris/degit):
266
-
267
- ```
268
- npx degit KilledByAPixel/LittleJS/examples/vite-starter my-game
269
- cd my-game
270
- npm install
271
- npm run dev
272
- ```
273
-
274
- Vite will print a local URL (usually `http://localhost:5173/`) — open it and you should see the LittleJS logo tile and "LittleJS + Vite" text.
275
-
276
- Key things the template sets up for you:
277
- - `base: './'` in `vite.config.js` so the build works on GitHub Pages, itch.io, and other subdirectory hosts without further config.
278
- - Assets in `public/` (like `tiles.png`) are served at the site root in dev and copied to the build output, so `engineInit(..., ['tiles.png'])` works in both modes without a separate import.
279
- - A `public/.nojekyll` marker so GitHub Pages serves Vite's `_`-prefixed chunk files correctly.
280
- - A full page reload on save instead of partial HMR, since LittleJS has engine-level global state (canvas, WebGL, input listeners, the RAF loop) that doesn't survive a module hot swap. The relevant line in `src/main.js` is `if (import.meta.hot) import.meta.hot.accept(() => location.reload())`.
281
-
282
- Requires Node 20.19+ or 22.12+ (Vite 7 requirement). Other community projects like [Michael Haynie's LittleJS Jam project](https://github.com/michael-dean-haynie/littlejs-game-jam-2024) are good real-world references for more elaborate Vite setups.
283
-
284
- ### How do I use Box2D with Vite?
285
-
286
- Box2D ships as two separate files in the npm package: `box2d.wasm.js` (the Emscripten loader, ~300KB) and `box2d.wasm.wasm` (the binary, ~167KB). Both live in `node_modules/littlejsengine/dist/`.
287
-
288
- The catch: `box2d.wasm.js` fetches the `.wasm` from **its own script directory** using `document.currentScript.src`. That means the two files have to be served side by side, and `box2d.wasm.js` has to be loaded as a classic `<script>` tag — not a module, no `defer`, no `async` — so `document.currentScript` resolves correctly.
289
-
290
- **To add Box2D to the Vite starter:**
291
-
292
- 1. Copy both files into `public/`:
293
-
294
- ```
295
- public/
296
- box2d.wasm.js
297
- box2d.wasm.wasm
298
- ```
299
-
300
- 2. Add a script tag to `index.html` *before* your module script:
301
-
302
- ```html
303
- <script src=./box2d.wasm.js></script>
304
- <script src=./src/main.js type=module></script>
305
- ```
306
-
307
- Vite may print an informational warning during build that the classic script can't be bundled without `type=module` — this is expected and harmless. The classic-script form is exactly what's needed so Emscripten can read `document.currentScript.src` to locate the `.wasm` file.
308
-
309
- 3. Await `box2dInit()` in `main.js` before `engineInit`:
310
-
311
- ```javascript
312
- import { box2dInit, engineInit } from 'littlejsengine';
313
-
314
- await box2dInit();
315
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
316
- ```
317
-
318
- To avoid copying the files by hand on every fresh install, add a `postinstall` script to `package.json`:
319
-
320
- ```json
321
- "scripts": {
322
- "postinstall": "node -e \"['box2d.wasm.js','box2d.wasm.wasm'].forEach(f=>require('fs').copyFileSync('node_modules/littlejsengine/dist/'+f,'public/'+f))\""
323
- }
324
- ```
325
-
326
- Then `npm install` keeps `public/box2d.wasm.*` in sync with whatever version of `littlejsengine` you have installed. Add them to `.gitignore` if you don't want the binaries in your repo.
327
-
328
- ### How do I build and publish my game?
329
-
330
- It depends on how your project is set up:
331
-
332
- **Plain script-tag projects** (like `examples/starter`) are already deployable as-is. For production, swap the debug bundle for a release build to strip asserts and shrink the file:
333
-
334
- ```html
335
- <!-- During development -->
336
- <script src=dist/littlejs.js></script>
337
-
338
- <!-- For production: stripped asserts, smaller -->
339
- <script src=dist/littlejs.release.js></script>
340
-
341
- <!-- Smallest: minified release -->
342
- <script src=dist/littlejs.min.js></script>
343
- ```
344
-
345
- Then zip up your game folder for itch.io or push to GitHub Pages.
346
-
347
- **Vite / module projects** — run `npm run build` and deploy the output `dist/` folder.
348
-
349
- **For GitHub Pages**, use relative asset paths (the Vite starter sets `base: './'` in `vite.config.js` for this), and include an empty `.nojekyll` file in your output so files starting with `_` aren't ignored by Jekyll.
350
-
351
- **For itch.io**, zip your build output and upload as an HTML5 game. Tick "This file will be played in the browser" in the upload settings.
352
-
353
- **For size coding competitions** like js13kGames, see the [js13k branch](https://github.com/KilledByAPixel/LittleJS/tree/js13k) which provides a build that fits in 7KB zipped.
354
-
355
- ### How do I shrink my game for js13kGames?
356
-
357
- LittleJS has a dedicated [js13k branch](https://github.com/KilledByAPixel/LittleJS/tree/js13k) that builds to about 7KB zipped, leaving most of the 13KB budget for your game. A few practical tips for hitting tiny sizes:
358
-
359
- - **Use Canvas2D instead of WebGL**: call `setGLEnable(false)` before `engineInit`. Stripping the WebGL renderer saves a significant chunk of bytes, and for most pixel-art and low-sprite-count games the visual difference is imperceptible. A nice side effect: once the jam is over, you can flip WebGL back on with a single setting change for a big performance boost when publishing the game elsewhere.
360
- - **Drop unused plugins**: every plugin is self-contained, so only include what you actually use. Skip `uiSystem.js`, `box2d.js`, `pathFinder.js`, etc. unless you need them.
361
- - **Lean on built-ins to avoid asset files**: `Sound` with ZzFX parameters generates sound effects from a tiny array, `ZzFXMusic` makes procedural chiptunes, and built-in shape drawing avoids shipping image assets.
362
- - **Use a packer**: the js13k branch is set up to compress the final bundle with Roadroller or a similar packer. Check the branch's build script for the current toolchain.
363
-
364
- For normal production (non-13KB) builds, the engine ships `dist/littlejs.release.js` (asserts stripped) and `dist/littlejs.min.js` (minified) — both well under 100KB.
365
-
366
- ---
367
-
368
- ## Graphics and Sound
369
-
370
- ### How do I load and add images to my game?
371
-
372
- First you need to load an image file. For LittleJS this is typically done on startup via a parameter to engineInit that is a list of images to load. The engine will ensure that all images are loaded before starting. Most modern devices support textures up to 4096x4096 or larger, though some older mobile devices cap out at 2048x2048 — keep that lower limit in mind if you need broad compatibility. Most games only need one texture, but you can load as many as you need.
373
-
374
- ```javascript
375
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
376
- ```
377
-
378
- LittleJS works best when your tile sheet is broken up into grids of tiles because the rendering system can be batched up. To draw a tile from a source image you can call drawTile and pass in TileInfo object. Another common approach is to create an EngineObject and set its tileInfo, it will automatically be rendered.
379
-
380
- ```javascript
381
- drawTile(vec2(21,5), vec2(4.5), tile(3,128));
382
- ```
383
-
384
- Once loaded, the underlying images are available via `textureInfos[0].image`, `textureInfos[1].image`, etc. This is useful for tasks like reading pixel data to generate a level — see [examples/module/game.js](examples/module/game.js) for an example that reads tile data directly from an image.
385
-
386
- ### What is the tile function and how do tile indexes work?
387
-
388
- The `tile` function returns a `TileInfo` object pointing at a region of a loaded image, ready to pass to `drawTile`, an `EngineObject`, a particle emitter, etc.
389
-
390
- ```javascript
391
- tile(2) // index 2 at the default tile size (16x16)
392
- tile(5, 8) // index 5 at 8x8 tile size
393
- tile(1, 16, 3) // index 1 of size 16 on texture 3 (multi-image projects)
394
- tile(vec2(4,8), vec2(30,10)) // explicit (x,y) tile position with a 30x10 region
395
- ```
396
-
397
- When you pass a numeric index, the engine multiplies it by the tile size to get pixel coordinates inside the texture. Indexes count left-to-right, top-to-bottom — so for a 16x16 tile sheet, `tile(0, 16)` is the top-left tile, `tile(1, 16)` is the one to its right, `tile(8, 16)` wraps to the next row if your sheet is 8 tiles wide, etc.
398
-
399
- The full signature is `tile(index, size, texture, padding, bleed)`:
400
- - `texture` — which loaded image to read from (index into the array you passed to `engineInit`, default 0)
401
- - `padding` — pixels of padding between tiles in the sheet, if you laid them out with space around each
402
- - `bleed` — shrink the sampled region slightly to avoid edge bleeding (see the tile-bleed entry above)
403
-
404
- ### Can I add and switch between multiple sprites for a game object?
405
-
406
- Yes — there are several ways depending on what you need.
407
-
408
- **Set the sprite when creating the object** via the `tileInfo` parameter on `EngineObject`:
409
-
410
- ```javascript
411
- class Player extends EngineObject {
412
- constructor(pos) {
413
- super(pos, vec2(1), tile(0, 16)); // start with tile index 0
414
- }
415
- }
416
- ```
417
-
418
- **Swap the sprite at runtime** by assigning a new `TileInfo`:
419
-
420
- ```javascript
421
- this.tileInfo = tile(3, 16); // now showing tile index 3
422
- ```
423
-
424
- **For frame-based animation**, store a base `TileInfo` and use `.frame(n)` to offset along the row:
425
-
426
- ```javascript
427
- const baseSprite = tile(2, 16);
428
- this.tileInfo = baseSprite.frame(animationFrame); // 0, 1, 2... walks to the right
429
- ```
430
-
431
- **To draw an arbitrary sprite without an object**, call `drawTile` directly:
432
-
433
- ```javascript
434
- drawTile(pos, vec2(1), tile(5, 16));
435
- ```
436
-
437
- ### There are thin lines around my sprites sometimes, how can I fix that?
438
-
439
- That's called tile bleeding — pixels from one tile blend into a neighboring tile when sampling at sub-pixel positions. Two ways to fix it:
440
-
441
- 1. Call `setTileDefaultBleed(.5)` (or pass a `bleed` parameter to `tile()` / the `TileInfo` constructor). This shrinks each tile slightly when sampling. A value around `.5` usually eliminates the issue with no noticeable visual difference. Smaller values like `.1` are sometimes enough.
442
-
443
- 2. Add 1 pixel of padding around each sprite in your spritesheet, then pass that padding to `tile(index, size, texture, padding)` so the engine accounts for it.
444
-
445
- ### How do I handle animations in LittleJS?
446
-
447
- Use the `TileInfo.frame(n)` method to offset along a row of animation frames on your sprite sheet. Lay out each animation's frames left-to-right next to each other on the sheet, then index into them from a base sprite:
448
-
449
- ```javascript
450
- // store the base sprite (the first frame of the animation)
451
- const playerSprite = tile(0, 16);
452
-
453
- // inside update(), advance the frame based on time
454
- const animationFrame = Math.floor(time * 10) % 4; // 4-frame loop at 10 FPS
455
- this.tileInfo = playerSprite.frame(animationFrame);
456
- ```
457
-
458
- `time` is a global updated by the engine each frame (in seconds). The `% 4` keeps the index inside the 4 frames you actually drew on the sheet — adjust to match your animation length.
459
-
460
- ### How do I control the camera in LittleJS?
461
-
462
- LittleJS uses a world space rendering system, so objects can move independently of the camera.
463
- The camera is easy to control using cameraPos and cameraScale, which indicate the world space position and how many pixels is equivalent to one world unit.
464
- The default cameraScale is 32 while the default cameraPos is just the origin.
465
- It is also possible to draw using screen space pixel coordinates by passing in true as the screenSpace parameter to most drawing functions.
466
-
467
- ```javascript
468
- setCameraPos(vec2(22,5)); // move camera to world position (22,5)
469
- setCameraScale(20); // zoom camera to 20 pixels per world unit
470
- ```
471
-
472
- ### How do I use post-processing shaders?
473
-
474
- LittleJS supports Shadertoy-style fragment shaders as a final pass on the rendered output via the post-process plugin. Create the plugin with your shader source before `engineInit`:
475
-
476
- ```javascript
477
- const shader = `
478
- void mainImage(out vec4 c, vec2 p) {
479
- vec2 uv = p / iResolution.xy;
480
- c = texture(iChannel0, uv);
481
- c.rgb *= 1.0 - distance(uv, vec2(0.5)) * 0.7; // vignette
482
- }`;
483
-
484
- new PostProcessPlugin(shader);
485
- engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost);
486
- ```
487
-
488
- The shader gets these uniforms automatically:
489
-
490
- - `iChannel0` (`sampler2D`) — the rendered game frame
491
- - `iResolution` (`vec3`) — canvas width, height, and `1`
492
- - `iTime` (`float`) — seconds since engine start
493
-
494
- The constructor has two optional positional arguments after the shader source:
495
-
496
- ```javascript
497
- new PostProcessPlugin(shader, includeMainCanvas, feedbackTexture);
498
- ```
499
-
500
- - `includeMainCanvas` (default `false`) composites the Canvas2D layer (where some debug and text rendering goes) onto the WebGL canvas before the shader runs. Pass `true` if your post-process effect should apply to *everything* on screen, not just the WebGL-rendered objects.
501
- - `feedbackTexture` (default `false`) makes the previous frame available as `iChannel0` for effects like motion trails or feedback loops. Mutually exclusive with `includeMainCanvas`.
502
-
503
- See [plugins/postProcess.js](plugins/postProcess.js) and the [Breakout example](https://killedbyapixel.github.io/LittleJS/examples/breakout/) for working post-process effects.
504
-
505
- ### How do I play sounds in LittleJS?
506
-
507
- Sounds can either be generated on startup using ZzFX or loaded from a wave or mp3 file.
508
- [ZzFX sounds can be created using the sound designer app.](https://killedbyapixel.github.io/ZzFX/)
509
- Once loaded sounds can be played by calling Sound.play with some parameters to control how it is played.
510
-
511
- ```javascript
512
- const sound_click = new Sound([1,.5]); // create a ZzFX sound
513
- const sound_jump = new Sound('jump.mp3'); // load an mp3 sound
514
- sound_click.play(pos, volume, pitch); // play a sound
515
- ```
516
-
517
- ### How do I play music?
518
-
519
- For procedural chiptune music, use the `ZzFXMusic` class from the [ZzFXM plugin](plugins/zzfxm.js). It extends `Sound` and accepts a ZzFXM-format song array — the music is generated on the fly so it adds essentially no bytes to your build. For longer scored music, load an mp3 or ogg file with the regular `Sound` class and pass `true` as the loop parameter.
520
-
521
- ```javascript
522
- // procedural ZzFXM music (tiny, generated at runtime)
523
- const music = new ZzFXMusic(zzfxmSongData);
524
- music.play();
525
-
526
- // or load an mp3/ogg and loop it
527
- const music = new Sound('music.mp3');
528
- music.play(undefined, 1, 1, 0, true); // pos, volume, rate, pan, loop
529
- ```
530
-
531
- Both pause automatically when the tab loses focus (audio context suspends) and resume when it comes back. Use `music.stop()` to halt playback.
532
-
533
- ### Why doesn't my audio play until the user clicks something?
534
-
535
- Browsers require a user interaction (click, key press, touch) before audio is allowed to play — this is a hard rule from the browser's autoplay policy, not something the engine can bypass. LittleJS handles the actual resume for you: the audio context starts in a suspended state and is automatically resumed on the first input event. You don't need to call `audioContext.resume()` yourself.
536
-
537
- The practical implication is that sounds you try to play during `gameInit()` or on the very first frame won't be audible. Design opening audio (title-screen music, intro sounds) so it kicks in after the first input — a common pattern is a "Press any key to start" screen that begins playing music the moment the player presses something.
538
-
539
- ### If I load several images, how do I control which is used?
540
-
541
- Pass multiple image paths to `engineInit` and select between them with the third argument to `tile()` (the `texture` index):
542
-
543
- ```javascript
544
- // load two images
545
- engineInit(..., ['tiles.png', 'background.png']);
546
-
547
- // later, draw a tile from each
548
- const fgTile = tile(0, 16); // texture 0 → tiles.png
549
- const bgTile = tile(0, 16, 1); // texture 1 → background.png
550
- ```
551
-
552
- The `texture` argument defaults to `0`, so single-image projects can ignore it. One thing to keep in mind for large games: switching between textures forces WebGL to flush its current render batch, so frequently alternating between textures within a single frame can hurt performance. Group draws by texture where you can.
553
-
554
- ### How can I check if an object is on screen?
555
-
556
- You can use the `isOverlapping` function to check the object against the camera's viewable window. For culling you might want to enlarge the object size slightly to account for attached objects or rotation — I usually do `this.size.scale(2)`.
557
-
558
- ```javascript
559
- if (!isOverlapping(this.pos, this.size, cameraPos, getCameraSize()))
560
- return;
561
- ```
562
-
563
- `getCameraSize()` returns the viewable window in world units (canvas pixel size divided by `cameraScale`).
564
-
565
- ---
566
-
567
- ## Gameplay and Programming
568
-
569
- ### How do I add keyboard or mouse input to my game?
570
-
571
- LittleJS provides input handling functions for keyboard, mouse, and gamepads. Touch input is also routed to the mouse. There are functions for isDown, wasPressed, and wasReleased. Input can only be checked during the update and should not be called from render functions.
572
-
573
- ```javascript
574
- if (keyIsDown('ArrowLeft')) // Left arrow key
575
- obj.pos.x -= 5;
576
- if (mouseWasReleased(0)) // Left mouse button
577
- console.log('Mouse clicked at:', mousePos);
578
- if (gamepadWasPressed(0)) // Gamepad button 0
579
- console.log('Gamepad pressed, stick is:', gamepadStick(0));
580
- ```
581
-
582
- Keyboard keys are identified by `KeyboardEvent.code` strings (e.g. `'KeyA'`, `'Space'`, `'ArrowUp'`, `'Enter'`). See the [MDN code reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values) for the full list.
583
-
584
- ### How does touch input work on mobile?
585
-
586
- Touch input is routed to the mouse functions automatically — `mouseIsDown(0)`, `mouseWasPressed(0)`, and `mousePos` all work with a finger tap on the canvas. Games written for mouse usually work on mobile without code changes.
587
-
588
- For games that need movement and multiple buttons, enable the built-in on-screen gamepad:
589
-
590
- ```javascript
591
- setTouchGamepadEnable(true);
592
- ```
593
-
594
- Once enabled, an analog stick and face buttons appear on touch devices, and the regular gamepad input functions (`gamepadIsDown`, `gamepadStick`) work normally — so the same code can drive both a physical gamepad and the touch overlay. Customization helpers like `setTouchGamepadSize`, `setTouchGamepadAnalog`, `setTouchGamepadButtonCount`, and `setTouchGamepadAlpha` let you tune the appearance.
595
-
596
- You can also branch behavior on `isTouchDevice` if you need to detect touch hardware explicitly.
597
-
598
- ### How do I create and update game objects?
599
-
600
- LittleJS can be used as an object oriented system by extending the base class [EngineObject](https://github.com/KilledByAPixel/LittleJS/blob/main/src/engineObject.js) with your own. This lightweight class provides many useful features including physics, collision, parent/child system, and sorted rendering. These objects are added to the global list of objects where they will automatically be updated and rendered until destroyed.
601
-
602
- Here is a template you can use to make objects that behave however you want. See the examples for a complete demonstration.
603
-
604
- ```javascript
605
- class MyObject extends EngineObject
606
- {
607
- constructor(pos, size, tileInfo)
608
- {
609
- super(pos, size, tileInfo);
610
- // setup object
611
- }
612
-
613
- update()
614
- {
615
- // update object physics and position
616
- super.update();
617
- }
618
-
619
- render()
620
- {
621
- // draw object as a sprite
622
- super.render();
623
- }
624
- }
625
-
626
- // spawn one of those objects
627
- const object = new MyObject(pos, size, tileInfo);
628
- ```
629
-
630
- Common things you'll do on an `EngineObject`:
631
-
632
- - `this.setCollision()` — opt in to tile collision and object-object collision
633
- - `this.tileInfo = tile(7, 16)` — swap the sprite at runtime
634
- - `this.velocity = vec2(.1, 0)` — physics is applied automatically each frame
635
- - `this.gravityScale = 0` — disable gravity for this object (e.g. UI or floating objects)
636
- - `this.destroy()` — remove from the world (calls cleanup on children too)
637
-
638
- ### How do I use parent/child objects?
639
-
640
- `EngineObject` supports a parent/child hierarchy with local transforms. Attached children automatically follow their parent's position, angle, and mirror state each frame:
641
-
642
- ```javascript
643
- const ship = new EngineObject(vec2(10, 5), vec2(2));
644
- const turret = new EngineObject(vec2(), vec2(.5));
645
- ship.addChild(turret, vec2(0, .5)); // turret sits .5 units above the ship in local space
646
- ```
647
-
648
- `addChild(child, localPos, localAngle)` attaches the child with a local offset; the child's world-space `pos` and `angle` are recomputed each frame from the parent's transform. To detach, call `parent.removeChild(child)` — or just `child.destroy()`, which also detaches it (and destroying a parent destroys all its children).
649
-
650
- A few things to know:
651
- - **Mirroring a parent flips children** — a mirrored ship has its turret on the correct side automatically (`localPos.x` and `localAngle` are inverted).
652
- - **Velocity is not inherited** — physics runs on the world transform after parenting, so a child doesn't automatically pick up the parent's velocity. If you need that, set the child's velocity manually before attaching.
653
- - **For decorative attachments** that shouldn't be affected by physics, set `gravityScale = 0` and skip `setCollision()` on the child.
654
-
655
- ### How do collisions work?
656
-
657
- Opt in to collision on an `EngineObject` by calling `setCollision()` — by default it's off so most objects don't pay the collision cost.
658
-
659
- ```javascript
660
- class Player extends EngineObject {
661
- constructor(pos) {
662
- super(pos, vec2(1), tile(0, 16));
663
- this.setCollision(); // collide with solid objects and tile map
664
- }
665
- }
666
- ```
667
-
668
- `setCollision(collideSolidObjects, isSolid, collideTiles, collideRaycast)` — all default to `true`. Pass `false` to opt out of any of them (e.g. a ghost that passes through walls: `setCollision(false, false, false)`).
669
-
670
- For custom collision logic, override `collideWithObject(other)` and `collideWithTile(tileData, pos)` on your subclass. Return `true` to actually block the collision, `false` to pass through. Both fire during the physics step:
671
-
672
- ```javascript
673
- class Bullet extends EngineObject {
674
- collideWithObject(other) {
675
- if (other instanceof Enemy) {
676
- other.damage(1);
677
- this.destroy();
678
- return false; // don't physically bounce off the enemy
679
- }
680
- return true; // solid collision with everything else
681
- }
682
- }
683
- ```
684
-
685
- For raycasting against the tile collision map (line-of-sight checks, hitscan weapons, etc.), use `tileCollisionRaycast(start, end)` which returns the hit position or `undefined`.
686
-
687
- ### How can I load a 2D level map?
688
-
689
- There are two parts that work together to make 2D level maps in LittleJS.
690
- The tileCollision system is an array of values that correspond to each tile in the map.
691
- The built in physics system can resolve collisions against this tile map very quickly.
692
- For the visual side of things, there is a class called TileLayer which can be used in conjunction with tileCollision.
693
- The TileLayer system works by pre-rendering the entire level to a texture so it can be drawn extremely fast each frame.
694
- It is also possible to have multiple TileLayers for foreground and background layers.
695
- [The platformer example shows a basic example of how to load data that was exported from an external editor.](https://killedbyapixel.github.io/LittleJS/examples/platformer/)
696
-
697
- ### Can I use physics with LittleJS?
698
-
699
- Yes! LittleJS comes with a robust game physics system included and [also a plugin using Box2D.](https://killedbyapixel.github.io/LittleJS/examples/box2d/)
700
- The platformer example includes a character object class that can be used as a starting point for advanced platforming physics.
701
-
702
- ### How do I add particle effects to my game?
703
-
704
- [There is a particle system designer that is useful for experimenting with particle designs.](https://killedbyapixel.github.io/LittleJS/examples/particles/)
705
-
706
- You can create a particle system in code using the ParticleEmitter object.
707
-
708
- ```javascript
709
- // fire particle system
710
- new ParticleEmitter(
711
- pos, 0, // pos, angle
712
- 1, .1, 100, PI, // emitSize, emitTime, emitRate, emitCone
713
- undefined, // tileInfo (undefined = untextured)
714
- rgb(1,.5,.1), rgb(1,.1,.1), // colorStartA, colorStartB
715
- rgb(1,.5,.1,0), rgb(1,.1,.1,0), // colorEndA, colorEndB
716
- .7, .8, .2, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
717
- .9, 1, -.2, PI, .05, // damp, angleDamp, gravity, particleCone, fade
718
- .5, 0, 1, 0, 1e9 // randomness, collide, additive, colorLinear, renderOrder
719
- );
720
- ```
721
-
722
- ### How do I use timers?
723
-
724
- The `Timer` class is a small but very useful utility for anything that needs to happen *after some time has passed* — cooldowns, animation timing, delayed spawns, fades, etc. It tracks elapsed time automatically, so you don't have to add `timeDelta` to a variable every frame.
725
-
726
- ```javascript
727
- const cooldown = new Timer(2); // 2-second timer, starts immediately
728
-
729
- function gameUpdate() {
730
- if (cooldown.elapsed()) {
731
- fireWeapon();
732
- cooldown.set(2); // restart it
733
- }
734
- }
735
- ```
736
-
737
- Useful methods:
738
- - `set(seconds)` — start or restart the timer
739
- - `unset()` — disable it (`isSet()` becomes false)
740
- - `active()` / `elapsed()` — boolean checks
741
- - `get()` — seconds elapsed (negative while still counting down)
742
- - `getPercent()` — 0 → 1 progress, handy for easing animations
743
-
744
- By default, a timer ticks on **game time** — it pauses when the game is paused and slows down or speeds up with `setTimeScale`. For UI animations, on-screen notifications, or anything that should keep running regardless of game state, use a real-time timer:
745
-
746
- ```javascript
747
- const uiFade = new Timer(.5, true); // useRealTime = true
748
- ```
749
-
750
- Real-time timers track `timeReal` instead of `time` — unaffected by pause or time scale. You can also flip an existing timer between modes with `timer.setUseRealTime(true)` while it's unset.
751
-
752
- ### How do I save and load game state?
753
-
754
- LittleJS has built-in helpers for localStorage-backed save data — no need to roll your own JSON serialization:
755
-
756
- ```javascript
757
- // Read save data, falling back to defaults if nothing is saved yet
758
- const saveData = readSaveData('MyGame', { highScore: 0, level: 1 });
759
-
760
- // Write save data
761
- writeSaveData('MyGame', { highScore: 99999, level: 5 });
762
- ```
763
-
764
- The `saveName` should be unique per game so multiple LittleJS games on the same host don't collide. The data object must be JSON-serializable (no functions, no circular references). Under the hood these are thin wrappers around `localStorage` + `JSON.stringify` / `JSON.parse`.
765
-
766
- ### How do I use the medals (achievements) system?
767
-
768
- LittleJS includes a medals plugin for tracking unlockable achievements with toast notifications:
769
-
770
- ```javascript
771
- // Create medals — each needs a unique id, optional emoji icon
772
- const medal_firstWin = new Medal(0, 'First Win', 'Win your first match', '🏆');
773
- const medal_perfect = new Medal(1, 'Perfect Score', 'Score 100%', '⭐');
774
-
775
- // Initialize medals — saveName persists unlocks to localStorage
776
- medalsInit('MyGame');
777
-
778
- // Unlock a medal (shows notification, persists across sessions)
779
- medal_firstWin.unlock();
780
- ```
781
-
782
- You can pass an image URL as the fifth argument to `Medal` instead of an emoji icon. The `saveName` you pass to `medalsInit` is used to track which medals have been unlocked in localStorage, so unlocks persist across sessions.
783
-
784
- The plugin also supports [Newgrounds](https://www.newgrounds.com) integration via `newgrounds.io` for hosted leaderboards and cloud-synced achievements. See [plugins/medalSystem.js](plugins/medalSystem.js) for details.
785
-
786
- ---
787
-
788
- ## Debugging and Development
789
-
790
- ### How do I debug my game in LittleJS?
791
-
792
- In addition to your browser's built-in developer tools, LittleJS has its own debug overlay. Press `Esc` to show it — the overlay lists the available toggles and their number-key bindings, so you don't have to memorize them. `+` and `-` adjust the time scale, which is great for slowing tricky moments down or speeding past a slow section.
793
-
794
- **Debug draw functions** let you visualize state directly in the world without committing to permanent UI. They're drawn for one frame and only render in debug builds:
795
-
796
- ```javascript
797
- debugRect(pos, size, color); // outline a rectangle
798
- debugCircle(pos, radius, color); // outline a circle
799
- debugLine(posA, posB, color); // a line between two points
800
- debugText(string, pos, size, color); // text at a world position
801
- debugPoint(pos, color); // small marker
802
- ```
803
-
804
- These are perfect for "where exactly is this AI heading?" or "is this hitbox where I think it is?" investigations — drop a call into your update or render code, see the answer, then delete it.
805
-
806
- **`ASSERT` and `LOG`** are similar to `console.assert` / `console.log`, but they're *stripped from release builds* (`littlejs.release.js`, `littlejs.min.js`). That means you can leave them in your code with no cost to shipped users. Don't rely on side effects inside the arguments — they won't run in release.
807
-
808
- ```javascript
809
- ASSERT(player.health > 0, 'player damaged below zero', player);
810
- LOG('spawned enemy at', pos);
811
- ```
812
-
813
- ---
814
-
815
- ## Contribute to the FAQ
816
- If you have additional questions or think something should be added to this FAQ, please open an issue or pull request on the [LittleJS GitHub repository](https://github.com/KilledByAPixel/LittleJS).
817
-
818
- ---
819
-
820
- Happy coding with LittleJS! 🎮🚂💨
1
+ # LittleJS Frequently Asked Questions
2
+
3
+ Welcome to the LittleJS FAQ!
4
+ This document addresses common questions and issues to help developers get started and troubleshoot their projects.
5
+ If you don't find an answer here, feel free to ask the community or check the documentation.
6
+
7
+ **Getting Started**
8
+ - [What is LittleJS, and how is it different from other JavaScript game engines?](#what-is-littlejs-and-how-is-it-different-from-other-javascript-game-engines)
9
+ - [How do I set up a basic LittleJS project?](#how-do-i-set-up-a-basic-littlejs-project)
10
+ - [How do I use LittleJS as an ES module?](#how-do-i-use-littlejs-as-an-es-module)
11
+ - [How do I use LittleJS with TypeScript?](#how-do-i-use-littlejs-with-typescript)
12
+ - [Why do I see a blank screen when I run my game?](#why-do-i-see-a-blank-screen-when-i-run-my-game)
13
+ - [Do I need a local server to run LittleJS games, and how do I set one up?](#do-i-need-a-local-server-to-run-littlejs-games-and-how-do-i-set-one-up)
14
+ - [How does the camera and world coordinate systems work?](#how-does-the-camera-and-world-coordinate-systems-work)
15
+ - [How do I configure engine settings?](#how-do-i-configure-engine-settings)
16
+ - [What plugins ship with LittleJS?](#what-plugins-ship-with-littlejs)
17
+ - [How do I use Vite with LittleJS?](#how-do-i-use-vite-with-littlejs)
18
+ - [How do I use Box2D with Vite?](#how-do-i-use-box2d-with-vite)
19
+ - [How do I build and publish my game?](#how-do-i-build-and-publish-my-game)
20
+ - [How do I shrink my game for js13kGames?](#how-do-i-shrink-my-game-for-js13kgames)
21
+
22
+ **Graphics and Sound**
23
+ - [How do I load and add images to my game?](#how-do-i-load-and-add-images-to-my-game)
24
+ - [What is the tile function and how do tile indexes work?](#what-is-the-tile-function-and-how-do-tile-indexes-work)
25
+ - [Can I add and switch between multiple sprites for a game object?](#can-i-add-and-switch-between-multiple-sprites-for-a-game-object)
26
+ - [There are thin lines around my sprites sometimes, how can I fix that?](#there-are-thin-lines-around-my-sprites-sometimes-how-can-i-fix-that)
27
+ - [How do I handle animations in LittleJS?](#how-do-i-handle-animations-in-littlejs)
28
+ - [How do I control the camera in LittleJS?](#how-do-i-control-the-camera-in-littlejs)
29
+ - [How do I use post-processing shaders?](#how-do-i-use-post-processing-shaders)
30
+ - [How do I play sounds in LittleJS?](#how-do-i-play-sounds-in-littlejs)
31
+ - [How do I play music?](#how-do-i-play-music)
32
+ - [Why doesn't my audio play until the user clicks something?](#why-doesnt-my-audio-play-until-the-user-clicks-something)
33
+ - [If I load several images, how do I control which is used?](#if-i-load-several-images-how-do-i-control-which-is-used)
34
+ - [How can I check if an object is on screen?](#how-can-i-check-if-an-object-is-on-screen)
35
+
36
+ **Gameplay and Programming**
37
+ - [How do I add keyboard or mouse input to my game?](#how-do-i-add-keyboard-or-mouse-input-to-my-game)
38
+ - [How does touch input work on mobile?](#how-does-touch-input-work-on-mobile)
39
+ - [How do I create and update game objects?](#how-do-i-create-and-update-game-objects)
40
+ - [How do I use parent/child objects?](#how-do-i-use-parentchild-objects)
41
+ - [How do collisions work?](#how-do-collisions-work)
42
+ - [How can I load a 2D level map?](#how-can-i-load-a-2d-level-map)
43
+ - [Can I use physics with LittleJS?](#can-i-use-physics-with-littlejs)
44
+ - [How do I add particle effects to my game?](#how-do-i-add-particle-effects-to-my-game)
45
+ - [How do I use timers?](#how-do-i-use-timers)
46
+ - [How do I save and load game state?](#how-do-i-save-and-load-game-state)
47
+ - [How do I use the medals (achievements) system?](#how-do-i-use-the-medals-achievements-system)
48
+
49
+ **Debugging and Development**
50
+ - [How do I debug my game in LittleJS?](#how-do-i-debug-my-game-in-littlejs)
51
+
52
+ ---
53
+
54
+ ## Getting Started
55
+ ### What is LittleJS, and how is it different from other JavaScript game engines?
56
+
57
+ LittleJS is a lightweight, high-performance JavaScript game engine designed for simplicity and speed.
58
+ It offers a hybrid rendering system that combines the advantages of WebGL and 2D Canvas, and in our sprite-rendering benchmarks it outperforms larger engines like Phaser, Pixi, and Kaboom.
59
+ Unlike those feature-heavy engines, LittleJS focuses on 2D games and providing a comprehensive set of simple, easy to use features.
60
+ LittleJS is perfect for developers who want a minimal yet powerful engine to bring their 2D game ideas to life without the complexity of larger frameworks, and it's especially well suited for size-constrained competitions like js13kGames.
61
+
62
+ ### How do I set up a basic LittleJS project?
63
+
64
+ Download the LittleJS repository via GitHub or npm.
65
+ Include one of the LittleJS builds from the dist folder.
66
+ Several examples are included for you to build on.
67
+ The most basic example is just an empty project.
68
+
69
+ [Empty Example HTML file:](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/empty/index.html)
70
+ ```html
71
+ <!DOCTYPE html><head>
72
+ <title>LittleJS Hello World Demo</title>
73
+ <meta charset=utf-8>
74
+ </head><body>
75
+ <script src=game.js type=module></script>
76
+ ```
77
+
78
+ [Empty Example JavaScript file:](https://github.com/KilledByAPixel/LittleJS/blob/main/examples/empty/game.js)
79
+ ```javascript
80
+ /*
81
+ Little JS Hello World Demo
82
+ - Just prints 'Hello World!'
83
+ - A good starting point for new projects
84
+ */
85
+
86
+ 'use strict';
87
+
88
+ // import LittleJS module
89
+ import * as LJS from '../../dist/littlejs.esm.js';
90
+ const {vec2, hsl} = LJS;
91
+
92
+ ///////////////////////////////////////////////////////////////////////////////
93
+ function gameInit()
94
+ {
95
+ // called once after the engine starts up
96
+ // setup the game
97
+ }
98
+
99
+ ///////////////////////////////////////////////////////////////////////////////
100
+ function gameUpdate()
101
+ {
102
+ // called every frame at 60 frames per second
103
+ // handle input and update the game state
104
+ }
105
+
106
+ ///////////////////////////////////////////////////////////////////////////////
107
+ function gameUpdatePost()
108
+ {
109
+ // called after physics and objects are updated
110
+ // setup camera and prepare for render
111
+ }
112
+
113
+ ///////////////////////////////////////////////////////////////////////////////
114
+ function gameRender()
115
+ {
116
+ // called before objects are rendered
117
+ // draw any background effects that appear behind objects
118
+ }
119
+
120
+ ///////////////////////////////////////////////////////////////////////////////
121
+ function gameRenderPost()
122
+ {
123
+ // called after objects are rendered
124
+ // draw effects or hud that appear above all objects
125
+ LJS.drawTextScreen('Hello World!', LJS.mainCanvasSize.scale(.5), 80);
126
+ }
127
+
128
+ ///////////////////////////////////////////////////////////////////////////////
129
+ // Startup LittleJS Engine
130
+ LJS.engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
131
+ ```
132
+
133
+ The empty example is an ES module, so engine names come from the `LJS` namespace. For a classic setup where the engine is a plain `<script src=../../dist/littlejs.js>` tag and every engine name is a global, start from [examples/starter](https://github.com/KilledByAPixel/LittleJS/tree/main/examples/starter) instead.
134
+
135
+ This is the simplest setup — two files served by a local web server. If you'd rather start from a bundler-based project with hot reload, see [How do I use Vite with LittleJS?](#how-do-i-use-vite-with-littlejs) for the official Vite starter template.
136
+
137
+ ### How do I use LittleJS as an ES module?
138
+
139
+ If you're using a bundler (Vite, Rollup, webpack, etc.) or just want to use native ES modules, install via npm and import what you need:
140
+
141
+ ```
142
+ npm install littlejsengine
143
+ ```
144
+
145
+ ```javascript
146
+ import { engineInit, drawText, vec2 } from 'littlejsengine';
147
+
148
+ function gameInit() {}
149
+ function gameUpdate() {}
150
+ function gameUpdatePost() {}
151
+ function gameRender() {}
152
+ function gameRenderPost() {
153
+ drawText('Hello!', vec2(0,0), 4);
154
+ }
155
+
156
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
157
+ ```
158
+
159
+ You can also import everything under a namespace:
160
+
161
+ ```javascript
162
+ import * as LJS from 'littlejsengine';
163
+ LJS.drawText('Hello!', LJS.vec2(0,0), 4);
164
+ ```
165
+
166
+ For a working example, see [examples/module/](examples/module/). That example imports from a relative path (`'../../dist/littlejs.esm.js'`) since it runs straight out of the repo without `npm install` — once you've installed the package, replace that with `'littlejsengine'` as shown above. The Vite section below shows the full bundler-based setup.
167
+
168
+ ### How do I use LittleJS with TypeScript?
169
+
170
+ LittleJS ships with type definitions in `dist/littlejs.d.ts`, which the npm package wires up automatically. Install via npm and you get type checking with no extra setup:
171
+
172
+ ```
173
+ npm install littlejsengine
174
+ ```
175
+
176
+ ```typescript
177
+ import { engineInit, drawText, vec2 } from 'littlejsengine';
178
+
179
+ function gameInit(): void {}
180
+ function gameUpdate(): void {}
181
+ function gameUpdatePost(): void {}
182
+ function gameRender(): void {}
183
+ function gameRenderPost(): void {
184
+ drawText('Hello!', vec2(0,0), 4);
185
+ }
186
+
187
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
188
+ ```
189
+
190
+ See [examples/typescript/](examples/typescript/) for a complete TypeScript project with `tsconfig.json`. You can also adapt the Vite starter — Vite supports TypeScript out of the box, just rename `src/main.js` to `src/main.ts` and update the `index.html` script reference.
191
+
192
+ ### Why do I see a blank screen when I run my game?
193
+
194
+ If you are seeing a blank screen, first try opening the dev tools console (F12 in most browsers).
195
+ This will show you any errors that occur and allows stepping through code to help debug.
196
+ A common issue is the image data failing to load with a message like "The image element contains cross-origin data, and may not be loaded."
197
+ This is probably because the game was loaded directly without using a web server!
198
+
199
+ ### Do I need a local server to run LittleJS games, and how do I set one up?
200
+
201
+ Yes, this is a necessary step because web browsers just have protection from loading local files which includes images.
202
+ So any JavaScript projects that load images like games must be opened from a local web server.
203
+ Don't panic though, it's very easy to fix!
204
+
205
+ If you are using [Visual Studio Code](https://code.visualstudio.com/) there is a [Live Preview Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.live-server) that will handle this for you automatically.
206
+
207
+ Another option is to setup a simple local web server like [http-server](https://www.npmjs.com/package/http-server) via npm.
208
+
209
+ ### How does the camera and world coordinate systems work?
210
+
211
+ LittleJS drawing functions are all handled in world coordinates by default.
212
+ The conversion from world to screen is determined by the camera position and scale as well as the canvas size in pixels.
213
+ Camera scale determines how many screen pixels equals 1 world unit while the cameraPos is the offset in world units.
214
+ There is also a function you can use called getCameraSize() to get the viewable camera window in world coordinates.
215
+
216
+ One thing to keep in mind: in **world space**, Y increases *upward* and tile coordinates use a bottom-left origin. This often surprises developers coming from Canvas2D or other 2D libraries. **Screen space** (used when you pass `screenSpace=true` to a draw function, or when reading `mousePosScreen`) follows the Canvas2D convention instead — Y increases *downward* with a top-left origin. Pick the right one for what you're doing: world space for gameplay, screen space for HUDs and UI overlays.
217
+
218
+ ### How do I configure engine settings?
219
+
220
+ LittleJS exposes settings through `setX()` functions for every tunable value. Most settings need to be applied **before** `engineInit()` because they affect canvas creation, WebGL setup, or input registration:
221
+
222
+ ```javascript
223
+ setCanvasFixedSize(vec2(640, 480)); // lock canvas to a fixed resolution
224
+ setCanvasPixelated(true); // crisp pixel-art scaling
225
+ setGLEnable(false); // Canvas2D only (no WebGL)
226
+ setShowSplashScreen(false); // skip the LittleJS splash
227
+ setGravity(vec2(0, -.02)); // global gravity for physics
228
+ setTileDefaultSize(vec2(8)); // default tile size for tile()
229
+
230
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
231
+ ```
232
+
233
+ A few common categories of settings:
234
+
235
+ - **Canvas** — `setCanvasFixedSize`, `setCanvasMaxSize`, `setCanvasMinAspect`, `setCanvasPixelated`, `setCanvasPixelRatio`
236
+ - **Rendering** — `setGLEnable`, `setTilesPixelated`, `setCanvasClearColor`
237
+ - **Physics** — `setGravity`, `setObjectDefaultDamping`, `setObjectDefaultRestitution`, `setEnablePhysicsSolver`
238
+ - **Audio** — `setSoundEnable`, `setSoundVolume`, `setSoundDefaultRange`
239
+ - **Input** — `setTouchInputEnable`, `setTouchGamepadEnable`, `setInputWASDEmulateDirection`
240
+
241
+ Camera settings (`setCameraPos`, `setCameraScale`, `setCameraAngle`) are safe to call at runtime — typically from `gameInit` or `gameUpdate`. See [src/engineSettings.js](src/engineSettings.js) for the full list with defaults.
242
+
243
+ ### What plugins ship with LittleJS?
244
+
245
+ Plugins are self-contained features that live alongside the engine but aren't part of it — nothing in the core engine source references them, and they only depend on the public engine API. Drop in only what you need:
246
+
247
+ | Plugin | Purpose |
248
+ |--------|---------|
249
+ | `math3d.js` | `Vector3` and `Matrix4` for 3D games and plugins, with 3D collision and raycast helpers |
250
+ | `render3d.js` | The built-in 3D renderer: meshes, lights, shadows, fog and EngineObject3D, into the same canvas as the 2D scene (needs `math3d.js`) |
251
+ | `render3dExtras.js` | The rest of the 3D toolkit on top of it: more shape builders, terrain, camera controls, 3D particles and trails, and the OBJ loader |
252
+ | `gltf.js` | Loads glTF and GLB models onto the 3D renderer: meshes, node placement and node animations, colors and textures |
253
+ | `threejs.js` | Renders a Three.js scene behind the LittleJS canvas, an alternative to `render3d.js` |
254
+ | `box2d.js` | Full Box2D physics via WebAssembly (more realistic than the built-in arcade physics) |
255
+ | `lightSystem.js` | 2D dynamic lighting, lights accumulate into a lightmap that darkens the unlit scene |
256
+ | `textureSheet.js` | Packs images into texture sheets as they load, and imports TexturePacker and Aseprite atlases |
257
+ | `postProcess.js` | Shadertoy-style fragment shaders for screen-wide effects |
258
+ | `uiSystem.js` | Lightweight in-engine UI widgets (buttons, text, checkboxes, sliders, text input, video, layout) |
259
+ | `tweenSystem.js` | Tween any property over time with easing curves |
260
+ | `pathFinder.js` | Grid-based A* pathfinding with optional path smoothing |
261
+ | `tweakables.js` | A debug panel to change values live while the game runs, press 9 with the debug overlay open |
262
+ | `medalSystem.js` | Achievement / medal tracking with popup notifications |
263
+ | `newgrounds.js` | Newgrounds.io integration (medals held on the server, scoreboards) |
264
+ | `zzfxm.js` | Procedural chiptune music via the `ZzFXMusic` class |
265
+ | `audioEffects.js` | Web Audio effects like filter, reverb, delay, distortion and compressor, for a group of sounds or everything |
266
+ | `drawUtilities.js` | Higher-level drawing helpers like nine-slice and three-slice |
267
+
268
+ This self-contained design means the community can also ship plugins independently of the main repo — you can write your own plugin as just a JavaScript file that uses the engine's public API.
269
+
270
+ ### How do I use Vite with LittleJS?
271
+
272
+ There is an official [Vite](https://vite.dev) starter template in `examples/vite-starter`. The fastest way to start a new project from it is with [degit](https://github.com/Rich-Harris/degit):
273
+
274
+ ```
275
+ npx degit KilledByAPixel/LittleJS/examples/vite-starter my-game
276
+ cd my-game
277
+ npm install
278
+ npm run dev
279
+ ```
280
+
281
+ Vite will print a local URL (usually `http://localhost:5173/`) — open it and you should see the LittleJS logo tile and "LittleJS + Vite" text.
282
+
283
+ Key things the template sets up for you:
284
+ - `base: './'` in `vite.config.js` so the build works on GitHub Pages, itch.io, and other subdirectory hosts without further config.
285
+ - Assets in `public/` (like `tiles.png`) are served at the site root in dev and copied to the build output, so `engineInit(..., ['tiles.png'])` works in both modes without a separate import.
286
+ - A `public/.nojekyll` marker so GitHub Pages serves Vite's `_`-prefixed chunk files correctly.
287
+ - A full page reload on save instead of partial HMR, since LittleJS has engine-level global state (canvas, WebGL, input listeners, the RAF loop) that doesn't survive a module hot swap. The full reload is a small plugin in `vite.config.js`.
288
+
289
+ Requires Node 20.19+ or 22.12+ (Vite 7 requirement). Other community projects like [Michael Haynie's LittleJS Jam project](https://github.com/michael-dean-haynie/littlejs-game-jam-2024) are good real-world references for more elaborate Vite setups.
290
+
291
+ ### How do I use Box2D with Vite?
292
+
293
+ Box2D ships as two separate files in the npm package: `box2d.wasm.js` (the Emscripten loader, ~300KB) and `box2d.wasm.wasm` (the binary, ~167KB). Both live in `node_modules/littlejsengine/dist/`.
294
+
295
+ The catch: `box2d.wasm.js` fetches the `.wasm` from **its own script directory** using `document.currentScript.src`. That means the two files have to be served side by side, and `box2d.wasm.js` has to be loaded as a classic `<script>` tag — not a module, no `defer`, no `async` — so `document.currentScript` resolves correctly.
296
+
297
+ **To add Box2D to the Vite starter:**
298
+
299
+ 1. Copy both files into `public/`:
300
+
301
+ ```
302
+ public/
303
+ box2d.wasm.js
304
+ box2d.wasm.wasm
305
+ ```
306
+
307
+ 2. Add a script tag to `index.html` *before* your module script:
308
+
309
+ ```html
310
+ <script src=./box2d.wasm.js></script>
311
+ <script src=./src/main.js type=module></script>
312
+ ```
313
+
314
+ Vite may print an informational warning during build that the classic script can't be bundled without `type=module` — this is expected and harmless. The classic-script form is exactly what's needed so Emscripten can read `document.currentScript.src` to locate the `.wasm` file.
315
+
316
+ 3. Await `box2dInit()` in `main.js` before `engineInit`:
317
+
318
+ ```javascript
319
+ import { box2dInit, engineInit } from 'littlejsengine';
320
+
321
+ await box2dInit();
322
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
323
+ ```
324
+
325
+ To avoid copying the files by hand on every fresh install, add a `postinstall` script to `package.json`:
326
+
327
+ ```json
328
+ "scripts": {
329
+ "postinstall": "node -e \"['box2d.wasm.js','box2d.wasm.wasm'].forEach(f=>require('fs').copyFileSync('node_modules/littlejsengine/dist/'+f,'public/'+f))\""
330
+ }
331
+ ```
332
+
333
+ Then `npm install` keeps `public/box2d.wasm.*` in sync with whatever version of `littlejsengine` you have installed. Add them to `.gitignore` if you don't want the binaries in your repo.
334
+
335
+ ### How do I build and publish my game?
336
+
337
+ It depends on how your project is set up:
338
+
339
+ **Plain script-tag projects** (like `examples/starter`) are already deployable as-is. For production, swap the debug bundle for a release build to strip asserts and shrink the file:
340
+
341
+ ```html
342
+ <!-- During development -->
343
+ <script src=dist/littlejs.js></script>
344
+
345
+ <!-- For production: stripped asserts, smaller -->
346
+ <script src=dist/littlejs.release.js></script>
347
+
348
+ <!-- Smallest: minified release -->
349
+ <script src=dist/littlejs.min.js></script>
350
+ ```
351
+
352
+ Then zip up your game folder for itch.io or push to GitHub Pages.
353
+
354
+ **Vite / module projects** — run `npm run build` and deploy the output `dist/` folder.
355
+
356
+ **For GitHub Pages**, use relative asset paths (the Vite starter sets `base: './'` in `vite.config.js` for this), and include an empty `.nojekyll` file in your output so files starting with `_` aren't ignored by Jekyll.
357
+
358
+ **For itch.io**, zip your build output and upload as an HTML5 game. Tick "This file will be played in the browser" in the upload settings.
359
+
360
+ **For size coding competitions** like js13kGames, see the [js13k branch](https://github.com/KilledByAPixel/LittleJS/tree/js13k) which provides a build that fits in 7KB zipped.
361
+
362
+ ### How do I shrink my game for js13kGames?
363
+
364
+ LittleJS has a dedicated [js13k branch](https://github.com/KilledByAPixel/LittleJS/tree/js13k) that builds to about 7KB zipped, leaving most of the 13KB budget for your game. A few practical tips for hitting tiny sizes:
365
+
366
+ - **Use Canvas2D instead of WebGL**: call `setGLEnable(false)` before `engineInit`. Stripping the WebGL renderer saves a significant chunk of bytes, and for most pixel-art and low-sprite-count games the visual difference is imperceptible. A nice side effect: once the jam is over, you can flip WebGL back on with a single setting change for a big performance boost when publishing the game elsewhere.
367
+ - **Drop unused plugins**: every plugin is self-contained, so only include what you actually use. Skip `uiSystem.js`, `box2d.js`, `pathFinder.js`, etc. unless you need them.
368
+ - **Lean on built-ins to avoid asset files**: `Sound` with ZzFX parameters generates sound effects from a tiny array, `ZzFXMusic` makes procedural chiptunes, and built-in shape drawing avoids shipping image assets.
369
+ - **Use a packer**: the js13k branch is set up to compress the final bundle with Roadroller or a similar packer. Check the branch's build script for the current toolchain.
370
+
371
+ For normal production (non-13KB) builds, the engine ships `dist/littlejs.release.js` (asserts stripped) and `dist/littlejs.min.js` (minified) — both well under 100KB.
372
+
373
+ ---
374
+
375
+ ## Graphics and Sound
376
+
377
+ ### How do I load and add images to my game?
378
+
379
+ First you need to load an image file. For LittleJS this is typically done on startup via a parameter to engineInit that is a list of images to load. The engine will ensure that all images are loaded before starting. Most modern devices support textures up to 4096x4096 or larger, though some older mobile devices cap out at 2048x2048 — keep that lower limit in mind if you need broad compatibility. Most games only need one texture, but you can load as many as you need.
380
+
381
+ ```javascript
382
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);
383
+ ```
384
+
385
+ LittleJS works best when your tile sheet is broken up into grids of tiles because the rendering system can be batched up. To draw a tile from a source image you can call drawTile and pass in TileInfo object. Another common approach is to create an EngineObject and set its tileInfo, it will automatically be rendered.
386
+
387
+ ```javascript
388
+ drawTile(vec2(21,5), vec2(4.5), tile(3,128));
389
+ ```
390
+
391
+ Once loaded, the underlying images are available via `textureInfos[0].image`, `textureInfos[1].image`, etc. This is useful for tasks like reading pixel data to generate a level — see [examples/module/game.js](examples/module/game.js) for an example that reads tile data directly from an image.
392
+
393
+ ### What is the tile function and how do tile indexes work?
394
+
395
+ The `tile` function returns a `TileInfo` object pointing at a region of a loaded image, ready to pass to `drawTile`, an `EngineObject`, a particle emitter, etc.
396
+
397
+ ```javascript
398
+ tile(2) // index 2 at the default tile size (16x16)
399
+ tile(5, 8) // index 5 at 8x8 tile size
400
+ tile(1, 16, 3) // index 1 of size 16 on texture 3 (multi-image projects)
401
+ tile(vec2(4,8), vec2(30,10)) // explicit (x,y) tile position with a 30x10 region
402
+ ```
403
+
404
+ When you pass a numeric index, the engine multiplies it by the tile size to get pixel coordinates inside the texture. Indexes count left-to-right, top-to-bottom — so for a 16x16 tile sheet, `tile(0, 16)` is the top-left tile, `tile(1, 16)` is the one to its right, `tile(8, 16)` wraps to the next row if your sheet is 8 tiles wide, etc.
405
+
406
+ The full signature is `tile(index, size, texture, padding, bleed)`:
407
+ - `texture` — which loaded image to read from (index into the array you passed to `engineInit`, default 0)
408
+ - `padding` — pixels of padding between tiles in the sheet, if you laid them out with space around each
409
+ - `bleed` — shrink the sampled region slightly to avoid edge bleeding (see the tile-bleed entry below)
410
+
411
+ ### Can I add and switch between multiple sprites for a game object?
412
+
413
+ Yes — there are several ways depending on what you need.
414
+
415
+ **Set the sprite when creating the object** via the `tileInfo` parameter on `EngineObject`:
416
+
417
+ ```javascript
418
+ class Player extends EngineObject {
419
+ constructor(pos) {
420
+ super(pos, vec2(1), tile(0, 16)); // start with tile index 0
421
+ }
422
+ }
423
+ ```
424
+
425
+ **Swap the sprite at runtime** by assigning a new `TileInfo`:
426
+
427
+ ```javascript
428
+ this.tileInfo = tile(3, 16); // now showing tile index 3
429
+ ```
430
+
431
+ **For frame-based animation**, store a base `TileInfo` and use `.frame(n)` to offset along the row:
432
+
433
+ ```javascript
434
+ const baseSprite = tile(2, 16);
435
+ this.tileInfo = baseSprite.frame(animationFrame); // 0, 1, 2... walks to the right
436
+ ```
437
+
438
+ **To draw an arbitrary sprite without an object**, call `drawTile` directly:
439
+
440
+ ```javascript
441
+ drawTile(pos, vec2(1), tile(5, 16));
442
+ ```
443
+
444
+ ### There are thin lines around my sprites sometimes, how can I fix that?
445
+
446
+ That's called tile bleeding — pixels from one tile blend into a neighboring tile when sampling at sub-pixel positions. Two ways to fix it:
447
+
448
+ 1. Call `setTileDefaultBleed(.5)` (or pass a `bleed` parameter to `tile()` / the `TileInfo` constructor). This shrinks each tile slightly when sampling. A value around `.5` usually eliminates the issue with no noticeable visual difference. Smaller values like `.1` are sometimes enough.
449
+
450
+ 2. Add 1 pixel of padding around each sprite in your spritesheet, then pass that padding to `tile(index, size, texture, padding)` so the engine accounts for it.
451
+
452
+ ### How do I handle animations in LittleJS?
453
+
454
+ Use `SpriteAnimation`, which steps through frames laid out left-to-right on your sprite sheet, and can loop, play once or ping-pong, pausing with the game:
455
+
456
+ ```javascript
457
+ const walk = new SpriteAnimation(tile(0, 16), 4, .1); // four frames, a tenth of a second each
458
+ const attack = new SpriteAnimation(tile(4, 16), 3, .05).play(); // once, then holds the last frame
459
+
460
+ // in update()
461
+ this.tileInfo = (attack.isDone ? walk : attack).tileInfo;
462
+ ```
463
+
464
+ To do it by hand, use the `TileInfo.frame(n)` method to offset along the row of frames from a base sprite:
465
+
466
+ ```javascript
467
+ // store the base sprite (the first frame of the animation)
468
+ const playerSprite = tile(0, 16);
469
+
470
+ // inside update(), advance the frame based on time
471
+ const animationFrame = Math.floor(time * 10) % 4; // 4-frame loop at 10 FPS
472
+ this.tileInfo = playerSprite.frame(animationFrame);
473
+ ```
474
+
475
+ `time` is a global updated by the engine each frame (in seconds). The `% 4` keeps the index inside the 4 frames you actually drew on the sheet — adjust to match your animation length.
476
+
477
+ ### How do I control the camera in LittleJS?
478
+
479
+ LittleJS uses a world space rendering system, so objects can move independently of the camera.
480
+ The camera is easy to control using cameraPos and cameraScale, which indicate the world space position and how many pixels is equivalent to one world unit.
481
+ The default cameraScale is 32 while the default cameraPos is just the origin.
482
+ It is also possible to draw using screen space pixel coordinates by passing in true as the screenSpace parameter to most drawing functions.
483
+
484
+ ```javascript
485
+ setCameraPos(vec2(22,5)); // move camera to world position (22,5)
486
+ setCameraScale(20); // zoom camera to 20 pixels per world unit
487
+ ```
488
+
489
+ ### How do I use post-processing shaders?
490
+
491
+ LittleJS supports Shadertoy-style fragment shaders as a final pass on the rendered output via the post-process plugin. Create the plugin with your shader source in `gameInit`, once `engineInit` has set up WebGL:
492
+
493
+ ```javascript
494
+ const shader = `
495
+ void mainImage(out vec4 c, vec2 p) {
496
+ vec2 uv = p / iResolution.xy;
497
+ c = texture(iChannel0, uv);
498
+ c.rgb *= 1.0 - distance(uv, vec2(0.5)) * 0.7; // vignette
499
+ }`;
500
+
501
+ function gameInit()
502
+ {
503
+ new PostProcessPlugin(shader);
504
+ }
505
+
506
+ engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost);
507
+ ```
508
+
509
+ The shader gets these uniforms automatically:
510
+
511
+ - `iChannel0` (`sampler2D`) — the rendered game frame
512
+ - `iChannel1` (`sampler2D`) — the shader's own output from the previous frame, when `feedbackTexture` is on
513
+ - `iResolution` (`vec3`) — canvas width, height, and `1`
514
+ - `iTime` (`float`) — seconds since engine start
515
+
516
+ The constructor has two optional positional arguments after the shader source:
517
+
518
+ ```javascript
519
+ new PostProcessPlugin(shader, includeMainCanvas, feedbackTexture);
520
+ ```
521
+
522
+ - `includeMainCanvas` (default `false`) composites the Canvas2D layer (where some debug and text rendering goes) onto the WebGL canvas before the shader runs. Pass `true` if your post-process effect should apply to *everything* on screen, not just the WebGL-rendered objects.
523
+ - `feedbackTexture` (default `false`) also passes the shader's own output from the previous frame as `iChannel1`, for effects like motion trails or feedback loops; `iChannel0` is still the frame just drawn. Mutually exclusive with `includeMainCanvas`.
524
+
525
+ See [plugins/postProcess.js](plugins/postProcess.js) and the [Breakout example](https://killedbyapixel.github.io/LittleJS/examples/breakout/) for working post-process effects.
526
+
527
+ ### How do I play sounds in LittleJS?
528
+
529
+ Sounds can either be generated on startup using ZzFX or loaded from a wave or mp3 file.
530
+ [ZzFX sounds can be created using the sound designer app.](https://killedbyapixel.github.io/ZzFX/)
531
+ Once loaded sounds can be played by calling Sound.play with some parameters to control how it is played.
532
+
533
+ ```javascript
534
+ const sound_click = new Sound([1,.5]); // create a ZzFX sound
535
+ const sound_jump = new Sound('jump.mp3'); // load an mp3 sound
536
+ sound_click.play(pos, volume, pitch); // play a sound
537
+ ```
538
+
539
+ ### How do I play music?
540
+
541
+ For procedural chiptune music, use the `ZzFXMusic` class from the [ZzFXM plugin](plugins/zzfxm.js). It extends `Sound` and accepts a ZzFXM-format song array — the music is generated on the fly so it adds essentially no bytes to your build. For longer scored music, load an mp3 or ogg file with the regular `Sound` class and play it with `playMusic`, which loops by default.
542
+
543
+ ```javascript
544
+ // procedural ZzFXM music (tiny, generated at runtime)
545
+ const song = new ZzFXMusic(zzfxmSongData);
546
+ const songInstance = song.playMusic();
547
+
548
+ // or load an mp3/ogg and loop it
549
+ const track = new Sound('music.mp3');
550
+ const trackInstance = track.playMusic(1, true); // volume, loop
551
+ ```
552
+
553
+ Both pause automatically when the tab loses focus (audio context suspends) and resume when it comes back. `playMusic` returns a `SoundInstance`; keep it and call `songInstance.stop()` to halt playback.
554
+
555
+ ### Why doesn't my audio play until the user clicks something?
556
+
557
+ Browsers require a user interaction (click, key press, touch) before audio is allowed to play — this is a hard rule from the browser's autoplay policy, not something the engine can bypass. LittleJS handles the actual resume for you: the audio context starts in a suspended state and is automatically resumed on the first input event. You don't need to call `audioContext.resume()` yourself.
558
+
559
+ A sound played before that, in `gameInit()` or on the first frames, waits and starts once audio runs, so music started in `gameInit()` begins on the player's first click or key press.
560
+
561
+ ### If I load several images, how do I control which is used?
562
+
563
+ Pass multiple image paths to `engineInit` and select between them with the third argument to `tile()` (the `texture` index):
564
+
565
+ ```javascript
566
+ // load two images
567
+ engineInit(..., ['tiles.png', 'background.png']);
568
+
569
+ // later, draw a tile from each
570
+ const fgTile = tile(0, 16); // texture 0 → tiles.png
571
+ const bgTile = tile(0, 16, 1); // texture 1 → background.png
572
+ ```
573
+
574
+ The `texture` argument defaults to `0`, so single-image projects can ignore it. One thing to keep in mind for large games: switching between textures forces WebGL to flush its current render batch, so frequently alternating between textures within a single frame can hurt performance. Group draws by texture where you can.
575
+
576
+ ### How can I check if an object is on screen?
577
+
578
+ You can use the `isOverlapping` function to check the object against the camera's viewable window. For culling you might want to enlarge the object size slightly to account for attached objects or rotation — I usually do `this.size.scale(2)`.
579
+
580
+ ```javascript
581
+ if (!isOverlapping(this.pos, this.size, cameraPos, getCameraSize()))
582
+ return;
583
+ ```
584
+
585
+ `getCameraSize()` returns the viewable window in world units (canvas pixel size divided by `cameraScale`).
586
+
587
+ ---
588
+
589
+ ## Gameplay and Programming
590
+
591
+ ### How do I add keyboard or mouse input to my game?
592
+
593
+ LittleJS provides input handling functions for keyboard, mouse, and gamepads. Touch input is also routed to the mouse. There are functions for isDown, wasPressed, and wasReleased. Input can only be checked during the update and should not be called from render functions.
594
+
595
+ ```javascript
596
+ if (keyIsDown('ArrowLeft')) // Left arrow key
597
+ obj.pos.x -= 5;
598
+ if (mouseWasReleased(0)) // Left mouse button
599
+ console.log('Mouse clicked at:', mousePos);
600
+ if (gamepadWasPressed(0)) // Gamepad button 0
601
+ console.log('Gamepad pressed, stick is:', gamepadStick(0));
602
+ ```
603
+
604
+ Keyboard keys are identified by `KeyboardEvent.code` strings (e.g. `'KeyA'`, `'Space'`, `'ArrowUp'`, `'Enter'`). See the [MDN code reference](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values) for the full list.
605
+
606
+ ### How does touch input work on mobile?
607
+
608
+ Touch input is routed to the mouse functions automatically — `mouseIsDown(0)`, `mouseWasPressed(0)`, and `mousePos` all work with a finger tap on the canvas. Games written for mouse usually work on mobile without code changes.
609
+
610
+ For games that need movement and multiple buttons, enable the built-in on-screen gamepad:
611
+
612
+ ```javascript
613
+ setTouchGamepadEnable(true);
614
+ ```
615
+
616
+ Once enabled, an analog stick and face buttons appear on touch devices, and the regular gamepad input functions (`gamepadIsDown`, `gamepadStick`) work normally — so the same code can drive both a physical gamepad and the touch overlay. Customization helpers like `setTouchGamepadSize`, `setTouchGamepadAnalog`, `setTouchGamepadButtonCount`, and `setTouchGamepadAlpha` let you tune the appearance.
617
+
618
+ You can also branch behavior on `isTouchDevice` if you need to detect touch hardware explicitly.
619
+
620
+ ### How do I create and update game objects?
621
+
622
+ LittleJS can be used as an object oriented system by extending the base class [EngineObject](https://github.com/KilledByAPixel/LittleJS/blob/main/src/engineObject.js) with your own. This lightweight class provides many useful features including physics, collision, parent/child system, and sorted rendering. These objects are added to the global list of objects where they will automatically be updated and rendered until destroyed.
623
+
624
+ Here is a template you can use to make objects that behave however you want. See the examples for a complete demonstration.
625
+
626
+ ```javascript
627
+ class MyObject extends EngineObject
628
+ {
629
+ constructor(pos, size, tileInfo)
630
+ {
631
+ super(pos, size, tileInfo);
632
+ // setup object
633
+ }
634
+
635
+ update()
636
+ {
637
+ // custom logic; physics runs separately in updatePhysics
638
+ super.update();
639
+ }
640
+
641
+ render()
642
+ {
643
+ // draw object as a sprite
644
+ super.render();
645
+ }
646
+ }
647
+
648
+ // spawn one of those objects
649
+ const object = new MyObject(pos, size, tileInfo);
650
+ ```
651
+
652
+ Common things you'll do on an `EngineObject`:
653
+
654
+ - `this.setCollision()` — opt in to tile collision and object-object collision
655
+ - `this.tileInfo = tile(7, 16)` — swap the sprite at runtime
656
+ - `this.velocity = vec2(.1, 0)` — physics is applied automatically each frame
657
+ - `this.gravityScale = 0` — disable gravity for this object (e.g. UI or floating objects)
658
+ - `this.destroy()` — remove from the world (calls cleanup on children too)
659
+
660
+ ### How do I use parent/child objects?
661
+
662
+ `EngineObject` supports a parent/child hierarchy with local transforms. Attached children automatically follow their parent's position, angle, and mirror state each frame:
663
+
664
+ ```javascript
665
+ const ship = new EngineObject(vec2(10, 5), vec2(2));
666
+ const turret = new EngineObject(vec2(), vec2(.5));
667
+ ship.addChild(turret, vec2(0, .5)); // turret sits .5 units above the ship in local space
668
+ ```
669
+
670
+ `addChild(child, localPos, localAngle)` attaches the child with a local offset; the child's world-space `pos` and `angle` are recomputed each frame from the parent's transform. To detach, call `parent.removeChild(child)` — or just `child.destroy()`, which also detaches it (and destroying a parent destroys all its children).
671
+
672
+ A few things to know:
673
+ - **Mirroring a parent flips children** — a mirrored ship has its turret on the correct side automatically (`localPos.x` and `localAngle` are inverted).
674
+ - **Children have no physics of their own** — they follow the parent's transform, so a child's velocity, gravity and collision do nothing while it is attached. Detach it with `removeChild` to let physics move it again.
675
+
676
+ ### How do collisions work?
677
+
678
+ Opt in to collision on an `EngineObject` by calling `setCollision()` — by default it's off so most objects don't pay the collision cost.
679
+
680
+ ```javascript
681
+ class Player extends EngineObject {
682
+ constructor(pos) {
683
+ super(pos, vec2(1), tile(0, 16));
684
+ this.setCollision(); // collide with solid objects and tile map
685
+ }
686
+ }
687
+ ```
688
+
689
+ `setCollision(collideSolidObjects, isSolid, collideLevel, collideRaycast)` — all default to `true`. Pass `false` to opt out of any of them (e.g. a ghost that passes through walls: `setCollision(false, false, false)`).
690
+
691
+ For custom collision logic, override `collideWithObject(other)` and `collideWithTile(tileData, pos)` on your subclass. Return `true` to actually block the collision, `false` to pass through. Both fire during the physics step, and `collideWithTile` can fire several times a frame for the same tile, including for positions the object only tries, so guard side effects like damage to once a frame:
692
+
693
+ ```javascript
694
+ class Bullet extends EngineObject {
695
+ collideWithObject(other) {
696
+ if (other instanceof Enemy) {
697
+ other.damage(1);
698
+ this.destroy();
699
+ return false; // don't physically bounce off the enemy
700
+ }
701
+ return true; // solid collision with everything else
702
+ }
703
+ }
704
+ ```
705
+
706
+ For raycasting against the tile collision map (line-of-sight checks, hitscan weapons, etc.), use `tileCollisionRaycast(start, end)` which returns the hit position or `undefined`.
707
+
708
+ ### How can I load a 2D level map?
709
+
710
+ There are two parts that work together to make 2D level maps in LittleJS.
711
+ The tileCollision system is an array of values that correspond to each tile in the map.
712
+ The built in physics system can resolve collisions against this tile map very quickly.
713
+ For the visual side of things, there is a class called TileLayer which can be used in conjunction with tileCollision.
714
+ The TileLayer system works by pre-rendering the entire level to a texture so it can be drawn extremely fast each frame.
715
+ It is also possible to have multiple TileLayers for foreground and background layers.
716
+ [The platformer example shows a basic example of how to load data that was exported from an external editor.](https://killedbyapixel.github.io/LittleJS/examples/platformer/)
717
+
718
+ ### Can I use physics with LittleJS?
719
+
720
+ Yes! LittleJS comes with a robust game physics system included and [also a plugin using Box2D.](https://killedbyapixel.github.io/LittleJS/examples/box2d/)
721
+ The platformer example includes a character object class that can be used as a starting point for advanced platforming physics.
722
+
723
+ ### How do I add particle effects to my game?
724
+
725
+ [There is a particle system designer that is useful for experimenting with particle designs.](https://killedbyapixel.github.io/LittleJS/examples/particles/)
726
+
727
+ You can create a particle system in code using the ParticleEmitter object.
728
+
729
+ ```javascript
730
+ // fire particle system
731
+ new ParticleEmitter(
732
+ pos, 0, // pos, angle
733
+ 1, .1, 100, PI, // emitSize, emitTime, emitRate, emitCone
734
+ undefined, // tileInfo (undefined = untextured)
735
+ rgb(1,.5,.1), rgb(1,.1,.1), // colorStartA, colorStartB
736
+ rgb(1,.5,.1,0), rgb(1,.1,.1,0), // colorEndA, colorEndB
737
+ .7, .8, .2, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
738
+ .9, 1, -.2, PI, .05, // damp, angleDamp, gravity, particleCone, fade
739
+ .5, 0, 1, 0, 1e9 // randomness, collide, additive, colorLinear, renderOrder
740
+ );
741
+ ```
742
+
743
+ ### How do I use timers?
744
+
745
+ The `Timer` class is a small but very useful utility for anything that needs to happen *after some time has passed* — cooldowns, animation timing, delayed spawns, fades, etc. It tracks elapsed time automatically, so you don't have to add `timeDelta` to a variable every frame.
746
+
747
+ ```javascript
748
+ const cooldown = new Timer(2); // 2-second timer, starts immediately
749
+
750
+ function gameUpdate() {
751
+ if (cooldown.elapsed()) {
752
+ fireWeapon();
753
+ cooldown.set(2); // restart it
754
+ }
755
+ }
756
+ ```
757
+
758
+ Useful methods:
759
+ - `set(seconds)` — start or restart the timer
760
+ - `unset()` — disable it (`isSet()` becomes false)
761
+ - `active()` / `elapsed()` — boolean checks
762
+ - `get()` — seconds elapsed (negative while still counting down)
763
+ - `getPercent()` — 0 → 1 progress, handy for easing animations
764
+
765
+ By default, a timer ticks on **game time** — it pauses when the game is paused and slows down or speeds up with `setTimeScale`. For UI animations, on-screen notifications, or anything that should keep running regardless of game state, use a real-time timer:
766
+
767
+ ```javascript
768
+ const uiFade = new Timer(.5, true); // useRealTime = true
769
+ ```
770
+
771
+ Real-time timers track `timeReal` instead of `time` — unaffected by pause or time scale. You can also flip an existing timer between modes with `timer.setUseRealTime(true)` while it's unset.
772
+
773
+ ### How do I save and load game state?
774
+
775
+ LittleJS has built-in helpers for localStorage-backed save data — no need to roll your own JSON serialization:
776
+
777
+ ```javascript
778
+ // Read save data, falling back to defaults if nothing is saved yet
779
+ const saveData = readSaveData('MyGame', { highScore: 0, level: 1 });
780
+
781
+ // Write save data
782
+ writeSaveData('MyGame', { highScore: 99999, level: 5 });
783
+ ```
784
+
785
+ The `saveName` should be unique per game so multiple LittleJS games on the same host don't collide. The data object must be JSON-serializable (no functions, no circular references). Under the hood these are thin wrappers around `localStorage` + `JSON.stringify` / `JSON.parse`.
786
+
787
+ ### How do I use the medals (achievements) system?
788
+
789
+ LittleJS includes a medals plugin for tracking unlockable achievements with popup notifications:
790
+
791
+ ```javascript
792
+ // Create medals — each needs a unique id, optional emoji icon
793
+ const medal_firstWin = new Medal(0, 'First Win', 'Win your first match', '🏆');
794
+ const medal_perfect = new Medal(1, 'Perfect Score', 'Score 100%', '⭐');
795
+
796
+ // Initialize medals — saveName persists unlocks to localStorage, a different name from your save data
797
+ medalsInit('MyGame medals');
798
+
799
+ // Unlock a medal (shows a popup, persists across visits)
800
+ medal_firstWin.unlock();
801
+ ```
802
+
803
+ You can pass an image URL as the fifth argument to `Medal` instead of an emoji icon. The `saveName` you pass to `medalsInit` is used to track which medals have been unlocked in localStorage, so unlocks persist across visits. Give it a name of its own, not the one you pass to `readSaveData` and `writeSaveData`, or the medals and your save data overwrite each other.
804
+
805
+ The plugin also supports [Newgrounds](https://www.newgrounds.com) scoreboards and medals held on the server through [plugins/newgrounds.js](plugins/newgrounds.js): create each medal as a `NewgroundsMedal` with the id Newgrounds gave it, call `medalsInit` as usual, then `new NewgroundsPlugin(app_id, cipher)`. The "LittleJS Medals & Newgrounds" section of REFERENCE.md has a short example.
806
+
807
+ ---
808
+
809
+ ## Debugging and Development
810
+
811
+ ### How do I debug my game in LittleJS?
812
+
813
+ In addition to your browser's built-in developer tools, LittleJS has its own debug overlay. Press `Esc` to show it — the overlay lists the available toggles and their number-key bindings, so you don't have to memorize them. `+` and `-` adjust the time scale, which is great for slowing tricky moments down or speeding past a slow section.
814
+
815
+ **Debug draw functions** let you visualize state directly in the world without committing to permanent UI. They're drawn for one frame and only render in debug builds:
816
+
817
+ ```javascript
818
+ debugRect(pos, size, color); // outline a rectangle
819
+ debugCircle(pos, diameter, color); // outline a circle
820
+ debugLine(posA, posB, color); // a line between two points
821
+ debugText(string, pos, size, color); // text at a world position
822
+ debugPoint(pos, color); // small marker
823
+ ```
824
+
825
+ These are perfect for "where exactly is this AI heading?" or "is this hitbox where I think it is?" investigations — drop a call into your update or render code, see the answer, then delete it.
826
+
827
+ **`ASSERT` and `LOG`** are similar to `console.assert` / `console.log`, but they're *stripped from release builds* (`littlejs.release.js`, `littlejs.min.js`). That means you can leave them in your code with no cost to shipped users. Don't rely on side effects inside the arguments — they won't run in release.
828
+
829
+ ```javascript
830
+ ASSERT(player.health > 0, 'player damaged below zero', player);
831
+ LOG('spawned enemy at', pos);
832
+ ```
833
+
834
+ ---
835
+
836
+ ## Contribute to the FAQ
837
+ If you have additional questions or think something should be added to this FAQ, please open an issue or pull request on the [LittleJS GitHub repository](https://github.com/KilledByAPixel/LittleJS).
838
+
839
+ ---
840
+
841
+ Happy coding with LittleJS! 🎮🚂💨