Skip to content
Development
Skill

/roblox-characters

Roblox player characters: CharacterAdded lifecycle, Humanoid and HumanoidRootPart, R6, R15 and custom rigs, respawn and Tools. Use when a character script breaks after respawn, animations do not play or replicate, or sprint code fights the Humanoid. Not for hit validation

From plugin
awesome-gamedev-agent-skills
8.6k79 skills

Claude Code

$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill roblox-characters --agent claude-code

SKILL.md

roblox-characters.SKILL.md
name: roblox-characters
description: >
  Roblox player characters: CharacterAdded lifecycle, Humanoid and HumanoidRootPart, R6, R15 and
  custom rigs, respawn and Tools. Use when a character script breaks after respawn, animations do
  not play or replicate, or sprint code fights the Humanoid. Not for hit validation
  (`roblox-networking`), forces and raycasts (`roblox-physics`) or cameras (`camera-systems`).

Roblox characters

A `Player` is the durable identity; each `Character` is a replaceable session with its own references, connections, animation tracks, and cleanup. Most character bugs are one character's state leaking into the next.

Workflow

1. **Read the character contract.** Avatar Settings (rig type, movement system), `StarterCharacter`, `StarterCharacterScripts`, `Players.CharacterAutoLoads`, `Workspace.PlayerCharacterDestroyBehavior`, the existing Animate or controller scripts, tools, tags, and collision groups. Extend what is there; do not add a second controller. 2. **Separate scopes.** Player-scope state survives respawn; character-scope state does not. Put each character's connections, tracks, and temporary instances in one cleanup scope. 3. **Bind existing and future characters.** Connect `CharacterAdded`, then bind `player.Character` if it already exists. On the server, do this for players already in `Players:GetPlayers()`. 4. **Resolve parts defensively.** `WaitForChild` with a timeout for `Humanoid`, the root part, and `Animator`; after every yield, check the character is still the current one before acting. 5. **Choose one movement owner per state.** Standard avatars use Humanoid movement or the Character Controller Library. Physical mechanics (dash, knockback, grapple) use an impulse or a constraint for their duration and hand control back cleanly. 6. **Own animation lifecycle.** Load through the rig's `Animator`, keep tracks and marker connections in the character scope, and stop them on cleanup. Markers drive presentation; any gameplay effect they trigger is validated on the server. 7. **Run the lifecycle check.** With the Studio MCP server connected, start play, respawn the player several times from `execute_luau` on the `Server` datamodel (`for _ = 1, 5 do player:LoadCharacterAsync() task.wait(1) end`), then read `get_console_output`: expect no errors and exactly one setup log per spawn. Without MCP, ask the user to reset five times in Test and once with two players in Server & Clients, and to report duplicated logs, stuck animations, or errors.

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 call must not clear a binding that is already current.
    if player.Character ~= character or currentCharacter == character then return end
    clearCharacter()
    currentCharacter = character
    local thisGeneration = generation
    local humanoid = character:WaitForChild("Humanoid", 10)
    local root = character:WaitForChild("HumanoidRootPart", 10)
    -- 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 rather than introducing a 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 only; the server validates any hit
end)

track:Play(0.1)
-- On character teardown:
markerConnection:Disconnect()
track:Stop(0.1)
animation:Destroy()

Rigs without a `Humanoid` need an `AnimationController` with an `Animator`. `Humanoid:LoadAnimation()` is deprecated; always load through the `Animator`.

Movement and rig rules

  • If R6 is supported, do not hardcode R15 limb names. Prefer attachments, tags, or a small rig

adapter; branch on `Humanoid.RigType` only where topology really differs.

  • `HumanoidRootPart` is the usual assembly root, not a guarantee for custom models. Write down the

custom rig contract and validate it at spawn.

  • Setting `AssemblyLinearVelocity` is an instant change; use `ApplyImpulse` for one-off pushes and

a `LinearVelocity` constraint for sustained control (details in `roblox-physics`).

  • For new actions such as sprint or rebinding jump, use the Input Action System (`InputContext`,

`InputAction`, `InputBinding`) instead of raw `UserInputService` hooks. Give your context a higher `Priority` (and `Sink`) than the default player contexts so it wins.

  • If the place uses the Character Controller Library (Avatar Settings → Movement → Abilities) or a

`ControllerManager`, extend its abilities and controllers. Layering `HumanoidStateType` changes on top of it fights the library.

  • Tools move between `Backpack` and t
Read more
@gamedev-skills
Ships withawesome-gamedev-agent-skills

<img src="docs/assets/awesome-gamedev-agent-skills-banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.

Stars on GitHub

Momentum

stars in total
8.6k
Star history of gamedev-skills/awesome-gamedev-agent-skills on GitHub, a chart by star-history.com

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills13d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react4d252k
@facebook@facebookInstall Skill

Questions about roblox-characters.

Which coding agents can use roblox-characters?
roblox-characters installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.