@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
| Parameter | Type | Description |
|---|---|---|
code | GameableCharacterErrorCode | Why. |
message | string | What happened and what to do. |
options? | { cause?: unknown; } | The underlying error, when there is one. |
options.cause? | unknown | - |
Returns
Overrides
ts
Error.constructorProperties
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
| Parameter | Type | Description |
|---|---|---|
target | Object3D<Object3DEventMap> | Vector3 | null | What to look at, in world space, or null. |
options? | LookAtOptions | Each 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 clipplay()
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
| Parameter | Type | Description |
|---|---|---|
name | string | One of clips. |
options? | PlayOptions | Fade 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
| Parameter | Type | Description |
|---|---|---|
exposure | number | 1 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
| Parameter | Type | Description |
|---|---|---|
space | "arkit52" | 'arkit52'. |
weights | ArrayLike<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
| Parameter | Type | Description |
|---|---|---|
color | ColorRepresentation | Any three colour: '#ffe6cc', 0xffe6cc, a Color. White is none. |
Returns
void
Nothing.
Example
ts
character.setTint('#ffe6cc'); // a warm roomupdate()
ts
update(dt, camera): void;Advance the animation and pose the character. Call once per frame, before renderer.render(scene, camera).
Parameters
| Parameter | Type | Description |
|---|---|---|
dt | number | Seconds since the last frame. |
camera | Camera | The 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
| Parameter | Type |
|---|---|
input | URL | 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
| Parameter | Type |
|---|---|
loadedBytes | number |
totalBytes | number |
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.
head?
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
| Parameter | Type | Description |
|---|---|---|
camera | Camera | The 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
| Parameter | Type | Description |
|---|---|---|
renderer | WebGPURenderer | The app's renderer. |
scene | Scene | The scene the characters are in. |
Returns
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
| Parameter | Type | Description |
|---|---|---|
renderer | WebGPURenderer | The 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
| Parameter | Type | Description |
|---|---|---|
renderer | WebGPURenderer | Your WebGPURenderer, after await renderer.init(). |
url | string | The character's character.json. |
options | LoadGameableCharacterOptions | The 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);
});