Skip to content

@gameable/gameable-character ​

Classes ​

GameableCharacterError ​

A character could not be loaded; code says why, message says what to do.

Example ​

ts
import { GameableCharacterError, loadGameableCharacter } from 'gameable/three';

try {
  await loadGameableCharacter(renderer, url);
} catch (error) {
  if (error instanceof GameableCharacterError && error.code === 'webgpu-required') {
    console.warn('this browser cannot show the character yet');
  }
}

Extends ​

  • Error

Constructors ​

Constructor ​
ts
new GameableCharacterError(
   code, 
   message, 
   options?
): GameableCharacterError;
Parameters ​
ParameterTypeDescription
codeGameableCharacterErrorCodeWhy.
messagestringWhat happened and what to do.
options?{ cause?: unknown; }The underlying error, when there is one.
options.cause?unknown-
Returns ​

GameableCharacterError

Overrides ​
ts
Error.constructor

Properties ​

code ​
ts
readonly code: GameableCharacterErrorCode;

Why.

Interfaces ​

GameableCharacter ​

A loaded character.

Example ​

ts
scene.add(character.object3D);
character.play('idle');
character.setExpression('arkit52', { jawOpen: 0.3 });
// every frame: character.update(dt, camera)

Properties ​

castShadow ​
ts
castShadow: boolean;

Whether the character casts a shadow into your scene's shadow maps, from its body mesh (a character whose package has none casts nothing). The mesh is never drawn to the screen.

clip ​
ts
readonly clip: string;

The clip playing now (the one fading in, during a fade).

clips ​
ts
readonly clips: readonly string[];

The clip names this character can play, e.g. idle, wave.

object3D ​
ts
readonly object3D: Object3D;

Add this to your scene. Move, turn and parent it like any Object3D; its feet are at its origin.

Methods ​

dispose() ​
ts
dispose(): void;

Remove the character from its parent and free its GPU memory. Idempotent.

Returns ​

void

Nothing.

lookAt() ​
ts
lookAt(target, options?): void;

Look at something: the eyes, the head and a little of the upper body turn toward it, over whatever clip plays, and follow it while it moves (an Object3D such as your camera is read every frame; so is a Vector3 you keep changing). null hands the head back to the clip.

The shares are the Gameable studio's defaults ("Looks at you"): the face turns head of the way (0.55, a tenth of the look taken by the upper body), and the eyes take eyes of what is left (1: all of it). The head turns at most 45 degrees from the body, the eyes 35 more.

Parameters ​
ParameterTypeDescription
targetObject3D<Object3DEventMap> | Vector3 | nullWhat to look at, in world space, or null.
options?LookAtOptionsEach part's share, 0..1.
Returns ​

void

Nothing.

Example ​
ts
character.lookAt(camera); // the visitor
character.lookAt(new Vector3(2, 1.5, 0), { head: 0.3 }); // a shelf, mostly with the eyes
character.lookAt(null); // back to the clip
play() ​
ts
play(name, options?): void;

Play a body clip by name.

Playing the clip that is already playing does nothing: a one-shot runs on to its end (and returns to the looping clip as it would have), a looping clip keeps looping.

Parameters ​
ParameterTypeDescription
namestringOne of clips.
options?PlayOptionsFade time and looping.
Returns ​

void

Nothing.

Throws ​

When the character has no clip by that name.

setExposure() ​
ts
setExposure(exposure): void;

Multiply the character's brightness, on top of the tint.

Parameters ​
ParameterTypeDescription
exposurenumber1 is as captured; 1.2 is 20 % brighter.
Returns ​

void

Nothing.

Example ​
ts
character.setExposure(0.9);
setExpression() ​
ts
setExpression(space, weights): void;

Set the face, in Apple ARKit's 52 blendshapes: an array of 52 weights in ARKit order, or named weights ({ jawOpen: 0.4, eyeBlinkLeft: 1 }; names not given are 0; any capitalisation). The weights hold until the next call. Blinking carries on on top.

Parameters ​
ParameterTypeDescription
space"arkit52"'arkit52'.
weightsArrayLike<number> | Readonly<Record<string, number>>Weights in [0, 1]; one outside is clamped into it (a NaN is 0).
Returns ​

void

Nothing.

setTint() ​
ts
setTint(color): void;

Multiply the character's colour (linear light): a tint to fit it into your scene's light.

Parameters ​
ParameterTypeDescription
colorColorRepresentationAny three colour: '#ffe6cc', 0xffe6cc, a Color. White is none.
Returns ​

void

Nothing.

Example ​
ts
character.setTint('#ffe6cc'); // a warm room
update() ​
ts
update(dt, camera): void;

Advance the animation and pose the character. Call once per frame, before renderer.render(scene, camera).

Parameters ​
ParameterTypeDescription
dtnumberSeconds since the last frame.
cameraCameraThe camera you render with (the character's colours depend on the view).
Returns ​

void

Nothing.


LoadGameableCharacterOptions ​

Options for loadGameableCharacter.

Example ​

ts
const character = await loadGameableCharacter(renderer, url, {
  clip: 'wave',
  fetch: (input, init) => fetch(input, { ...init, credentials: 'include' }),
});

Properties ​

allowSoftwareRenderer? ​
ts
readonly optional allowSoftwareRenderer?: boolean;

Draw even when the renderer runs in software (no GPU; see isSoftwareRenderer). Default false: the load throws software-renderer there, before anything is downloaded, since a character draws at well under a frame a second on the CPU and freezes the page.

allowWebGL? ​
ts
readonly optional allowWebGL?: boolean;

Draw on the WebGL2 fallback, when the browser has no WebGPU. Default true: the same passes run there as transform feedback, slower, and the gaussians' draw order is sorted on the CPU and lags the camera by a frame or a few. False refuses the fallback: the load throws webgpu-required, for an app that would rather show something else.

attachPass? ​
ts
readonly optional attachPass?: boolean;

Attach the sRGB pass (see attachSrgbPass) to the character's scene on its first update, with the scene's onBeforeRender / onAfterRender hooks. Default true. False for an app that assigns those hooks itself or renders through something that skips them: it attaches the pass with attachManualPass and calls begin(camera) just before its render and end() just after.

castShadow? ​
ts
readonly optional castShadow?: boolean;

Cast a shadow into your scene's shadow maps (the renderer's shadowMap.enabled and a light with castShadow), from the character's body mesh, skinned to its skeleton. Default false: it costs a skinned draw per shadow-casting light each frame (six for a point light). The mesh is made the first time it turns on. See castShadow.

clip? ​
ts
readonly optional clip?: string;

The clip to start with. Defaults to idle when the character has one, else its first.

exposure? ​
ts
readonly optional exposure?: number;

A multiplier on the character's brightness, like renderer.toneMappingExposure. Default 1.

fetch? ​
ts
readonly optional fetch?: (input, init?) => Promise<Response>;

Fetch every file of the package with this instead of the global fetch (for credentials, a proxy, or an asset manager). Same signature as fetch.

Parameters ​
ParameterType
inputURL | RequestInfo
init?RequestInit
Returns ​

Promise<Response>

mouth? ​
ts
readonly optional mouth?: boolean;

Draw the inside of the mouth (teeth, gums, tongue) when the lips part, for a package that carries the teeth's files. Default false, as on the studio's own stage, where it is off unless switched on.

onProgress? ​
ts
readonly optional onProgress?: (loadedBytes, totalBytes) => void;

Told as the character's files arrive: the bytes so far and the bytes expected in all (the sizes the package lists, its shared clip pack included). Both count the files as they are, not as they travel compressed. The last call has loadedBytes === totalBytes; the character is then built on the GPU (a moment more) before loadGameableCharacter resolves.

Parameters ​
ParameterType
loadedBytesnumber
totalBytesnumber
Returns ​

void

Example ​
ts
onProgress: (loaded, total) => (bar.value = total > 0 ? loaded / total : 0),
signal? ​
ts
readonly optional signal?: AbortSignal;

Aborts the download.

tint? ​
ts
readonly optional tint?: ColorRepresentation;

A colour to multiply the character's by (in linear light), to fit it into your scene's light: '#ffe6cc' warms it. White (the default) keeps the captured colours. See setTint.


LookAtOptions ​

Each part's share of a look (see GameableCharacter.lookAt).

Example ​

ts
const mostlyEyes: LookAtOptions = { head: 0.3, eyes: 1 };
character.lookAt(camera, mostlyEyes);

Properties ​

eyes? ​
ts
readonly optional eyes?: number;

How much of what the head leaves the eyes take. Default 1.

ts
readonly optional head?: number;

How far the face turns toward the target, of the whole look. Default 0.55.


ManualPass ​

The characters' pass driven by the app (see attachManualPass).

Example ​

ts
const pass: ManualPass = attachManualPass(renderer, scene);
pass.begin(camera);
renderer.render(scene, camera);
pass.end();

Methods ​

begin() ​
ts
begin(camera): void;

Call just before renderer.render(scene, camera): draws the characters for this camera.

Parameters ​
ParameterTypeDescription
cameraCameraThe camera the app renders with.
Returns ​

void

Nothing.

dispose() ​
ts
dispose(): void;

Take the pass off the scene.

Returns ​

void

Nothing.

end() ​
ts
end(): void;

Call just after the render.

Returns ​

void

Nothing.


PlayOptions ​

Options for GameableCharacter.play.

Example ​

ts
character.play('wave', { fade: 0.4 });

Properties ​

fade? ​
ts
readonly optional fade?: number;

Cross-fade time from the current clip, seconds. Default 0.25.

loop? ​
ts
readonly optional loop?: boolean;

Loop the clip. Defaults to what the clip says (the studio marks one-shot gestures such as a wave as not looping). A clip that does not loop fades back to the previous looping clip when it ends.

Type Aliases ​

GameableCharacterErrorCode ​

ts
type GameableCharacterErrorCode = 
  | "webgpu-required"
  | "renderer-not-ready"
  | "load-failed"
  | "software-renderer";

Why a character could not be loaded.

Example ​

ts
import type { GameableCharacterErrorCode } from 'gameable/three';
const code: GameableCharacterErrorCode = 'webgpu-required';

Variables ​

PACKAGE ​

ts
const PACKAGE: "gameable/three";

Package identity marker.

Example ​

ts
import { PACKAGE } from 'gameable/three';
console.log(PACKAGE); // 'gameable/three'

Functions ​

attachManualPass() ​

ts
function attachManualPass(renderer, scene): ManualPass;

The pass the characters are drawn in, driven by the app instead of by the scene's onBeforeRender / onAfterRender hooks: for an app that assigns those itself, or renders through something that skips them. Load the characters with { attachPass: false }, attach this once per scene, and call begin(camera) just before each render and end() just after.

Parameters ​

ParameterTypeDescription
rendererWebGPURendererThe app's renderer.
sceneSceneThe scene the characters are in.

Returns ​

ManualPass

The pass.

Example ​

ts
const character = await loadGameableCharacter(renderer, url, { attachPass: false });
scene.add(character.object3D);
const pass = attachManualPass(renderer, scene);
renderer.setAnimationLoop(() => {
  character.update(timer.getDelta(), camera);
  pass.begin(camera);
  renderer.render(scene, camera);
  pass.end();
});

isSoftwareRenderer() ​

ts
function isSoftwareRenderer(renderer): boolean;

Whether the renderer runs in software: no GPU, the browser emulating one on the CPU (SwiftShader, llvmpipe, Windows' basic render driver). A character draws there at well under a frame a second, so loadGameableCharacter refuses it unless allowSoftwareRenderer is set; an app can ask first and show its placeholder straight away.

Parameters ​

ParameterTypeDescription
rendererWebGPURendererThe app's renderer, after await renderer.init().

Returns ​

boolean

True when it runs in software; false on a GPU or when the browser does not say.

Example ​

ts
await renderer.init();
if (isSoftwareRenderer(renderer)) showPlaceholder();
else scene.add((await loadGameableCharacter(renderer, url)).object3D);

loadGameableCharacter() ​

ts
function loadGameableCharacter(
   renderer, 
   url, 
   options?
): Promise<GameableCharacter>;

Load a character published from the Gameable studio into your own three.js app.

url is the character's character.json; every file it names is fetched relative to it and checked against its sha256 before use, so a URL on another origin is safe to load (the host must send CORS headers; the studio does). Versions 1 and 2 of the package format load, plain or packed.

The renderer must be a WebGPURenderer (from three/webgpu) on real WebGPU, initialised. The character uses the renderer's own device and needs no device limits above WebGPU's defaults, so there is nothing to call before renderer.init().

Parameters ​

ParameterTypeDescription
rendererWebGPURendererYour WebGPURenderer, after await renderer.init().
urlstringThe character's character.json.
optionsLoadGameableCharacterOptionsThe first clip, the mouth, tint, exposure and shadow, a custom fetch, progress, an abort signal, and allowWebGL.

Returns ​

Promise<GameableCharacter>

The character. Add object3D to your scene and call update every frame.

Throws ​

webgpu-required on the WebGL2 fallback with allowWebGL: false, renderer-not-ready before renderer.init(), load-failed when the package cannot be read or has no clip named options.clip, software-renderer when the renderer runs in software.

Example ​

ts
import { WebGPURenderer, Scene, PerspectiveCamera, Timer } from 'three/webgpu';
import { loadGameableCharacter } from 'gameable/three';

const renderer = new WebGPURenderer({ antialias: true });
await renderer.init();
const scene = new Scene();
const camera = new PerspectiveCamera(40, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 1.5, 3.5);

const character = await loadGameableCharacter(renderer, 'https://studio.example/.../character.json');
scene.add(character.object3D);
character.play('wave');

const timer = new Timer();
renderer.setAnimationLoop((time) => {
  timer.update(time);
  character.update(timer.getDelta(), camera);
  renderer.render(scene, camera);
});