Roblox characters
Treat a Player as the durable identity and each Character as a replaceable session with its own
references, connections, animation tracks, and cleanup. Targets Roblox's rolling platform APIs.
When to use
- Use for player-character lifecycle, Humanoid state/movement, rigs, animations, tools,
accessories, custom characters, death, or respawn defects.
- Use whenever code stores a Character/Humanoid/root reference longer than one spawn.
When not to use: general physics queries and constraints belong to roblox-physics; remote
trust belongs to roblox-networking; camera logic belongs to camera-systems.
Workflow
- Inspect the character contract. Check avatar settings,
StarterCharacter,
StarterCharacterScripts, CharacterAutoLoads, R6/R15 support, existing Animate/controller
scripts, tools, tags, collision groups, and server/client ownership.
- Separate scopes. Player-scope state survives respawn; character-scope state does not. Put
character connections/tracks/resources in one cleanup scope and destroy it on removal.
- Bind existing and future characters. Connect
CharacterAdded, then bind player.Character
if present. Do not assume event subscription alone sees a character that already spawned.
- Resolve required components defensively. Wait with a timeout where replication warrants it;
validate
Humanoid, root, Animator, and rig assumptions. Abort if that character is no longer
current before applying delayed work.
- Choose movement ownership. Use Humanoid movement for standard avatars; use
AssemblyLinearVelocity, BasePart:ApplyImpulse(), or a LinearVelocity/AlignPosition
constraint only for mechanics that need physical control. Keep gameplay
authority and network ownership implications explicit.
- Own animation lifecycle. Load via the rig's
Animator; store tracks/connections; use named
markers for gameplay timing only with server validation; stop/disconnect on character cleanup.
- Verify lifecycle stress. Spawn, die, reset, rapid-respawn, swap rig if supported, equip/drop
tools, leave during setup, and run with at least two players when character interactions matter.
Pattern: replaceable character scope
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local generation = 0
local connections: {RBXScriptConnection} = {}
local currentCharacter: Model? = nil
local function clearCharacter()
generation += 1
for _, connection in connections do connection:Disconnect() end
table.clear(connections)
currentCharacter = nil
end
local function bindCharacter(character: Model)
-- Guard BEFORE teardown. A stale invocation (see the CharacterAdded/defer race below) must not
-- clear a binding that is already current, or nothing ends up bound at all.
if player.Character ~= character then return end
clearCharacter()
currentCharacter = character
local thisGeneration = generation
local humanoid = character:WaitForChild("Humanoid", 10)
local root = character:WaitForChild("HumanoidRootPart", 10)
-- Re-check after the yields: a respawn during WaitForChild bumps generation and makes this call stale.
if not humanoid or not root or generation ~= thisGeneration then return end
table.insert(connections, humanoid.Died:Connect(function()
if generation ~= thisGeneration then return end
setCharacterUiEnabled(false)
end))
attachCurrentCharacterSystems(character, humanoid, root)
end
player.CharacterRemoving:Connect(function(character)
if currentCharacter == character then clearCharacter() end
end)
player.CharacterAdded:Connect(bindCharacter)
if player.Character then task.defer(bindCharacter, player.Character) end
Use the project's cleanup utility when one exists; do not introduce a new framework for three
connections. Server systems repeat this binding per Player and clear player-scope tables on
PlayerRemoving.
Pattern: animation through Animator and markers
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://1234567890"
local track = animator:LoadAnimation(animation)
local markerConnection = track:GetMarkerReachedSignal("Commit"):Connect(function(parameter)
playLocalSwingEffect(parameter) -- presentation; server still validates any hit
end)
track:Play(0.1)
-- On character teardown:
markerConnection:Disconnect()
track:Stop(0.1)
animation:Destroy()
For rigs without a Humanoid, use an AnimationController with an Animator. Do not use the
deprecated convenience path as a substitute for owning the actual Animator and track lifecycle.
Movement and rig rules
- Do not hardcode R15 limb names if R6 is supported. Prefer attachments, tags, or a rig-type
adapter; branch on
Humanoid.RigType only where topology materially differs.
HumanoidRootPart is the usual character assembly root, not a universal guarantee for every
custom model. Define the custom rig contract and validate it at spawn.
- Prefer
Humanoid:Move()/standard controls for ordinary avatar locomotion. Directly changing
AssemblyLinearVelocity is an instantaneous physical action; use forces/constraints or impulses
when continuous or instantaneous physics is the real intent.
- Never grant damage or movement authority because a client owns its character physics. Validate
cross-player consequences on the server.
- Tools move between Backpack and Character during equip; listen to lifecycle/state rather than
assuming one fixed parent. Modify accessories/appearance through current character APIs and
preserve an up-to-date applied
HumanoidDescription when editing avatar appearance.
Common failures
| Symptom |
Likely cause |
Remedy |
| works once, breaks after reset |
cached character/Humanoid/root |
rebuild per CharacterAdded; clear on removal |
| callbacks fire twice after deaths |
old character connections survived |
character-scoped cleanup and generation/current checks |
| delayed load edits wrong rig |
async work outlived spawn |
compare current Character/generation after every yield |
| animation visible only locally or not at all |
wrong Animator/context/asset ownership |
inspect rig Animator, execution side, permissions, and replication |
| hit marker awards impossible hit |
animation marker trusted as authority |
use marker for timing/presentation; server validates combat state |
| R6/custom rig errors |
R15 names assumed |
define rig contract; attachments/adapter; test each supported rig |
| tool disappears from system |
fixed Backpack/Character parent assumed |
handle equip/unequip ancestry and character replacement |
| custom force fights Humanoid |
two controllers own motion |
choose one movement authority per state and restore cleanly |
Resources
- Read
references/lifecycle-and-animation.md for server binding, death vs removal, rig and
animation verification, custom characters, and the lifecycle stress matrix.
Related skills
roblox-physics — forces, constraints, assemblies, collision, and network ownership.
roblox-networking — server validation of character actions and stale requests.
input-systems — action mapping and responsive movement intent.
camera-systems — camera behavior following replaceable characters.
Primary references
https://create.roblox.com/docs/characters
https://create.roblox.com/docs/animation/using
https://create.roblox.com/docs/characters/appearance
1---
2name: roblox-characters
3description: >
4 Build respawn-safe Roblox character systems around Players, CharacterAdded/CharacterRemoving,
5 Humanoid, HumanoidRootPart, Animator, R6/R15 rigs, movement, animations and markers, death,
6 tools, accessories, ownership, and custom characters. Use when character scripts break after
7 respawn, cache stale Humanoids, control movement or velocity, load AnimationTracks, handle death,
8 equip Tools, modify avatars, or support custom player rigs.
9---
10
11# Roblox characters
12
13Treat a `Player` as the durable identity and each `Character` as a replaceable session with its own
14references, connections, animation tracks, and cleanup. Targets Roblox's rolling platform APIs.
15
16## When to use
17
18- Use for player-character lifecycle, Humanoid state/movement, rigs, animations, tools,
19 accessories, custom characters, death, or respawn defects.
20- Use whenever code stores a Character/Humanoid/root reference longer than one spawn.
21
22**When not to use:** general physics queries and constraints belong to `roblox-physics`; remote
23trust belongs to `roblox-networking`; camera logic belongs to `camera-systems`.
24
25## Workflow
26
271. **Inspect the character contract.** Check avatar settings, `StarterCharacter`,
28 `StarterCharacterScripts`, `CharacterAutoLoads`, R6/R15 support, existing Animate/controller
29 scripts, tools, tags, collision groups, and server/client ownership.
302. **Separate scopes.** Player-scope state survives respawn; character-scope state does not. Put
31 character connections/tracks/resources in one cleanup scope and destroy it on removal.
323. **Bind existing and future characters.** Connect `CharacterAdded`, then bind `player.Character`
33 if present. Do not assume event subscription alone sees a character that already spawned.
344. **Resolve required components defensively.** Wait with a timeout where replication warrants it;
35 validate `Humanoid`, root, `Animator`, and rig assumptions. Abort if that character is no longer
36 current before applying delayed work.
375. **Choose movement ownership.** Use Humanoid movement for standard avatars; use
38 `AssemblyLinearVelocity`, `BasePart:ApplyImpulse()`, or a `LinearVelocity`/`AlignPosition`
39 constraint only for mechanics that need physical control. Keep gameplay
40 authority and network ownership implications explicit.
416. **Own animation lifecycle.** Load via the rig's `Animator`; store tracks/connections; use named
42 markers for gameplay timing only with server validation; stop/disconnect on character cleanup.
437. **Verify lifecycle stress.** Spawn, die, reset, rapid-respawn, swap rig if supported, equip/drop
44 tools, leave during setup, and run with at least two players when character interactions matter.
45
46## Pattern: replaceable character scope
47
48```lua
49local Players = game:GetService("Players")
50local player = Players.LocalPlayer
51local generation = 0
52local connections: {RBXScriptConnection} = {}
53local currentCharacter: Model? = nil
54
55local function clearCharacter()
56 generation += 1
57 for _, connection in connections do connection:Disconnect() end
58 table.clear(connections)
59 currentCharacter = nil
60end
61
62local function bindCharacter(character: Model)
63 -- Guard BEFORE teardown. A stale invocation (see the CharacterAdded/defer race below) must not
64 -- clear a binding that is already current, or nothing ends up bound at all.
65 if player.Character ~= character then return end
66 clearCharacter()
67 currentCharacter = character
68 local thisGeneration = generation
69 local humanoid = character:WaitForChild("Humanoid", 10)
70 local root = character:WaitForChild("HumanoidRootPart", 10)
71 -- Re-check after the yields: a respawn during WaitForChild bumps generation and makes this call stale.
72 if not humanoid or not root or generation ~= thisGeneration then return end
73
74 table.insert(connections, humanoid.Died:Connect(function()
75 if generation ~= thisGeneration then return end
76 setCharacterUiEnabled(false)
77 end))
78 attachCurrentCharacterSystems(character, humanoid, root)
79end
80
81player.CharacterRemoving:Connect(function(character)
82 if currentCharacter == character then clearCharacter() end
83end)
84player.CharacterAdded:Connect(bindCharacter)
85if player.Character then task.defer(bindCharacter, player.Character) end
86```
87
88Use the project's cleanup utility when one exists; do not introduce a new framework for three
89connections. Server systems repeat this binding per `Player` and clear player-scope tables on
90`PlayerRemoving`.
91
92## Pattern: animation through Animator and markers
93
94```lua
95local animation = Instance.new("Animation")
96animation.AnimationId = "rbxassetid://1234567890"
97local track = animator:LoadAnimation(animation)
98local markerConnection = track:GetMarkerReachedSignal("Commit"):Connect(function(parameter)
99 playLocalSwingEffect(parameter) -- presentation; server still validates any hit
100end)
101
102track:Play(0.1)
103-- On character teardown:
104markerConnection:Disconnect()
105track:Stop(0.1)
106animation:Destroy()
107```
108
109For rigs without a `Humanoid`, use an `AnimationController` with an `Animator`. Do not use the
110deprecated convenience path as a substitute for owning the actual Animator and track lifecycle.
111
112## Movement and rig rules
113
114- Do not hardcode R15 limb names if R6 is supported. Prefer attachments, tags, or a rig-type
115 adapter; branch on `Humanoid.RigType` only where topology materially differs.
116- `HumanoidRootPart` is the usual character assembly root, not a universal guarantee for every
117 custom model. Define the custom rig contract and validate it at spawn.
118- Prefer `Humanoid:Move()`/standard controls for ordinary avatar locomotion. Directly changing
119 `AssemblyLinearVelocity` is an instantaneous physical action; use forces/constraints or impulses
120 when continuous or instantaneous physics is the real intent.
121- Never grant damage or movement authority because a client owns its character physics. Validate
122 cross-player consequences on the server.
123- Tools move between Backpack and Character during equip; listen to lifecycle/state rather than
124 assuming one fixed parent. Modify accessories/appearance through current character APIs and
125 preserve an up-to-date applied `HumanoidDescription` when editing avatar appearance.
126
127## Common failures
128
129| Symptom | Likely cause | Remedy |
130|---|---|---|
131| works once, breaks after reset | cached character/Humanoid/root | rebuild per `CharacterAdded`; clear on removal |
132| callbacks fire twice after deaths | old character connections survived | character-scoped cleanup and generation/current checks |
133| delayed load edits wrong rig | async work outlived spawn | compare current Character/generation after every yield |
134| animation visible only locally or not at all | wrong Animator/context/asset ownership | inspect rig Animator, execution side, permissions, and replication |
135| hit marker awards impossible hit | animation marker trusted as authority | use marker for timing/presentation; server validates combat state |
136| R6/custom rig errors | R15 names assumed | define rig contract; attachments/adapter; test each supported rig |
137| tool disappears from system | fixed Backpack/Character parent assumed | handle equip/unequip ancestry and character replacement |
138| custom force fights Humanoid | two controllers own motion | choose one movement authority per state and restore cleanly |
139
140## Resources
141
142- Read `references/lifecycle-and-animation.md` for server binding, death vs removal, rig and
143 animation verification, custom characters, and the lifecycle stress matrix.
144
145## Related skills
146
147- `roblox-physics` — forces, constraints, assemblies, collision, and network ownership.
148- `roblox-networking` — server validation of character actions and stale requests.
149- `input-systems` — action mapping and responsive movement intent.
150- `camera-systems` — camera behavior following replaceable characters.
151
152## Primary references
153
154- `https://create.roblox.com/docs/characters`
155- `https://create.roblox.com/docs/animation/using`
156- `https://create.roblox.com/docs/characters/appearance`