Log inGet started

avatar

Updated October 14, 2026
  • 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

Only the avatar bound as the local player's body answers the player's controls. Every other avatar you instantiate (an NPC, a mannequin, a preview) stands where it is until something drives it: with the standard controller, write its CharacterController input bus (inputX / inputZ / inputJump) from a component of your own, the way an AI brain does (man CharacterController).

local npc = hero:instantiate()                         -- stands still
npc.component.get("CharacterController").inputZ = -2   -- walks while this is written each frame

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. Items seated with Humanoid:attach are kept: each moves to the new body's socket for the same point, with the transform it was seated with. An item whose point the new body lacks stays on the avatar root and is seated by the next swap to a body that has it. The new body takes the old body's id and is composed with the options the avatar was instantiated with, and the avatar's Asset id map names it.

In the Inspector

Selecting an avatar opens its Avatar section, read from avatar.json: its label when it has one, how it moves (controller: the standard mover, none, or the system it names), what animates it, and whether it is humanoid. It links the body the avatar wears, or says the avatar spawns no visible body, and offers to open avatar.json in the Code panel.

  • asset-type
  • reference