avatar
A playable character, composed from three independent parts:
- body — a
.bundle: a skinned.mesh+ bone entities (a character imported from a GLB/FBX). The look. - controller — the movement system. Default: the standard humanoid kit
(
CharacterController+MovementState+Humanoid). - animation — the system that animates the body. Default: the shared
Locomotionblend space (reads the controller's velocity, plays retargeted clips). OrClipPlayer. Or your own component.
Movement and animation are not tied together — attaching the controller never
pulls in Locomotion, and choosing your own animation keeps the standard
controller. Authoring your own locomotion is a one-field swap (animation = ...).
Because the standard clip set retargets onto any humanoid rig, the default
locomotion drives any humanoid body with no per-body setup and no editing of any
built-in content.
Make an imported character playable
-- After importing a rigged humanoid GLB, the importer leaves a `.bundle`
-- (skinned mesh + bones). Survey for it, then wrap it in an avatar:
local body = asset.resolve("my_character", "bundle")
local hero = asset.create("avatar", "my_hero", { body = body })
To make hero the body every connecting player spawns with, point the scene's
PlayerPrototype.body at a body entity whose Asset source is hero (authored
in the scene tree — see man scene). To drive the local player with it right
now, instantiate it and bind the entity:
local root = hero:instantiate() -- spawns the body + movement kit
layers.active.players.localPlayer.avatar = root -- bind the live body
An avatar is not humanoid-only. The body can be anything — a humanoid, a
vehicle, an unrigged plane with a custom controller and shader. The defaults
adapt to the body: a humanoid body defaults to the shared humanoid Locomotion
(the synty clip preset); a non-humanoid / unrigged body gets no
auto-animation (you supply your own controller and animation). This is the
non-humanoid detection — an arbitrary animated model is never auto-driven by
humanoid locomotion — and it never blocks creating the avatar.
-- Non-humanoid avatar (e.g. a custom-controlled body), fully generic:
asset.create("avatar", "ship", { body = shipBundle, controller = { system = "ShipController" }, animation = false })
Options
asset.create("avatar", name, opts):
| opt | meaning |
|---|---|
body (required) | the body .bundle ref |
controller | movement: true/nil = standard kit, false = none, or a component name / { system, data } |
animation | a component name or { system, ... } — author your own locomotion here; defaults to Locomotion |
locomotion | clip preset for the default Locomotion (default "synty") |
clip | a single .animation ref — selects a one-clip ClipPlayer |
looping / speed / sourceRig | ClipPlayer tuning (with clip) |
Author your own locomotion (keeps the standard movement):
asset.create("avatar", "my_hero", { body = body, animation = { system = "MyLocomotion" } })
avatar.json:
{
"body": { "__ref": "<bundle guid>" },
"controller": true,
"animation": { "system": "Locomotion", "preset": "synty" }
}
The body's facing is read from its rig (the importer records a forward for each
body — {0,0,1} for a +Z Synty/FBX body, {0,0,-1} for a -Z glTF body), so
Locomotion turns the body to its movement direction regardless of how its source
authored it. No per-avatar facing knob.
Use
local root = avatarRef:instantiate() -- spawn body + movement kit
avatarRef:setCharacter(root, asset.resolve("knight", "bundle")) -- swap the body, keep the behaviour
:instantiate() spawns the body and attaches the movement system bound to it.
:setCharacter(root, body) despawns the current body and spawns a new one in its
place; the Locomotion kit on the avatar root re-resolves the new body's rig, so
the look swaps while the behaviour is kept.