Game
Back to the reference

Game

Starting an instance, the handle a running game exposes, its events, and what the renderer resolved to.

14 symbols

createGamefunctionnacatamalon

createGame(target: TGameTarget, game_options: TGameOptions) => (scenes: Readonly<Record<string, TSceneFn>>, initialScene?: string) => TGameInstance

Puts a game in the page: a canvas inside target, drawn at width × height.

It is called twice. The first call makes the canvas and returns a function; calling that with the game's scenes starts it, on the first one or on the one named second. A scene is named by its key, which is how useScene().change('Level') finds it later.

Nothing waits. The renderer starts on its own time, so what comes back is the running game at once: on('ready', …) hears when it is up, and on('error', …) why it could not start.

Parameters

targetTGameTarget

Where the canvas goes: a CSS selector, an element, or null for the page's body.

game_optionsTGameOptions

Its resolution, background, and how it scales to the page. See TGameOptions.

Example

const Title: TSceneFn = () => createScene();
const Level: TSceneFn = () => createScene();

const game = createGame('#game', {
    width: 320,
    height: 224,
    background: getColor('#000000'),
    scaling: 'integer',
})({ Title, Level }, 'Title');

game.on('error', (error) => console.error(error));
View source · game/bootstrap/create_game.ts:86

GAME_CONFIGvariablenacatamalon

GAME_CONFIG: { RENDER_TYPE }

The renderers' names, for createGame's renderer option: GAME_CONFIG.RENDER_TYPE.WEBGL2 is the same as writing 'WEBGL2'.

Properties

RENDER_TYPE{ AUTO, WEBGPU, WEBGL2 }
View source · CONST.ts:22

useGamefunctionhooknacatamalon

useGame() => TGameHandle

The game itself: its size, its background, whether images are kept crisp, how fast time runs, and whether everything is frozen.

These are the settings that belong to the whole game rather than to one scene, and until now they could only be chosen once, when the game was created. This is how a pause menu, an options screen or a boss entrance changes them while it runs.

Call it while the scene is being built, like every hook, and keep what it returns: the handle stays live, so reading it in a frame long afterwards still gives today's answer.

Example

export const Level: TSceneFn = () => {
    const game = useGame();
    const keys = useKeyboard();

    useUpdate(() => {
        // A pause menu: everything stops, everything is still drawn.
        if (keys.justPressed('Escape')) {
            game.isPaused() ? game.resume() : game.pause();
        }
        // Slow motion while the key is held.
        game.setTimeScale(keys.isDown('Shift') ? 0.25 : 1);
    });

    return createScene();
};
View source · hooks/game/use_game.ts:41

VERSIONvariablenacatamalon

VERSION: string

The engine's version, as a string: what the console prints when a game starts, and what a bug report should quote.

It is read from the package's own package.json, so it is always the version that is running.

View source · version.ts:13

TCanvasKeeptypenacatamalon

TCanvasKeep: 'both' | 'height' | 'width' | 'none'

Which sides of the resolution stay pinned. Writes the drawing buffer, not CSS: anything but 'both' makes the game see more or less world instead of leaving bars.

  • 'both': the buffer is always width × height. Every player sees exactly the same frame, and space the scale cannot fill stays as letterbox or pillarbox bars. The default, and the only value where a level designer can be sure what is off-screen.
  • 'height': the height is pinned and the width follows the container, so a wide screen sees more world to the sides. What a side-scroller usually wants.
  • 'width': the width is pinned and the height follows, so a tall screen sees more above and below. The vertical-shooter version of the same idea.
  • 'none': nothing pinned. With no fixed side there is no reference to compute a scale from, so it stays at 1× and the buffer becomes the container's size in CSS pixels. Desktop-app behaviour, not game behaviour.

Pairs with TCanvasScaling, which is the other axis and decides how big it is drawn.

View source · DOM/types/t_canvas_keep.ts:22

TCanvasScalingtypenacatamalon

TCanvasScaling: 'none' | 'integer' | 'contain' | 'fill'

How much the game's image is magnified. Writes CSS, never the drawing buffer: the game keeps drawing the same number of pixels, only the size they are painted at changes.

  • 'none': 1×. The canvas is painted at exactly width × height CSS pixels and ignores the space around it. The default, and what a fixed-size embed wants.
  • 'integer': the largest whole multiple that fits (2×, 3×, 4×…). The pixel-art one: every game pixel lands on an exact square block, so nothing is resampled and no row ends up one pixel taller than its neighbour. Leaves bars when the fit is not exact. Below 1× there is no whole number to snap to, so it falls back to the exact fit.
  • 'contain': the largest fit, fractions allowed (2.37×). Fills more of the container than 'integer' and keeps the aspect ratio, at the cost of resampling: pixel art shimmers, smooth art does not care.
  • 'fill': stretched to the container on both axes, aspect ratio broken. The only value that ignores keep: the buffer stays at the base resolution and CSS does the distorting. For a background or an effect where the shape does not matter.

Pairs with TCanvasKeep, which is the other axis and decides how much world is drawn.

View source · DOM/types/t_canvas_scaling.ts:24

TCaptureOptionstypenacatamalon

What a capture is a picture of. Every field left out means what the screen is showing, so capture() with nothing is a picture of the screen.

In the cameras and in layers, leaving a field out and writing null differ, and the difference is the reason a capture has options at all. Left out, it is the screen's: the camera a tool is flying, the kinds it is hiding. null is the game's: each scene's own camera, or none, and every kind. A tool working on the flat half of a level hides the models and looks through a camera of its own, and a picture of the game taken from it has to undo both.

Properties

camera3doptionalTCamera3d | null

The 3D camera to look through. Left out, the screen's: the tool's, or else each scene's own. null, each scene's own.

camera2doptionalTCamera2d | null

The 2D camera to look through. Left out, the screen's. null, each scene's own, and a scene with none is drawn in screen pixels, which is what that game shows.

widthoptionalnumber

Width of the picture in pixels. Left out, the canvas's.

heightoptionalnumber

Height of the picture in pixels. Left out, the canvas's.

layersoptionalReadonlyArray<indexedAccess> | null

The kinds of drawing to keep. Left out, what the screen keeps; null, all of them.

backgroundoptionalTColor

The colour behind everything. Left out, the game's background.

View source · game/capture/types/t_capture_options.ts:20

TGameEventstypenacatamalon

What a running game tells the page around it, and what each event carries.

  • ready: the renderer is up and the first scene has run. Carries the game's handle, which is how anything outside a scene (a React HUD, a debug panel) gets to talk to the game.
  • error: the game could not start, most often because the browser has neither WebGPU nor WebGL2, or a scene's update threw once it was running. Carries why, so the page can say so instead of showing an empty box. A game that throws goes on running, so this is heard once, for the first error.
  • destroy: the game has been taken down and its canvas removed.

Properties

errorError
destroyvoid
View source · game/types/t_game_events.ts:18

TGameHandletypenacatamalon

What a game can do with itself while it runs: the settings that belong to the whole game rather than to any one scene.

Everything here is live. Read it in a frame, change it from a menu, and the next frame is drawn with the answer.

Properties

getWidth
getHeight
getBackground
setBackground
isSmooth
setSmooth
getTimeScale
setTimeScale
pause
resume
isPaused
getBackend
getFps
getSceneCount
getDrawableCount
enterFullscreen
exitFullscreen
isFullscreen
View source · game/handle/t_game_handle.ts:15

TGameInstancetypenacatamalon

What the second call of createGame returns: the handle to a running game.

destroy - Stops the loop, releases the renderer and the store, and removes the canvas. Safe to call at any time; subsequent calls have no effect.

on - Listens to what the game tells the page around it: 'ready', 'error' and 'destroy'.

Properties

destroy
on
View source · game/types/t_game_instance.ts:14

TGameOptionstypenacatamalon

How a game's window is set up, for createGame: its resolution, the colour behind everything, how it scales to the page and which renderer draws it. Only width and height are required.

Properties

widthnumber

The game's resolution: how many pixels it draws, not how big it looks. 320 × 224 stays 320 × 224 however large the canvas ends up on screen: scaling changes the size it is painted at, and keep is the only thing that changes these numbers.

A camera measures in these units, so this is what decides how much of a level fits.

heightnumber

See TGameOptions.width: the two are one decision.

backgroundoptionalTColor

Colour the frame is cleared to before anything draws. Defaults to darkviolet, deliberately loud: a scene that renders nothing should be obvious, not black.

Read live from the store every frame, so changing it at runtime takes effect on the next one.

rendereroptionalTRendererType

Which backend draws.

  • 'AUTO' (default): tries WebGPU and falls back to WebGL2 if it fails to start.
  • 'WEBGPU': WebGPU or nothing. Throws instead of falling back.
  • 'WEBGL2': WebGL2 or nothing, same rule.

Naming one is for testing that backend: a silent fallback would hide the very thing being tested. What actually won is capabilities.backend.

seedoptionalnumber

Initial seed for this game's random generator (useRandom). Absent means a different run every time; set it to make a run reproducible: same seed, same sequence.

smoothoptionalboolean

Default texture filtering for sprites. false (default) renders them pixelated (nearest) for crisp pixel art; true renders them smooth (linear).

msaaoptional1 | 4

MSAA (multisample anti-aliasing) sample count. 1 (default) disables it: the raw, aliased look that suits a PS1/N64 aesthetic. 4 enables 4× MSAA, smoothing jagged geometry silhouettes (e.g. a 3D model's edges). Only 1 and 4 are guaranteed across devices. It smooths edges only, not texture shimmer.

scalingoptionalTCanvasScaling

How big the game's image is painted. Writes CSS only: the game keeps drawing width × height pixels and only their size on screen changes.

  • 'none' (default): 1×, exactly width × height CSS pixels.
  • 'integer': the largest whole multiple that fits. The pixel-art one, because nothing is resampled, at the cost of bars when the fit is not exact.
  • 'contain': the largest fit with fractions allowed. Fills more, resamples.
  • 'fill': stretched to the container, aspect ratio broken. Ignores keep.

See TCanvasScaling.

keepoptionalTCanvasKeep

Which sides of the resolution stay fixed. This one writes the buffer: anything but 'both' makes the game see more or less world instead of leaving bars.

  • 'both' (default): always width × height. What makes a game look the same on every screen; the leftover space becomes letterbox or pillarbox bars.
  • 'height': height pinned and width follows the container, so a wide screen sees more to the sides.
  • 'width': width pinned and height follows, so a tall screen sees more above and below.
  • 'none': nothing pinned, so there is no reference to scale from. Stays at 1× and the buffer becomes the container's size.

See TCanvasKeep.

pixelRatiooptionalnumber | 'device'

How many real pixels each game pixel is drawn with. 1 (default) draws exactly width × height; 2 draws four times as many pixels in the same space; 'device' follows the screen's devicePixelRatio (2 on most phones and Retina displays, 3 on some) and keeps following it if the window moves to another monitor.

Only the drawing gets sharper. width and height are still the game's size: a camera, a position, the pointer and getWidth() all keep measuring in them, so nothing in the game changes. What it buys is edges: a 1080 × 720 game on a Retina screen stops being upscaled by the browser, and 3D silhouettes, far textures and thin geometry come out clean.

Leave it at 1 for pixel art. A sprite that is not rotated or scaled looks exactly the same (each texel just becomes a bigger block of real pixels), so it gains nothing; a rotated or scaled one loses the stepped edges of the era and looks like a modern game. And it is not free: the cost of every pass grows with the square of the number, four times the pixels at 2 and nine at 3, which is what 'device' asks of many phones.

Screen effects keep the game's pixel: the resolution a full-screen effect reads is width × height, so a dither or a palette pattern stays the size of a game pixel instead of getting finer.

fullscreenScalingoptionalTCanvasScaling

How the game's image is painted while it is full screen (enterFullscreen on the game handle). Same values as scaling, and only used while full screen lasts: leaving puts scaling back.

'integer' (default) is the pixel-art answer, whole multiples with bars round the edge in the game's background colour. 'contain' fills more of the monitor and resamples to do it.

It is separate from scaling because the two places want different things: a game sitting in the middle of a page at 1× wants the whole monitor once the player asks for it.

pauseOnBluroptionalboolean

Freeze the loop while nobody can see the page, and unfreeze on the way back. true by default.

It exists because requestAnimationFrame stops in a hidden tab anyway: without this, the first frame back gets a delta of however long the player was away, and everything moving teleports. Pausing makes that gap explicit instead of letting it reach the game.

The page being hidden pauses the game the way a pause menu does, with the reason 'browser', so gamePaused and gameResumed are heard for it. Coming back releases only that hold, so a game paused from a menu stays paused; it resumes one frame after the page is seen again, so the frame that measured the time away is not run. false leaves the game to the browser, which stops sending frames to a hidden tab anyway.

banneroptionalboolean

Print the startup line in the console: the engine, its version and which backend started. true by default.

On by default because the backend is the first question asked of any drawing bug, and the line answers it in one screenshot. A shipped game that wants a clean console turns it off.

splashoptionalboolean | 'always'

Open with "Made with NacatamalOn": the N grows in, the name slides in beside it, and the game starts. Off unless asked for.

  • true shows it in the published game and skips it while you develop: on localhost (and 127.0.0.1, ::1, .local names) and in a browser driven by automation (Playwright, Puppeteer, Selenium), so neither a reload nor a test ever waits for it.
  • 'always' shows it there too, to see it while working on the game.
  • false, or leaving it out, never shows it.

It costs the game no time. The first scene is built and loads its files while the splash plays, and the splash fades into it once those have arrived, so on a game with a lot to load it is the loading screen. It lasts under two seconds, and a key or a click ends it. It never shows when a tool drives the game (editorHandleOf).

actionsoptionalTActionMap

The game's input map: named actions ('jump', 'move_left') and the keys, gamepad buttons and stick directions each one listens to, read with useActions.

The same list a project.json carries, which is where it comes from once there is an editor.

actionsPersistoptionalTActionPersist

Where the player's own rebindings are kept, so they are still there next time. Left out, the controls are whatever the game says every run.

postoptionalTPostChain

The full-screen effects this game is shown through, in order.

A game setting rather than a scene's, because "this looks like a Mega Drive" is a fact about the game and not about whichever room happens to be open: a scene that had to declare the palette again would be a scene that can forget to.

Nothing is waited for. The effects go in at once and each starts working the moment its own file lands, so a slow palette costs you the palette and never the first seconds of the game.

View source · game/types/t_game_options.ts:16

TGameTargettypenacatamalon

TGameTarget: string | HTMLElement | null

Where a game puts its canvas: a CSS selector or an element to put it in (a <canvas> is drawn on as it is), or null for the page's body.

View source · game/bootstrap/create_game.ts:29