Skip to main content

AnimationController

Per-character animation controller for Motix.

AnimationController manages all active animations for a single character or entity. It drives a built-in state machine, routes play requests through layer conflict resolution and exclusive group management, and provides per-frame weight interpolation for smooth blending.

Create one controller per character. Controllers do not share state.

local Motix = require(ReplicatedStorage.Motix)

-- Build the behavior once per character type
local Behavior = Motix.BehaviorBuilder.Humanoid():Build()

-- Create a controller for a specific character
local Config = Behavior:CreateController(
	character.Name,
	character.Humanoid:FindFirstChildOfClass("Animator"),
	true,  -- isOwningClient
	Players.LocalPlayer
)

local Controller = Motix.new(Config)

Controller.OnPlay:Connect(function(animName)
	print("Playing:", animName)
end)

Controller:Play("Idle")

The AnimationController module is re-exported as the return value of the root Motix module, so Motix.new(Config) creates a controller directly.

Registry required

AnimationRegistry must be initialized before any controller is created. Call AnimationRegistry.GetInstance():Init(configs) once at startup.

Properties

OnPlay

AnimationController.OnPlay: Signal<string>

Fires when an animation begins playing on this controller.

The argument is the animation config name. Connect once at initialization to react to any animation on this controller.

Controller.OnPlay:Connect(function(animationName)
	-- React to any animation starting
end)

OnStop

AnimationController.OnStop: Signal<string>

Fires when an animation stops on this controller.

The argument is the animation config name.

Controller.OnStop:Connect(function(animationName)
	-- React to any animation stopping
end)

Functions

new

AnimationController.new(ConfigControllerConfig) → AnimationController

Creates a new AnimationController from a ControllerConfig.

The config is typically produced by BuiltBehavior:CreateController(). Constructing it manually is valid but not recommended. Use the builder instead to ensure all required fields are present and validated.

ControllerConfig fields:

Field Type Description
CharacterId string Unique identifier for this character. Used by MotixNet for ownership tracking.
Animator Animator? The Roblox Animator instance. Ignored on the server.
IsOwningClient boolean Whether this is the client that owns this character. Controls whether MotixNet sends intents.
OwningPlayer Player? The player who owns this character. Optional, used by MotixNet.
LayerProfiles { LayerProfile } Layer definitions. Must match the layers referenced in animation configs.
States { StateDefinition } State definitions for the state machine.
InitialState string The state the machine starts in.
Predicates { [string]: () -> boolean } Named predicate functions for state transitions.
local Config = Behavior:CreateController(
	characterId,
	animator,
	isOwningClient,
	player
)

local Controller = Motix.new(Config)

Play

AnimationController:Play(
AnimationNamestring--

The registered name of the animation to play.

) → ()

Queues an animation to play on the next frame.

If the animation is already playing, the request is a no-op. The request is processed during the controller's frame tick through layer conflict resolution and group management.

Controller:Play("Fire")
CAUTION

The animation name must be registered in AnimationRegistry. An unregistered name produces a warning and the request is dropped.

PlayTag

AnimationController:PlayTag(
Tagstring--

The tag to look up.

) → ()

Plays all animations with the given tag.

Equivalent to calling Play for each animation registered under the tag. Tag membership is set via :Tag() or :Tags() on the animation builder.

Controller:PlayTag("footstep")

If no animations are registered under the tag, this is a no-op.

Stop

AnimationController:Stop(
AnimationNamestring,--

The registered name of the animation to stop.

Immediateboolean?--

If true, stop without fade. Default false.

) → ()

Stops an animation.

If Immediate is true, the animation stops without any fade-out. Otherwise, the animation fades out using its configured FadeOutTime.

If the animation is in the pending queue but has not started playing, it is removed from the queue immediately.

Controller:Stop("Idle")         -- fade out
Controller:Stop("Idle", true)   -- immediate stop

StopGroup

AnimationController:StopGroup(
GroupNamestring,--

The group name to stop.

Immediateboolean?--

If true, stop all group members immediately. Default false.

) → ()

Stops all animations in the given exclusive group.

Equivalent to calling Stop on every animation in the group that is currently active or queued.

Controller:StopGroup("WeaponAction")
Controller:StopGroup("WeaponAction", true) -- immediate

RequestStateTransition

AnimationController:RequestStateTransition(
StateNamestring,--

The target state name.

Prioritynumber--

Higher values win when multiple transitions compete in the same frame.

) → ()

Requests a manual state transition.

The transition is queued and processed in the next frame's state machine tick. If multiple transitions are queued in the same frame, the highest priority wins.

Manual transitions bypass predicate evaluation. The target state must be defined in the behavior.

humanoid.Jumping:Connect(function()
	Controller:RequestStateTransition("Jump", 100)
end)

GetActiveGroupAnimMap

AnimationController:GetActiveGroupAnimMap() → {[string]string}--

Map from group name to active animation name.

Returns a map from exclusive group name to the currently active animation name in that group.

Only includes groups that have an animation actively playing (not fading out). Useful for debugging or for game logic that needs to know which animation owns a group slot.

local GroupMap = Controller:GetActiveGroupAnimMap()
if GroupMap["WeaponAction"] == "Reload" then
	-- Block fire input while reloading
end

If an invariant is violated (two animations active in the same group), a warning is logged and both entries are included.

AttachInspector

AnimationController:AttachInspector() → DebugInspector

Attaches a debug inspector to the controller and returns it.

The inspector provides a live view of active animations, layer weights, and state machine status. Attach this during development to verify that layers, groups, and state transitions behave as expected.

local Inspector = Controller:AttachInspector()

Destroy

AnimationController:Destroy() → ()

Tears down the controller completely.

Disconnects the frame loop, stops all active animations immediately, destroys all pooled wrappers, fires OnStop for any active animations, and destroys the OnPlay and OnStop signals.

After this call the controller is inert. Do not call any method on it after Destroy.

Controller:Destroy()
Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Creates a new `AnimationController` from a `ControllerConfig`.\n\nThe config is typically produced by `BuiltBehavior:CreateController()`. Constructing it\nmanually is valid but not recommended. Use the builder instead to ensure all required\nfields are present and validated.\n\n**ControllerConfig fields:**\n\n| Field | Type | Description |\n|---|---|---|\n| `CharacterId` | `string` | Unique identifier for this character. Used by MotixNet for ownership tracking. |\n| `Animator` | `Animator?` | The Roblox `Animator` instance. Ignored on the server. |\n| `IsOwningClient` | `boolean` | Whether this is the client that owns this character. Controls whether MotixNet sends intents. |\n| `OwningPlayer` | `Player?` | The player who owns this character. Optional, used by MotixNet. |\n| `LayerProfiles` | `{ LayerProfile }` | Layer definitions. Must match the layers referenced in animation configs. |\n| `States` | `{ StateDefinition }` | State definitions for the state machine. |\n| `InitialState` | `string` | The state the machine starts in. |\n| `Predicates` | `{ [string]: () -> boolean }` | Named predicate functions for state transitions. |\n\n```lua\nlocal Config = Behavior:CreateController(\n\tcharacterId,\n\tanimator,\n\tisOwningClient,\n\tplayer\n)\n\nlocal Controller = Motix.new(Config)\n```",
            "params": [
                {
                    "name": "Config",
                    "desc": "",
                    "lua_type": "ControllerConfig"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "AnimationController"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 114,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "Play",
            "desc": "Queues an animation to play on the next frame.\n\nIf the animation is already playing, the request is a no-op. The request is processed\nduring the controller's frame tick through layer conflict resolution and group management.\n\n```lua\nController:Play(\"Fire\")\n```\n\n:::caution\nThe animation name must be registered in `AnimationRegistry`. An unregistered name\nproduces a warning and the request is dropped.\n:::",
            "params": [
                {
                    "name": "AnimationName",
                    "desc": "The registered name of the animation to play.",
                    "lua_type": "string"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 135,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "PlayTag",
            "desc": "Plays all animations with the given tag.\n\nEquivalent to calling `Play` for each animation registered under the tag. Tag membership\nis set via `:Tag()` or `:Tags()` on the animation builder.\n\n```lua\nController:PlayTag(\"footstep\")\n```\n\nIf no animations are registered under the tag, this is a no-op.",
            "params": [
                {
                    "name": "Tag",
                    "desc": "The tag to look up.",
                    "lua_type": "string"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 151,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "Stop",
            "desc": "Stops an animation.\n\nIf `Immediate` is `true`, the animation stops without any fade-out. Otherwise, the\nanimation fades out using its configured `FadeOutTime`.\n\nIf the animation is in the pending queue but has not started playing, it is removed from\nthe queue immediately.\n\n```lua\nController:Stop(\"Idle\")         -- fade out\nController:Stop(\"Idle\", true)   -- immediate stop\n```",
            "params": [
                {
                    "name": "AnimationName",
                    "desc": "The registered name of the animation to stop.",
                    "lua_type": "string"
                },
                {
                    "name": "Immediate",
                    "desc": "If true, stop without fade. Default false.",
                    "lua_type": "boolean?"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 170,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "StopGroup",
            "desc": "Stops all animations in the given exclusive group.\n\nEquivalent to calling `Stop` on every animation in the group that is currently active or\nqueued.\n\n```lua\nController:StopGroup(\"WeaponAction\")\nController:StopGroup(\"WeaponAction\", true) -- immediate\n```",
            "params": [
                {
                    "name": "GroupName",
                    "desc": "The group name to stop.",
                    "lua_type": "string"
                },
                {
                    "name": "Immediate",
                    "desc": "If true, stop all group members immediately. Default false.",
                    "lua_type": "boolean?"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 186,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "RequestStateTransition",
            "desc": "Requests a manual state transition.\n\nThe transition is queued and processed in the next frame's state machine tick. If\nmultiple transitions are queued in the same frame, the highest priority wins.\n\nManual transitions bypass predicate evaluation. The target state must be defined in the\nbehavior.\n\n```lua\nhumanoid.Jumping:Connect(function()\n\tController:RequestStateTransition(\"Jump\", 100)\nend)\n```",
            "params": [
                {
                    "name": "StateName",
                    "desc": "The target state name.",
                    "lua_type": "string"
                },
                {
                    "name": "Priority",
                    "desc": "Higher values win when multiple transitions compete in the same frame.",
                    "lua_type": "number"
                }
            ],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 208,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "GetActiveGroupAnimMap",
            "desc": "Returns a map from exclusive group name to the currently active animation name in that group.\n\nOnly includes groups that have an animation actively playing (not fading out). Useful for\ndebugging or for game logic that needs to know which animation owns a group slot.\n\n```lua\nlocal GroupMap = Controller:GetActiveGroupAnimMap()\nif GroupMap[\"WeaponAction\"] == \"Reload\" then\n\t-- Block fire input while reloading\nend\n```\n\nIf an invariant is violated (two animations active in the same group), a warning is logged\nand both entries are included.",
            "params": [],
            "returns": [
                {
                    "desc": "Map from group name to active animation name.",
                    "lua_type": "{ [string]: string }"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 230,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "AttachInspector",
            "desc": "Attaches a debug inspector to the controller and returns it.\n\nThe inspector provides a live view of active animations, layer weights, and state machine\nstatus. Attach this during development to verify that layers, groups, and state transitions\nbehave as expected.\n\n```lua\nlocal Inspector = Controller:AttachInspector()\n```",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "DebugInspector"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 245,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "Destroy",
            "desc": "Tears down the controller completely.\n\nDisconnects the frame loop, stops all active animations immediately, destroys all pooled\nwrappers, fires `OnStop` for any active animations, and destroys the `OnPlay` and `OnStop`\nsignals.\n\nAfter this call the controller is inert. Do not call any method on it after `Destroy`.\n\n```lua\nController:Destroy()\n```",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 262,
                "path": "docs/AnimationController.lua"
            }
        }
    ],
    "properties": [
        {
            "name": "OnPlay",
            "desc": "Fires when an animation begins playing on this controller.\n\nThe argument is the animation config name. Connect once at initialization to\nreact to any animation on this controller.\n\n```lua\nController.OnPlay:Connect(function(animationName)\n\t-- React to any animation starting\nend)\n```",
            "lua_type": "Signal<string>",
            "source": {
                "line": 62,
                "path": "docs/AnimationController.lua"
            }
        },
        {
            "name": "OnStop",
            "desc": "Fires when an animation stops on this controller.\n\nThe argument is the animation config name.\n\n```lua\nController.OnStop:Connect(function(animationName)\n\t-- React to any animation stopping\nend)\n```",
            "lua_type": "Signal<string>",
            "source": {
                "line": 77,
                "path": "docs/AnimationController.lua"
            }
        }
    ],
    "types": [],
    "name": "AnimationController",
    "desc": "Per-character animation controller for Motix.\n\n`AnimationController` manages all active animations for a single character or entity. It\ndrives a built-in state machine, routes play requests through layer conflict resolution and\nexclusive group management, and provides per-frame weight interpolation for smooth blending.\n\nCreate one controller per character. Controllers do not share state.\n\n```lua\nlocal Motix = require(ReplicatedStorage.Motix)\n\n-- Build the behavior once per character type\nlocal Behavior = Motix.BehaviorBuilder.Humanoid():Build()\n\n-- Create a controller for a specific character\nlocal Config = Behavior:CreateController(\n\tcharacter.Name,\n\tcharacter.Humanoid:FindFirstChildOfClass(\"Animator\"),\n\ttrue,  -- isOwningClient\n\tPlayers.LocalPlayer\n)\n\nlocal Controller = Motix.new(Config)\n\nController.OnPlay:Connect(function(animName)\n\tprint(\"Playing:\", animName)\nend)\n\nController:Play(\"Idle\")\n```\n\nThe `AnimationController` module is re-exported as the return value of the root `Motix`\nmodule, so `Motix.new(Config)` creates a controller directly.\n\n:::caution Registry required\n`AnimationRegistry` must be initialized before any controller is created. Call\n`AnimationRegistry.GetInstance():Init(configs)` once at startup.\n:::",
    "source": {
        "line": 43,
        "path": "docs/AnimationController.lua"
    }
}