Log inGet started

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 Locomotion blend space (reads the controller's velocity, plays retargeted clips). Or ClipPlayer. 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):

optmeaning
body (required)the body .bundle ref
controllermovement: true/nil = standard kit, false = none, or a component name / { system, data }
animationa component name or { system, ... }author your own locomotion here; defaults to Locomotion
locomotionclip preset for the default Locomotion (default "synty")
clipa single .animation ref — selects a one-clip ClipPlayer
looping / speed / sourceRigClipPlayer 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.

  • asset-type
  • reference