@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
BonePose
A body pose a game hands a character: read by reference every frame, so change the numbers in place.
Properties
hips?
ts
readonly optional hips?: Readonly<{
joint: string;
x: number;
y: number;
z: number;
}>;The hips' place on the joint named, in metres as the character is drawn (at its height), or none to leave them.
names
ts
readonly names: readonly string[];The joints, by the skeleton's own names (a package's bone names), '' for one the game has and the skeleton does not (a game's own root). Keep the same array: it is bound to the bones once, and a new array binds again.
rotations
ts
readonly rotations: readonly Readonly<{
w: number;
x: number;
y: number;
z: number;
}>[];Each joint's rotation in its parent's frame, one per name (x, y, z, w; three's own quaternions do).
CreateSharedCharacterSplatOptions
The options of createSharedCharacterSplat.
Example
ts
// two fighters of about 150,000 points each, on a stage 8 m across
const options = { capacity: 400_000, bounds: { center: [0, 1, 0], radius: 8 } };Properties
bounds?
ts
readonly optional bounds?: object;Where its characters stay, in the frame of the object it is added to: a sphere (centre, radius in metres). The sort spreads its 4096 depth steps across it, so a sphere much larger than the stage costs precision. Default: 12 m round the origin.
center
ts
readonly center: [number, number, number];radius
ts
readonly radius: number;capacity
ts
readonly capacity: number;Points it can hold, fixed for its life: the characters it will hold added up (a character's points are its package's splatCount and its pose corrections'; a character it has no room for draws on its own).
EyeShear
The studio's two eye volumes and reach fade; null at the setter switches them off.
Example
ts
character.setEyeShear({
left: { centre: [0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] },
right: { centre: [-0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] },
fade: [1, 1.35],
});Properties
fade?
ts
readonly optional fade?: readonly [number, number];Reach fade in eye radii, increasing. Studio default [1, 1.35].
left
ts
readonly left: EyeShearVolume;right
ts
readonly right: EyeShearVolume;EyeShearVolume
One calibrated eye volume in the package rest frame, as pcGazeFrame supplies it.
Example
ts
const eye: EyeShearVolume = { centre: [0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] };Properties
centre
ts
readonly centre: readonly [number, number, number];Rest centre in metres, including the package coordinate conversion.
radius
ts
readonly radius: number;Effective volume radius (studio rho * eye.radius), metres, positive.
shear
ts
readonly shear: readonly [number, number, number];Studio shearVector output; no easing, clamping or camera math in the engine.
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.
expressionSpace
ts
readonly expressionSpace: object;The head's own expression space; native frames must have exactly dim coefficients.
dim
ts
readonly dim: number;kind
ts
readonly kind: "arkit52" | "gnm" | "gnm68";hair
ts
readonly hair: AosrigHairState | null;Sara's strand hair, when the package carries hair/ (its guide strands as bone slots after the rig's joints, simulated each frame): the slots, her sim's ms a frame, reset() after a teleport; null without it.
measures
ts
readonly measures: CharacterMeasures | null;The character's measures as drawn, metres above its feet: the eyes, the head joint, the head's top, the crown of its points (hair and all), the hips, the stature (floor to crown) and its width. A framing or a camera is built from these, never from a number typed in. Null for a package whose rig has no head.
object3D
ts
readonly object3D: Object3D;Add this to your scene. Move, turn and parent it like any Object3D; its feet are at its origin.
scale
ts
readonly scale: number;How much larger (or smaller) it is drawn than it was exported, for the height option: 1 without it. The package's own measures are measures divided by it.
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.
setBonePose()
ts
setBonePose(pose): void;Your own body pose on the bones: a pose your game solves itself (its physics, its fight maths), laid on the skeleton after the clips and the head aim on every update, so your numbers are the last word on the body while the face, the blinks and the eyes stay the character's own. null hands the body back to the clips, the bones as they were when you took them. Read by reference every frame: keep the same names array (bound to the bones once) and change the numbers in place; the hips are in metres as drawn.
Parameters
| Parameter | Type | Description |
|---|---|---|
pose | BonePose | null | The pose, or null. |
Returns
void
Nothing.
Example
ts
const pose = { names: skeleton.names, rotations: myRotations, hips: { joint: 'hips', x: 0, y: 0.97, z: 0 } };
character.setBonePose(pose); // then each frame: write myRotations and pose.hips, call update
character.setBonePose(null); // the clips againsetExposure()
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()
Call Signature
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 of ARKit input.
Use 'native' for the head's own GNM coefficients (see expressionSpace). Native frames are copied, keep signed values without clamping, and replace non-finite values with 0.
Parameters
| Parameter | Type | Description |
|---|---|---|
space | "arkit52" | 'arkit52' or 'native'. |
weights | ArrayLike<number> | Readonly<Record<string, number>> | ARKit weights in [0, 1] (clamped; NaN is 0), or exactly expressionSpace.dim native coefficients. |
Returns
void
Nothing.
Throws
For a native frame of the wrong length, or native input on an ARKit head.
Example
ts
if (character.expressionSpace.kind !== 'arkit52') {
const frame = new Float32Array(character.expressionSpace.dim);
frame[0] = -0.5; // signed coefficients from the head's lip-sync model
character.setExpression('native', frame);
}
character.setExpression('arkit52', { jawOpen: 0.3 });Call Signature
ts
setExpression(space, weights): void;Set the face directly in the head's own space; see expressionSpace.
Parameters
| Parameter | Type | Description |
|---|---|---|
space | "native" | 'native'. |
weights | ArrayLike<number> | Exactly expressionSpace.dim coefficients, copied without clamping; non-finite values become 0. |
Returns
void
Nothing.
Throws
For a different length, or a character whose space is arkit52.
Example
ts
if (character.expressionSpace.kind !== 'arkit52') {
character.setExpression('native', new Float32Array(character.expressionSpace.dim));
}setEyeShear()
ts
setEyeShear(frame): void;Apply the studio's calibrated virtual-eye shear on rest centres and covariances. Caller computes shearVector/viewSlope and converts centres/vectors into the package frame. An explicit frame replaces procedural eye requests; null restores them. Off by default.
Parameters
| Parameter | Type | Description |
|---|---|---|
frame | EyeShear | null | Two eye volumes with effective radii, shear vectors and optional reach fade, or null. |
Returns
void
Nothing.
Throws
For invalid values or the vertex-only WebGL skin path.
Example
ts
character.setEyeShear({
left: { centre: [0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] },
right: { centre: [-0.03, 1.6, 0], radius: 0.014, shear: [0.2, 0, 0] },
fade: [1, 1.35],
});
character.setEyeShear(null);setJointAffines()
ts
setJointAffines(names, affines): void;Replace surface proportions with the studio's per-joint affine deltas, before skinning. Centres and covariances take the same normalized weighted affine. Off by default. Skeleton transforms remain supplied by the caller through setBonePose.
Parameters
| Parameter | Type | Description |
|---|---|---|
names | readonly string[] | Package joint names in the same order as the affine blocks; omitted joints are identity. |
affines | Float32Array<ArrayBufferLike> | null | Three row-major [A-I |
Returns
void
Nothing.
Throws
On invalid names/data or the vertex-only WebGL skin path.
Example
ts
character.setJointAffines(['Hips'], new Float32Array([
0.1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]));
character.setJointAffines([], null);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. Left unset, the page decides: a headless capture browser that wants the characters in its pictures sets globalThis.__gameableAllowSoftwareRenderer = true before the game loads (see pageAllowsSoftwareRenderer).
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>
hair?
ts
readonly optional hair?: boolean;Sara's strand hair, when the package carries it (hair/): its rows ride her simulated strands and character.hair reports it. Default true. False loads the character with the hair as captured (skinned to the rig).
height?
ts
readonly optional height?: number;Draw the character at its real height, metres, floor to the crown of its own points (hair and all). The studio makes every character about 1.75 m tall; a 1.88 m person's character is then drawn 1.88 m tall: its points, its joints (object3D is scaled, from the floor between its feet), its shadow and its GameableCharacter.measures all at that size. Default: as exported (GameableCharacter.scale 1).
Example
ts
const logan = await loadGameableCharacter(renderer, url, { height: 1.88 });lighting?
ts
readonly optional lighting?: boolean;Give every point a surface direction at load, read from the meshes the points ride on and turned with the joints every frame, so a scene's lights fall evenly on skin and cloth once the character is lit (a shared cloud's SharedCharacterSplat.setLighting). Default false: nothing is read and the load is as before. Lit without it, each point's direction is guessed from its own shape, which breaks a face and a chest into blotches close up. It costs loading time, read in slices between frames: about 11 s more for four fighters of 2.5 million points between them on a desk (5.4 s to 16 s). WebGPU only.
Example
ts
const fighter = await loadGameableCharacter(renderer, url, { shared: cloud, lighting: true });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.
mouthMesh?
ts
readonly optional mouthMesh?: "plain" | "textured";Draw the package's frozen mouth mesh with plain colours or its texture. Enables the mouth draw and loads mouth.json / mouth.glb (and mouth_texture.png for textured). Omit to keep the default mouth behaviour.
Example
ts
const character = await loadGameableCharacter(renderer, 'characters/miranda/character.json', {
mouthMesh: 'textured',
});
scene.add(character.object3D);mouthMotion?
ts
readonly optional mouthMotion?: "frame" | "studio";Mouth-neighbourhood expression motion: frame (default) keeps the triangle frame; studio uses four weighted vertex displacements without expression rotation/stretch. Body skinning, plugs and visibility rules are retained. As in the studio, band size caps apply while the mouth interior is drawn; the dark sheet keeps the skin's full coverage. Selected at load, independently of whether the interior is drawn.
Example
ts
const character = await loadGameableCharacter(renderer, 'characters/miranda/character.json', {
mouthMotion: 'studio',
});
scene.add(character.object3D);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),shared?
ts
readonly optional shared?: SharedCharacterSplat;Draw the character into a cloud it shares with other characters (createSharedCharacterSplat), so the points of all of them are sorted together: where two overlap, each point is drawn in its true order (a fighter's arm round the other's legs in front of them, his back behind them), not one whole character over the other. Its tint and exposure are then the cloud's, and the inside of its mouth is not drawn. Ignored on the WebGL2 fallback, and for a character the cloud has no room for, or whose colours are not sRGB: it draws in a cloud of its own, as without the option.
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.
SharedCharacterSplat
A cloud of points that several characters draw into, so their points are sorted together.
Splats sort only within one object (docs/concepts/splats.md, Draw order). Two characters each in a cloud of their own are drawn one whole after the other, and wherever they overlap on screen the second covers the first, whichever is nearer: a fighter's arm round the other's legs is drawn behind them, then, a moment later, his whole body in front. Characters loaded with this as their shared option each write their points into a slice of the one cloud, which sorts and draws them all at once, every point in its true order.
Example
ts
const cloud = await createSharedCharacterSplat(renderer, { capacity: 800_000 });
scene.add(cloud.object3D);
const fighter = await loadGameableCharacter(renderer, url, { shared: cloud });
cloud.setExposure(1.2); // every character in itProperties
available
ts
readonly available: number;Points not given to a character yet.
capacity
ts
readonly capacity: number;Points it holds.
object3D
ts
readonly object3D: Object3D;Add it to the scene once, where the characters are. Each character stands where its own object3D is.
sorted
ts
readonly sorted: boolean;False on the WebGL2 fallback: there each character draws in a cloud of its own.
Methods
dispose()
ts
dispose(): void;Free the cloud (dispose its characters first).
Returns
void
setExposure()
ts
setExposure(value): void;A multiplier on the brightness of every character in the cloud (see GameableCharacter.setExposure).
Parameters
| Parameter | Type |
|---|---|
value | number |
Returns
void
setLighting()
ts
setLighting(lighting): void;Light every character in the cloud with the scene's lights (directional, spot, point, hemisphere and ambient, as three lights a mesh), mixed with the light each was captured in by emissionWeight; null draws them as captured again, the default. Load the characters with lighting: true for their surface directions. Call it when the list of lights changes, not every frame: the lights themselves stay live. Does nothing on the WebGL2 fallback.
Parameters
| Parameter | Type |
|---|---|
lighting | SplatEnvironmentLighting | null |
Returns
void
Example
ts
import { AmbientLight, SpotLight } from 'three/webgpu';
const spot = new SpotLight(0xff9a5c, 46, 0, Math.PI / 5, 0.6, 2);
cloud.setLighting({
radianceSH: Array.from({ length: 9 }, () => [0, 0, 0] as const),
spotLights: [spot], ambientLights: [new AmbientLight(0x2a2230, 1)],
emissionWeight: 0.25, diffuseWeight: 1, wrap: 0.3,
});setTint()
ts
setTint(color): void;A colour to multiply every character in the cloud by (see GameableCharacter.setTint).
Parameters
| Parameter | Type |
|---|---|
color | ColorRepresentation |
Returns
void
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();
});createSharedCharacterSplat()
ts
function createSharedCharacterSplat(renderer, options): Promise<SharedCharacterSplat>;Make a cloud for several characters to draw into, sorted together (see SharedCharacterSplat).
Parameters
| Parameter | Type | Description |
|---|---|---|
renderer | WebGPURenderer | Your WebGPURenderer, after await renderer.init(). |
options | CreateSharedCharacterSplatOptions | Its capacity and where its characters stay. |
Returns
Promise<SharedCharacterSplat>
The cloud. Add object3D to your scene, then load each character with { shared: cloud }.
Example
ts
const cloud = await createSharedCharacterSplat(renderer, { capacity: 800_000 });
scene.add(cloud.object3D);
const a = await loadGameableCharacter(renderer, urlA, { shared: cloud });
const b = await loadGameableCharacter(renderer, urlB, { shared: cloud });
scene.add(a.object3D, b.object3D);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);
});pageAllowsSoftwareRenderer()
ts
function pageAllowsSoftwareRenderer(): boolean;Whether the page lets characters draw on a software renderer when the app does not say: globalThis.__gameableAllowSoftwareRenderer === true. A headless capture browser with no GPU (a sandbox taking a picture of a game nobody has open) sets it before the game loads, so the characters are in its pictures without the game asking for allowSoftwareRenderer itself.
Returns
boolean
True when the page has opted in.
Example
ts
// in the capture browser, before the game's own scripts run
(globalThis as Record<string, unknown>).__gameableAllowSoftwareRenderer = true;
pageAllowsSoftwareRenderer(); // true