BehaviorBuilder
Fluent typed configuration builder for AnimationController.
Instead of constructing raw config tables by hand, chain builder methods and call
:Build() to produce a validated, frozen BuiltBehavior.
local Motix = require(ReplicatedStorage.Motix)
local Behavior = Motix.BehaviorBuilder.new()
:Animation("Idle")
:AssetId("rbxassetid://IDLE_ID")
:Layer("Base")
:Looped(true)
:Priority(0)
:FadeIn(0.2)
:FadeOut(0.2)
:Done()
:Layer("Base")
:Order(0)
:BaseWeight(1.0)
:LerpRate(8.0)
:Done()
:State("Idle")
:OnEntry():Play("Idle"):Done()
:OnExit():Stop("Idle"):Done()
:ActiveLayer("Base")
:Transition("Walk", "IsWalking"):Priority(10):Done()
:Done()
:Predicate("IsWalking", function()
return humanoid.MoveDirection.Magnitude > 0.1
end)
:InitialState("Idle")
:Build()
Builder overview:
| Method | Configures |
|---|---|
:Animation(name) |
Opens the animation sub-builder |
:Layer(name) |
Opens the layer sub-builder |
:State(name) |
Opens the state sub-builder |
:Predicate(name, fn) |
Registers a boolean predicate function |
:InitialState(name) |
Sets the initial state machine state |
:Clone() |
Returns an independent copy of this builder |
:Merge(other) |
Merges a built behavior into this builder (non-destructive) |
:When(condition, fn) |
Conditionally applies a block without breaking the chain |
:Build() |
Validates and returns a frozen BuiltBehavior |
Presets
Use BehaviorBuilder.Humanoid() as a starting point for standard humanoid characters.
It includes Idle, Walk, and Run animations on Base and UpperBody layers with a
predicate-driven state machine.
Build-time validation
All validation is deferred to :Build(). The builder never throws mid-chain. Errors are
collected and reported together when :Build() is called. :Build() returns nil if
any error is found.
Functions
new
Creates a new empty builder.
Humanoid
Returns a pre-configured builder for a standard humanoid character.
Includes three animations (Idle, Walk, Run) with placeholder asset IDs, two layers
(Base at order 0, UpperBody additive at order 1), and a predicate-driven state machine
with IsIdle, IsWalking, and IsRunning predicates.
Replace the placeholder rbxassetid://0 values with real animation IDs before
calling :Build().
local Behavior = Motix.BehaviorBuilder.Humanoid()
-- Override or extend before building
:Build()
TIP
Use this as a starting point. Chain additional animations, layers, or states on top before
calling :Build().
Animation
BehaviorBuilder:Animation(Name: string--
Unique animation name.
) → AnimationBuilderOpens the animation configuration sub-builder for an animation with the given name.
The name must be unique within this builder. It is also the name used to look up the
animation in AnimationRegistry and the string passed to Controller:Play().
Available setters:
| Method | Type | Default | Description |
|---|---|---|---|
:AssetId(id) |
string |
required | The rbxassetid://... for this animation. |
:Layer(name) |
string |
required | The layer this animation belongs to. |
:Group(name) |
string? |
nil |
Exclusive group name. Only one animation per group may play at a time. |
:Priority(n) |
number |
0 |
Conflict priority within the layer or group. Higher wins. |
:Looped(v) |
boolean |
false |
Whether the animation loops. |
:FadeIn(t) |
number |
0.1 |
Fade-in time in seconds. |
:FadeOut(t) |
number |
0.1 |
Fade-out time in seconds. |
:Speed(s) |
number |
1.0 |
Playback speed multiplier. |
:Weight(w) |
number |
1.0 |
Blend weight for this animation within its layer. Range (0, 1]. |
:CanInterrupt(v) |
boolean |
true |
Whether a lower-priority group member can interrupt this animation. |
:MinDuration(d) |
number? |
nil |
Minimum seconds before this animation can be interrupted by a group conflict. |
:Tag(tag) |
string |
Appends a single tag. Multiple calls allowed. | |
:Tags(tags) |
{ string } |
{} |
Replaces the entire tag list. |
:Additive(v) |
boolean |
false |
Whether the animation track is played in additive blending mode. |
:Metadata(t) |
table? |
nil |
Arbitrary key-value table stored with the config. |
Call :Done() to return to the root builder.
:Animation("Reload")
:AssetId("rbxassetid://RELOAD_ID")
:Layer("UpperBody")
:Group("WeaponAction")
:Priority(5)
:Looped(false)
:FadeIn(0.1)
:FadeOut(0.15)
:CanInterrupt(false)
:Done()
Layer
BehaviorBuilder:Layer(Name: string--
Unique layer name.
) → LayerBuilderOpens the layer configuration sub-builder for a layer with the given name.
The name must be unique within this builder. It must match the Layer field of at least
one animation config or a state's ActiveLayer / SuppressLayer entry.
Available setters:
| Method | Type | Default | Description |
|---|---|---|---|
:Order(n) |
number |
required | Integer order for blending priority. Lower values are evaluated first. Must be unique. |
:BaseWeight(w) |
number |
1.0 |
Default layer weight when active. Range [0, 1]. |
:LerpRate(r) |
number |
8.0 |
Speed in units per second at which weight interpolates toward its target. Use math.huge for instant snapping. |
:Additive(v) |
boolean |
false |
When true, this layer composes with lower layers rather than replacing them. |
:Isolated(v) |
boolean |
false |
When true, this layer does not interact with other layers during blending. |
Call :Done() to return to the root builder.
:Layer("UpperBody")
:Order(1)
:BaseWeight(1.0)
:Additive(true)
:LerpRate(6.0)
:Done()
State
BehaviorBuilder:State(Name: string--
Unique state name.
) → StateBuilderOpens the state configuration sub-builder for a state with the given name.
States define what happens when the state machine enters or exits them, which layers are active or suppressed, and what conditions trigger transitions to other states.
Available methods:
| Method | Description |
|---|---|
:OnEntry() |
Returns a directive list builder for entry actions. |
:OnExit() |
Returns a directive list builder for exit actions. |
:Transition(toState, condition) |
Adds a transition rule. |
:ActiveLayer(name) |
Marks a layer as active (set to BaseWeight) when this state is entered. |
:SuppressLayer(name) |
Marks a layer as suppressed (set to 0) when this state is entered. |
Directive list builder methods (returned by :OnEntry() and :OnExit()):
| Method | Effect |
|---|---|
:Play(animName) |
Plays the animation when the directive fires |
:Stop(animName, immediate?) |
Stops the animation when the directive fires |
:StopGroup(groupName, immediate?) |
Stops the group when the directive fires |
:Done() |
Returns to the state builder |
Transition builder methods (returned by :Transition()):
| Method | Description |
|---|---|
:Priority(n) |
Priority for this transition rule. Higher wins when multiple conditions are true simultaneously. |
:Done() |
Returns to the state builder |
Call :Done() on the state builder to return to the root builder.
:State("Jump")
:OnEntry():Play("JumpRise"):Done()
:OnExit():Stop("JumpRise"):StopGroup("WeaponAction"):Done()
:ActiveLayer("Base")
:SuppressLayer("UpperBody")
:Transition("Idle", "IsGrounded"):Priority(10):Done()
:Done()
States with no transitions are terminal states. The state machine stays in them
indefinitely until RequestStateTransition is called.
Predicate
BehaviorBuilder:Predicate(Name: string,--
The condition name referenced in :Transition(toState, conditionName).
Fn: () → boolean--
The predicate function.
) → BehaviorBuilderRegisters a named predicate function.
Predicates are called every frame by the state machine to evaluate transition conditions. The function must return a boolean. Keep predicates cheap: avoid yielding, remote reads, or per-call allocations.
A predicate registered once can be referenced by any number of transitions across any number of states.
:Predicate("IsGrounded", function()
return humanoid.FloorMaterial ~= Enum.Material.Air
end)
InitialState
Sets the initial state for the state machine.
Must match one of the state names defined with :State(). Required: :Build() returns
nil if InitialState is not set or references an undefined state.
:InitialState("Idle")
Clone
Returns an independent BehaviorBuilder that is a deep copy of this builder.
Animations, layers, states, predicates, and initial state are all cloned. Changes to either builder after cloning do not affect the other.
local Base = Motix.BehaviorBuilder.Humanoid()
local Armed = Base:Clone()
:Animation("Fire")
:AssetId("rbxassetid://FIRE_ID")
:Layer("UpperBody")
:Looped(false)
:Done()
:Build()
-- Base is unchanged
Merge
Merges a built behavior into this builder.
Animations, layers, states, and predicates from Other are added to this builder.
If a name collision is detected (animation, layer, or state with the same name already
exists), the incoming item is skipped and a warning is logged.
Predicates from Other overwrite existing predicates with the same name.
local WeaponBehavior = Motix.BehaviorBuilder.new()
:Animation("Fire"):AssetId("..."):Layer("UpperBody"):Looped(false):Priority(5):Done()
:Build()
local FullBehavior = Motix.BehaviorBuilder.Humanoid()
:Merge(WeaponBehavior)
:Build()
When
BehaviorBuilder:When(Condition: any,--
Truthy value gates the block. Falsy skips it.
) → BehaviorBuilderConditionally applies a block of builder calls without breaking the fluent chain.
If Condition is falsy the builder is returned unchanged. The callback receives self
and is called for its side effects.
local Behavior = Motix.BehaviorBuilder.Humanoid()
:When(isCombatMode, function(b)
b:Animation("ADS"):AssetId("..."):Layer("UpperBody"):Looped(true):Done()
end)
:Build()
Build
BehaviorBuilder:Build() → BuiltBehavior?--
Frozen built behavior, or nil if validation failed.
Validates the current configuration and returns a frozen BuiltBehavior.
All validation errors are collected and logged together. Returns nil if any error is
found. Call :Build() multiple times to produce independent frozen results from the same
builder.
The returned BuiltBehavior exposes one method:
BuiltBehavior:CreateController(characterId, animator, isOwningClient, owningPlayer?)
Returns a ControllerConfig table ready to pass to Motix.new().
local Behavior = BehaviorBuilder.new()
-- ...
:Build()
local Config = Behavior:CreateController(
character.Name,
character.Humanoid:FindFirstChildOfClass("Animator"),
isOwningClient,
player
)
local Controller = Motix.new(Config)