Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SimpleAnimate API

The top-level SimpleAnimate module provides factory functions for creating and managing animation controllers.

Functions

new()

Creates a new AnimationController instance.

Signature:

function new(
    rig: Model,
    doPreload: boolean?,
    coreAnimations: AnimationsList?,
    emoteAnimations: AnimationsList?,
    stateMachine: StateMachine?
): AnimationController

Parameters:

  • rig - The character model (must contain an Animator)
  • doPreload - Whether to preload animations (default: true)
  • coreAnimations - Custom core animations (defaults to R15)
  • emoteAnimations - Custom emote animations (defaults to R15)
  • stateMachine - Custom state machine (defaults to Humanoid)

Returns:

  • AnimationController - A new controller instance

Examples:

Basic usage with defaults:

local controller = SimpleAnimate.new(character)

With custom animations:

local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R15")
coreAnims.Idle[1].id = "rbxassetid://123456789"

local controller = SimpleAnimate.new(
    character,
    true,
    coreAnims,
    emoteAnims
)

Without preloading (load animations on-demand):

local controller = SimpleAnimate.new(character, false)

With custom state machine:

local customStateMachine = character:FindFirstChild("CustomHumanoid")
local controller = SimpleAnimate.new(
    character,
    true,
    nil,
    nil,
    customStateMachine
)

fromExisting()

Gets an existing AnimationController for the specified rig.

Signature:

function fromExisting(rig: Model): AnimationController?

Parameters:

  • rig - The character model to get the controller for

Returns:

  • AnimationController? - The existing controller or nil if none exists

Examples:

local controller = SimpleAnimate.fromExisting(character)

if controller then
    print("Controller already exists!")
    controller.Core.PoseController:ChangePose("Idle")
else
    print("No controller found, creating new one...")
    controller = SimpleAnimate.new(character)
end

Checking multiple characters:

for _, player in Players:GetPlayers() do
    local char = player.Character
    if char then
        local controller = SimpleAnimate.fromExisting(char)
        if controller then
            print(player.Name, "has a controller")
        end
    end
end

awaitController()

Waits for an AnimationController to be created for the specified rig. Yields until available or timeout occurs.

Signature:

function awaitController(rig: Model, timeOut: number?): AnimationController?

Parameters:

  • rig - The character model to wait for
  • timeOut - Timeout in seconds (default: 5)

Returns:

  • AnimationController? - The controller or nil on timeout

Examples:

Basic usage:

-- Wait up to 5 seconds for controller
local controller = SimpleAnimate.awaitController(character)

if controller then
    print("Controller ready!")
else
    warn("Controller not found within timeout")
end

Custom timeout:

-- Wait up to 10 seconds
local controller = SimpleAnimate.awaitController(character, 10)

Useful in separate scripts:

-- Script A creates the controller
SimpleAnimate.new(character)

-- Script B (elsewhere) waits for it
local controller = SimpleAnimate.awaitController(character)
controller.Action:PlayAction("wave")

getCopyOfAnimsList()

Gets a deep copy of a default animation list by name and type.

Signature:

function getCopyOfAnimsList(
    name: "R6" | "R15",
    specifier: "Animations" | "Emotes"
): AnimationsList

Parameters:

  • name - The rig type ("R6" or "R15")
  • specifier - The list type ("Animations" or "Emotes")

Returns:

  • AnimationsList - A deep copy of the requested list

Examples:

Get R15 core animations:

local r15Anims = SimpleAnimate.getCopyOfAnimsList("R15", "Animations")

-- Modify as needed
r15Anims.Idle[1].speed = 0.8
r15Anims.Run[1].id = "rbxassetid://123456789"

Get R6 emotes:

local r6Emotes = SimpleAnimate.getCopyOfAnimsList("R6", "Emotes")

-- Add custom emote
r6Emotes.customWave = {
    {
        id = "rbxassetid://987654321",
        weight = 10
    }
}

getCopyOfAnims()

Gets deep copies of both default animation lists (Animations and Emotes).

Signature:

function getCopyOfAnims(name: "R6" | "R15"): (AnimationsList, AnimationsList)

Parameters:

  • name - The rig type ("R6" or "R15")

Returns:

  • AnimationsList - Core animations
  • AnimationsList - Emote animations

Examples:

Get both R15 lists:

local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R15")

-- Customize both
coreAnims.Walk[1].speed = 1.2
emoteAnims.wave[1].fadeTime = 0.3

local controller = SimpleAnimate.new(character, true, coreAnims, emoteAnims)

Get R6 lists:

local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R6")

getAnimPackageAsync()

Gets a player's equipped animation package asynchronously. Fills in missing animations with defaults.

Signature:

function getAnimPackageAsync(
    player: Player?,
    defaultPoseSourceIfUnavailable: AnimationsList?
): AnimationsList?

Parameters:

  • player - The player (required when called from server, optional on client)
  • defaultPoseSourceIfUnavailable - Fallback animations (defaults to R15)

Returns:

  • AnimationsList? - The player's animation package with defaults filled in

Examples:

Server-side:

local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        -- Get player's equipped animations
        local anims = SimpleAnimate.getAnimPackageAsync(player)
        
        local controller = SimpleAnimate.new(character, true, anims)
    end)
end)

Client-side:

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()

-- Get local player's animations (no player parameter needed)
local anims = SimpleAnimate.getAnimPackageAsync()

local controller = SimpleAnimate.new(character, true, anims)

With custom fallbacks:

local customDefaults = SimpleAnimate.getCopyOfAnimsList("R15", "Animations")
customDefaults.Idle[1].id = "rbxassetid://123456789"

local anims = SimpleAnimate.getAnimPackageAsync(player, customDefaults)

getEmotePackageAsync()

Gets a player's equipped emote package asynchronously.

Signature:

function getEmotePackageAsync(
    player: Player?,
    defaultEmoteSourceIfUnavailable: AnimationsList?
): AnimationsList?

Parameters:

  • player - The player (required when called from server)
  • defaultEmoteSourceIfUnavailable - Fallback emotes (defaults to R15)

Returns:

  • AnimationsList? - The player's emote package

Examples:

Server-side:

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local anims = SimpleAnimate.getAnimPackageAsync(player)
        local emotes = SimpleAnimate.getEmotePackageAsync(player)
        
        local controller = SimpleAnimate.new(
            character,
            true,
            anims,
            emotes
        )
    end)
end)

Client-side:

local emotes = SimpleAnimate.getEmotePackageAsync()

Properties

Preload

Reference to the Preload utility module.

Example:

local animator = character:FindFirstChildWhichIsA("Animator", true)
local animList = {
    Idle = {{id = "rbxassetid://123", weight = 10}}
}

local preloaded = SimpleAnimate.Preload.preloadAnimList(
    animator,
    animList,
    "core",
    Enum.AnimationPriority.Core,
    true
)

default

Reference to default animation lists (DefaultAnims module).

Example:

-- Access default R15 animations directly
local defaultR15 = SimpleAnimate.default.R15

print(defaultR15.Animations.Idle[1].id)
print(defaultR15.Emotes.wave[1].id)

Quick Start

Basic Setup

The simplest way to get started with SimpleAnimate:

local SimpleAnimate = require(path.to.SimpleAnimate)

-- Wait for character
local character = player.Character or player.CharacterAdded:Wait()

-- Create controller with default R15 animations
local controller = SimpleAnimate.new(character)

That's it! The controller will automatically handle all character animations based on humanoid state changes.

Playing an Emote

-- Play a random animation from the "wave" emote
local track = controller.Action:PlayAction("wave")

-- Wait for it to finish
track.Stopped:Wait()
print("Wave animation completed!")

Changing a Core Animation

-- Change the idle animation
controller.Core.PoseController:ChangeCoreAnim(
    "Idle",
    1,
    "rbxassetid://YOUR_ANIMATION_ID"
)

Listening to Pose Changes

controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    print(string.format("Pose changed: %s -> %s", oldPose, newPose))
end)

Getting Current State

-- Get current pose
local currentPose = controller.Core.PoseController:GetPose()
print("Current pose:", currentPose) -- "Idle", "Walk", "Run", etc.

-- Get current animation track
local currentTrack = controller.Core.PoseController:GetCurrentTrack()
print("Current speed:", currentTrack.Speed)

Creating Custom Actions

-- Define a custom action
local customAction = {
    {
        id = "rbxassetid://123456789",
        weight = 10,
        speed = 1,
        fadeTime = 0.2
    }
}

-- Add it to the controller
controller.Action:CreateAction("myCustomAction", customAction)

-- Play it
controller.Action:PlayAction("myCustomAction")

Cleanup

Always destroy the controller when done:

-- Cleanup (automatically called when character is destroyed)
controller:Destroy()

Next Steps

SimpleAnimate API

The top-level SimpleAnimate module provides factory functions for creating and managing animation controllers.

Functions

new()

Creates a new AnimationController instance.

Signature:

function new(
    rig: Model,
    doPreload: boolean?,
    coreAnimations: AnimationsList?,
    emoteAnimations: AnimationsList?,
    stateMachine: StateMachine?
): AnimationController

Parameters:

  • rig - The character model (must contain an Animator)
  • doPreload - Whether to preload animations (default: true)
  • coreAnimations - Custom core animations (defaults to R15)
  • emoteAnimations - Custom emote animations (defaults to R15)
  • stateMachine - Custom state machine (defaults to Humanoid)

Returns:

  • AnimationController - A new controller instance

Examples:

Basic usage with defaults:

local controller = SimpleAnimate.new(character)

With custom animations:

local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R15")
coreAnims.Idle[1].id = "rbxassetid://123456789"

local controller = SimpleAnimate.new(
    character,
    true,
    coreAnims,
    emoteAnims
)

Without preloading (load animations on-demand):

local controller = SimpleAnimate.new(character, false)

With custom state machine:

local customStateMachine = character:FindFirstChild("CustomHumanoid")
local controller = SimpleAnimate.new(
    character,
    true,
    nil,
    nil,
    customStateMachine
)

fromExisting()

Gets an existing AnimationController for the specified rig.

Signature:

function fromExisting(rig: Model): AnimationController?

Parameters:

  • rig - The character model to get the controller for

Returns:

  • AnimationController? - The existing controller or nil if none exists

Examples:

local controller = SimpleAnimate.fromExisting(character)

if controller then
    print("Controller already exists!")
    controller.Core.PoseController:ChangePose("Idle")
else
    print("No controller found, creating new one...")
    controller = SimpleAnimate.new(character)
end

Checking multiple characters:

for _, player in Players:GetPlayers() do
    local char = player.Character
    if char then
        local controller = SimpleAnimate.fromExisting(char)
        if controller then
            print(player.Name, "has a controller")
        end
    end
end

awaitController()

Waits for an AnimationController to be created for the specified rig. Yields until available or timeout occurs.

Signature:

function awaitController(rig: Model, timeOut: number?): AnimationController?

Parameters:

  • rig - The character model to wait for
  • timeOut - Timeout in seconds (default: 5)

Returns:

  • AnimationController? - The controller or nil on timeout

Examples:

Basic usage:

-- Wait up to 5 seconds for controller
local controller = SimpleAnimate.awaitController(character)

if controller then
    print("Controller ready!")
else
    warn("Controller not found within timeout")
end

Custom timeout:

-- Wait up to 10 seconds
local controller = SimpleAnimate.awaitController(character, 10)

Useful in separate scripts:

-- Script A creates the controller
SimpleAnimate.new(character)

-- Script B (elsewhere) waits for it
local controller = SimpleAnimate.awaitController(character)
controller.Action:PlayAction("wave")

getCopyOfAnimsList()

Gets a deep copy of a default animation list by name and type.

Signature:

function getCopyOfAnimsList(
    name: "R6" | "R15",
    specifier: "Animations" | "Emotes"
): AnimationsList

Parameters:

  • name - The rig type ("R6" or "R15")
  • specifier - The list type ("Animations" or "Emotes")

Returns:

  • AnimationsList - A deep copy of the requested list

Examples:

Get R15 core animations:

local r15Anims = SimpleAnimate.getCopyOfAnimsList("R15", "Animations")

-- Modify as needed
r15Anims.Idle[1].speed = 0.8
r15Anims.Run[1].id = "rbxassetid://123456789"

Get R6 emotes:

local r6Emotes = SimpleAnimate.getCopyOfAnimsList("R6", "Emotes")

-- Add custom emote
r6Emotes.customWave = {
    {
        id = "rbxassetid://987654321",
        weight = 10
    }
}

getCopyOfAnims()

Gets deep copies of both default animation lists (Animations and Emotes).

Signature:

function getCopyOfAnims(name: "R6" | "R15"): (AnimationsList, AnimationsList)

Parameters:

  • name - The rig type ("R6" or "R15")

Returns:

  • AnimationsList - Core animations
  • AnimationsList - Emote animations

Examples:

Get both R15 lists:

local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R15")

-- Customize both
coreAnims.Walk[1].speed = 1.2
emoteAnims.wave[1].fadeTime = 0.3

local controller = SimpleAnimate.new(character, true, coreAnims, emoteAnims)

Get R6 lists:

local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R6")

getAnimPackageAsync()

Gets a player's equipped animation package asynchronously. Fills in missing animations with defaults.

Signature:

function getAnimPackageAsync(
    player: Player?,
    defaultPoseSourceIfUnavailable: AnimationsList?
): AnimationsList?

Parameters:

  • player - The player (required when called from server, optional on client)
  • defaultPoseSourceIfUnavailable - Fallback animations (defaults to R15)

Returns:

  • AnimationsList? - The player's animation package with defaults filled in

Examples:

Server-side:

local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        -- Get player's equipped animations
        local anims = SimpleAnimate.getAnimPackageAsync(player)
        
        local controller = SimpleAnimate.new(character, true, anims)
    end)
end)

Client-side:

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()

-- Get local player's animations (no player parameter needed)
local anims = SimpleAnimate.getAnimPackageAsync()

local controller = SimpleAnimate.new(character, true, anims)

With custom fallbacks:

local customDefaults = SimpleAnimate.getCopyOfAnimsList("R15", "Animations")
customDefaults.Idle[1].id = "rbxassetid://123456789"

local anims = SimpleAnimate.getAnimPackageAsync(player, customDefaults)

getEmotePackageAsync()

Gets a player's equipped emote package asynchronously.

Signature:

function getEmotePackageAsync(
    player: Player?,
    defaultEmoteSourceIfUnavailable: AnimationsList?
): AnimationsList?

Parameters:

  • player - The player (required when called from server)
  • defaultEmoteSourceIfUnavailable - Fallback emotes (defaults to R15)

Returns:

  • AnimationsList? - The player's emote package

Examples:

Server-side:

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local anims = SimpleAnimate.getAnimPackageAsync(player)
        local emotes = SimpleAnimate.getEmotePackageAsync(player)
        
        local controller = SimpleAnimate.new(
            character,
            true,
            anims,
            emotes
        )
    end)
end)

Client-side:

local emotes = SimpleAnimate.getEmotePackageAsync()

Properties

Preload

Reference to the Preload utility module.

Example:

local animator = character:FindFirstChildWhichIsA("Animator", true)
local animList = {
    Idle = {{id = "rbxassetid://123", weight = 10}}
}

local preloaded = SimpleAnimate.Preload.preloadAnimList(
    animator,
    animList,
    "core",
    Enum.AnimationPriority.Core,
    true
)

default

Reference to default animation lists (DefaultAnims module).

Example:

-- Access default R15 animations directly
local defaultR15 = SimpleAnimate.default.R15

print(defaultR15.Animations.Idle[1].id)
print(defaultR15.Emotes.wave[1].id)

AnimationController API

The AnimationController is the main instance returned by SimpleAnimate.new(). It provides access to both core animation systems and action/emote systems.

Created by: SimpleAnimate.new()

Properties

Core

Access to the core animation system.

Type: Core

Contains:

  • PoseController - Controls pose transitions and animation playback
  • Connections - Manages humanoid event connections

Examples:

Access PoseController:

local controller = SimpleAnimate.new(character)

-- Get current pose
local pose = controller.Core.PoseController:GetPose()
print("Current pose:", pose)

-- Listen for pose changes
controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose)
    print("Pose changed:", oldPose, "->", newPose)
end)

Access Connections configuration:

local controller = SimpleAnimate.new(character)

-- Adjust run threshold
controller.Core.Connections.RunThreshold = 12

-- Modify animation speed
controller.Core.Connections.MoveAnimationSpeedMultiplier = 1.5

Action

Access to the action/emote animation system.

Type: Actions

Methods:

  • CreateAction() - Create a new action
  • PlayAction() - Play an action
  • RemoveAction() - Remove an action
  • And more (see Action API)

Examples:

Play emotes:

local controller = SimpleAnimate.new(character)

-- Play a wave emote
controller.Action:PlayAction("wave")

-- Create custom action
controller.Action:CreateAction("celebrate", {
    {id = "rbxassetid://123456", weight = 10}
})

-- Play it
controller.Action:PlayAction("celebrate")

Methods

:Destroy()

Destroys the AnimationController and cleans up all resources.

Signature:

function Destroy(): ()

Behavior:

  • Destroys the Core system (PoseController and Connections)
  • Destroys the Action system
  • Disconnects all event connections
  • Removes the controller from the global registry
  • Cleans up all internal references

Examples:

Manual cleanup:

local controller = SimpleAnimate.new(character)

-- Later...
controller:Destroy()

Cleanup on character removal:

local controller = SimpleAnimate.new(character)

character.Destroying:Connect(function()
    controller:Destroy()
end)

Note: The controller automatically destroys itself when the character is destroyed, so manual cleanup is usually not necessary.

Cleanup before respawn:

local Players = game:GetService("Players")
local controllers = {}

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        -- Clean up old controller if exists
        if controllers[player] then
            controllers[player]:Destroy()
        end
        
        -- Create new controller
        controllers[player] = SimpleAnimate.new(character)
    end)
    
    player.CharacterRemoving:Connect(function()
        if controllers[player] then
            controllers[player]:Destroy()
            controllers[player] = nil
        end
    end)
end)

Complete Examples

Basic Character Setup

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Create controller
local controller = SimpleAnimate.new(character)

-- Access core animations
print("Current pose:", controller.Core.PoseController:GetPose())

-- Play emote
controller.Action:PlayAction("wave")

-- Cleanup (automatic, but can be manual)
character.Destroying:Connect(function()
    controller:Destroy()
end)

Custom Animation System

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Get custom animations
local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R15")

-- Customize
coreAnims.Idle[1].id = "rbxassetid://CUSTOM_IDLE"
emoteAnims.wave[1].id = "rbxassetid://CUSTOM_WAVE"

-- Create controller
local controller = SimpleAnimate.new(
    character,
    true,
    coreAnims,
    emoteAnims
)

-- Configure behavior
controller.Core.Connections.RunThreshold = 14
controller.Core.Connections.MoveAnimationSpeedMultiplier = 1.2

-- Add custom actions
controller.Action:CreateAction("customEmote", {
    {id = "rbxassetid://123", weight = 10}
})

Multiple Controllers (NPCs)

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)
local npcControllers = {}

local function setupNPC(npc)
    local controller = SimpleAnimate.new(npc)
    
    -- Store reference
    npcControllers[npc] = controller
    
    -- Configure NPC-specific settings
    controller.Core.Connections.RunThreshold = 10
    controller.Core.Connections.MoveAnimationSpeedMultiplier = 0.8
    
    return controller
end

-- Setup all NPCs
for _, npc in workspace.NPCs:GetChildren() do
    setupNPC(npc)
end

-- Cleanup when NPC is removed
workspace.NPCs.ChildRemoved:Connect(function(npc)
    if npcControllers[npc] then
        npcControllers[npc]:Destroy()
        npcControllers[npc] = nil
    end
end)

Controller with Monitoring

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Create controller
local controller = SimpleAnimate.new(character)

-- Monitor pose changes
controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    print(string.format(
        "[%s] Pose: %s -> %s (Speed: %.2f)",
        character.Name,
        oldPose,
        newPose,
        track.Speed
    ))
end)

-- Monitor animation speed
game:GetService("RunService").Heartbeat:Connect(function()
    local track = controller.Core.PoseController:GetCurrentTrack()
    if track then
        -- Log or display animation info
        print("Current animation speed:", track.Speed)
    end
end)

Dynamic Animation Switching

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

local controller = SimpleAnimate.new(character)

-- Normal animations
local normalAnims = {
    Idle = {{id = "rbxassetid://NORMAL_IDLE", weight = 10}},
    Walk = {{id = "rbxassetid://NORMAL_WALK", weight = 10}},
    Run = {{id = "rbxassetid://NORMAL_RUN", weight = 10}}
}

-- Combat animations
local combatAnims = {
    Idle = {{id = "rbxassetid://COMBAT_IDLE", weight = 10}},
    Walk = {{id = "rbxassetid://COMBAT_WALK", weight = 10}},
    Run = {{id = "rbxassetid://COMBAT_RUN", weight = 10}}
}

-- Switch to combat mode
local function enterCombatMode()
    for pose, animInfos in combatAnims do
        controller.Core.PoseController:ChangeCoreAnim(pose, 1, animInfos[1])
    end
    print("Entered combat mode")
end

-- Switch to normal mode
local function exitCombatMode()
    for pose, animInfos in normalAnims do
        controller.Core.PoseController:ChangeCoreAnim(pose, 1, animInfos[1])
    end
    print("Exited combat mode")
end

-- Listen for combat state changes
character:GetAttributeChangedSignal("InCombat"):Connect(function()
    if character:GetAttribute("InCombat") then
        enterCombatMode()
    else
        exitCombatMode()
    end
end)

Controller with Custom State Machine

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)
local CustomStateMachine = require(game.ReplicatedStorage.CustomStateMachine)

-- Create custom state machine
local stateMachine = CustomStateMachine.new(character)

-- Create controller with custom state machine
local controller = SimpleAnimate.new(
    character,
    true,
    nil,
    nil,
    stateMachine
)

-- Control animations through state machine
stateMachine:FireRunning(10)  -- Walk
stateMachine:FireRunning(20)  -- Run
stateMachine:FireJumping()    -- Jump

Controller with Error Handling

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

local function safeCreateController(character)
    local success, controller = pcall(function()
        return SimpleAnimate.new(character)
    end)
    
    if success then
        print("Controller created for", character.Name)
        
        -- Setup monitoring
        controller.Core.PoseController.PoseChanged:Connect(function(old, new)
            print("Pose changed:", old, "->", new)
        end)
        
        return controller
    else
        warn("Failed to create controller:", controller)
        return nil
    end
end

-- Usage
local controller = safeCreateController(character)

if controller then
    -- Controller ready
    controller.Action:PlayAction("wave")
else
    -- Fallback behavior
    warn("Using fallback animation system")
end

Controller Pool System

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)
local Players = game:GetService("Players")

local ControllerPool = {}
ControllerPool.__index = ControllerPool

function ControllerPool.new()
    local self = setmetatable({}, ControllerPool)
    self._controllers = {}
    return self
end

function ControllerPool:Add(player, character)
    -- Remove old controller if exists
    self:Remove(player)
    
    -- Create new controller
    local controller = SimpleAnimate.new(character)
    self._controllers[player.UserId] = {
        player = player,
        character = character,
        controller = controller
    }
    
    return controller
end

function ControllerPool:Get(player)
    local entry = self._controllers[player.UserId]
    return entry and entry.controller
end

function ControllerPool:Remove(player)
    local entry = self._controllers[player.UserId]
    if entry then
        entry.controller:Destroy()
        self._controllers[player.UserId] = nil
    end
end

function ControllerPool:GetAll()
    local controllers = {}
    for _, entry in self._controllers do
        table.insert(controllers, entry.controller)
    end
    return controllers
end

-- Usage
local pool = ControllerPool.new()

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        pool:Add(player, character)
    end)
    
    player.CharacterRemoving:Connect(function()
        pool:Remove(player)
    end)
end)

Players.PlayerRemoving:Connect(function(player)
    pool:Remove(player)
end)

-- Get a specific controller
local controller = pool:Get(player)

-- Get all controllers
local allControllers = pool:GetAll()
for _, controller in allControllers do
    -- Do something with each controller
    controller.Core.Connections.RunThreshold = 15
end

Accessing Existing Controllers

Using fromExisting()

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- In Script A: Create controller
local controller = SimpleAnimate.new(character)

-- In Script B: Get existing controller
local controller = SimpleAnimate.fromExisting(character)

if controller then
    -- Controller exists, use it
    controller.Action:PlayAction("wave")
else
    -- Controller doesn't exist
    warn("No controller found for", character.Name)
end

Using awaitController()

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Wait for controller to be created (with timeout)
local controller = SimpleAnimate.awaitController(character, 5)

if controller then
    print("Controller ready!")
    controller.Action:PlayAction("dance")
else
    warn("Controller not found within 5 seconds")
end

Best Practices

  1. Let it auto-cleanup: The controller automatically destroys itself when the character is destroyed
  2. One controller per character: Don't create multiple controllers for the same character
  3. Use fromExisting(): Check for existing controllers before creating new ones
  4. Store references: Keep references to controllers you need to access later
  5. Handle errors: Use pcall when creating controllers for robustness
  6. Clean up properly: If you manually destroy, ensure you also clean up your references
  7. Don't over-configure: Default settings work well for most use cases

Common Patterns

Lazy Controller Creation

local controllers = {}

local function getOrCreateController(character)
    if controllers[character] then
        return controllers[character]
    end
    
    local controller = SimpleAnimate.new(character)
    controllers[character] = controller
    
    character.Destroying:Connect(function()
        controllers[character] = nil
    end)
    
    return controller
end

-- Usage
local controller = getOrCreateController(character)

Controller with Lifecycle Hooks

local function createControllerWithHooks(character, options)
    options = options or {}
    
    -- onCreate hook
    if options.onCreate then
        options.onCreate(character)
    end
    
    -- Create controller
    local controller = SimpleAnimate.new(character)
    
    -- onReady hook
    if options.onReady then
        options.onReady(controller)
    end
    
    -- onDestroy hook
    character.Destroying:Connect(function()
        if options.onDestroy then
            options.onDestroy(controller)
        end
    end)
    
    return controller
end

-- Usage
local controller = createControllerWithHooks(character, {
    onCreate = function(char)
        print("Creating controller for", char.Name)
    end,
    onReady = function(ctrl)
        print("Controller ready!")
        ctrl.Action:PlayAction("wave")
    end,
    onDestroy = function(ctrl)
        print("Controller destroyed")
    end
})

Core API

The Core module manages core character animations and automatic behavior in response to humanoid state changes. It consists of two main components: PoseController and Connections.

Access: controller.Core

Overview

The Core system handles:

  • Automatic animation playback based on character movement
  • Pose transitions (idle, walk, run, jump, etc.)
  • Animation speed adjustments based on movement speed
  • State machine event handling
  • Customizable animation behavior

Properties

PoseController

The pose controller manages animation playback and pose transitions.

Type: PoseController

Purpose: Controls which animations play based on the character's current state/pose

See: PoseController API

Examples:

Get current pose:

local core = controller.Core
local pose = core.PoseController:GetPose()
print("Current pose:", pose) -- "Idle", "Walk", "Run", etc.

Change pose manually:

core.PoseController:ChangePose("Idle")

Listen for pose changes:

core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    print(string.format("Changed from %s to %s", oldPose, newPose))
    print("Animation ID:", track.Animation.AnimationId)
end)

Connections

Manages humanoid event connections and animation behavior settings.

Type: Connections

Purpose: Handles automatic pose changes based on humanoid events (running, jumping, etc.) and provides configuration options for animation behavior

See: Connections API

Examples:

Configure run threshold:

local core = controller.Core

-- Character runs at speeds above 12
core.Connections.RunThreshold = 12

Adjust animation speeds:

-- Make all animations play faster
core.Connections.MoveAnimationSpeedMultiplier = 1.5

-- Make climbing animations slower
core.Connections.ClimbAnimationSpeedMultiplier = 0.7

Configure jump timing:

-- Stay in jump animation longer before falling
core.Connections.JumpDuration = 0.3

Methods

:Destroy()

Destroys the Core instance and cleans up resources.

Signature:

function Destroy(): ()

Behavior:

  • Destroys the PoseController
  • Destroys the Connections module
  • Disconnects all event connections
  • Cleans up all references

Note: This is automatically called when the AnimationController is destroyed. You should not need to call this directly.

Complete Examples

Basic Core Usage

local controller = SimpleAnimate.new(character)
local core = controller.Core

-- Get current state
local pose = core.PoseController:GetPose()
local track = core.PoseController:GetCurrentTrack()
local isActive = core.PoseController:GetCoreActive()

print("Pose:", pose)
print("Animation:", track.Animation.AnimationId)
print("Active:", isActive)

-- Configure behavior
core.Connections.RunThreshold = 15
core.Connections.MoveAnimationSpeedMultiplier = 1.2

Monitoring Character State

local core = controller.Core

-- Monitor pose changes
core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    print(string.format("[%s] %s -> %s", 
        os.date("%X"), oldPose, newPose))
end)

-- Get current state every second
while true do
    task.wait(1)
    
    local pose = core.PoseController:GetPose()
    local track = core.PoseController:GetCurrentTrack()
    
    print(string.format("State: %s (Speed: %.2f)", pose, track.Speed))
end

Custom Movement Behavior

local core = controller.Core

-- Configure for heavy character
core.Connections.RunThreshold = 12  -- Runs at lower speed
core.Connections.MoveAnimationSpeedMultiplier = 0.8  -- Slower animations
core.Connections.ClimbAnimationSpeedMultiplier = 0.6  -- Much slower climbing
core.Connections.JumpDuration = 0.15  -- Shorter jump

-- Configure animation speeds
core.Connections.IdleAnimationSpeed = 0.9
core.Connections.SwimIdleAnimationSpeed = 0.7

Dynamic Speed Adjustment

local core = controller.Core
local humanoid = character:FindFirstChildWhichIsA("Humanoid")

-- Adjust animations based on health
humanoid.HealthChanged:Connect(function(health)
    local healthPercent = health / humanoid.MaxHealth
    
    if healthPercent > 0.7 then
        -- Healthy: Normal speed
        core.Connections.MoveAnimationSpeedMultiplier = 1.0
        core.Connections.RunThreshold = 15
    elseif healthPercent > 0.3 then
        -- Injured: Slower
        core.Connections.MoveAnimationSpeedMultiplier = 0.7
        core.Connections.RunThreshold = 12
    else
        -- Critical: Very slow
        core.Connections.MoveAnimationSpeedMultiplier = 0.5
        core.Connections.RunThreshold = 10
    end
end)

Temporary Animation Override

local core = controller.Core

-- Save original settings
local originalRunThreshold = core.Connections.RunThreshold
local originalSpeedMult = core.Connections.MoveAnimationSpeedMultiplier

-- Apply temporary boost
local function applySpeedBoost(duration)
    -- Stop core animations temporarily
    core.PoseController:SetCoreActive(false)
    core.PoseController:StopCoreAnimations(0.1)
    
    -- Play boost animation
    local boostAnim = animator:LoadAnimation(boostAnimation)
    boostAnim:Play()
    
    task.wait(0.5)
    
    -- Resume with boosted settings
    core.PoseController:SetCoreActive(true)
    core.Connections.RunThreshold = 20
    core.Connections.MoveAnimationSpeedMultiplier = 2.0
    
    -- Restore after duration
    task.delay(duration, function()
        core.Connections.RunThreshold = originalRunThreshold
        core.Connections.MoveAnimationSpeedMultiplier = originalSpeedMult
    end)
end

applySpeedBoost(5) -- 5 second boost

Conditional Animation System

local core = controller.Core

-- Different settings for different areas
local zones = {
    Normal = {
        RunThreshold = 15,
        SpeedMult = 1.0
    },
    SlowZone = {
        RunThreshold = 10,
        SpeedMult = 0.6
    },
    FastZone = {
        RunThreshold = 20,
        SpeedMult = 1.5
    }
}

local function enterZone(zoneName)
    local settings = zones[zoneName]
    if not settings then return end
    
    core.Connections.RunThreshold = settings.RunThreshold
    core.Connections.MoveAnimationSpeedMultiplier = settings.SpeedMult
    
    print("Entered", zoneName)
end

-- Detect zones
local currentZone = "Normal"

workspace.SlowZone.Touched:Connect(function(hit)
    if hit.Parent == character then
        enterZone("SlowZone")
    end
end)

workspace.FastZone.Touched:Connect(function(hit)
    if hit.Parent == character then
        enterZone("FastZone")
    end
end)

State-Based Animation Control

local core = controller.Core

-- Game states
local states = {
    Exploring = {
        active = true,
        runThreshold = 15,
        speedMult = 1.0
    },
    Combat = {
        active = true,
        runThreshold = 12,
        speedMult = 1.2
    },
    Cutscene = {
        active = false,  -- Disable core animations
        runThreshold = 0,
        speedMult = 0
    },
    Stunned = {
        active = false,
        runThreshold = 0,
        speedMult = 0
    }
}

local function setState(stateName)
    local state = states[stateName]
    if not state then return end
    
    -- Apply state settings
    core.PoseController:SetCoreActive(state.active)
    core.Connections.RunThreshold = state.runThreshold
    core.Connections.MoveAnimationSpeedMultiplier = state.speedMult
    
    -- Stop animations if not active
    if not state.active then
        core.PoseController:StopCoreAnimations(0.2)
    end
    
    print("State changed to:", stateName)
end

-- Usage
setState("Exploring")  -- Normal gameplay
setState("Combat")     -- Combat mode
setState("Cutscene")   -- During cutscene
setState("Stunned")    -- Character stunned

Pose-Specific Behavior

local core = controller.Core

-- Track time in each pose
local poseTimers = {}

core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    -- Record time entering pose
    poseTimers[newPose] = tick()
    
    -- Special behavior for specific poses
    if newPose == "Jumping" then
        print("Character jumped!")
        -- Play jump sound, create particle effect, etc.
        
    elseif newPose == "Freefall" then
        print("Character is falling!")
        -- Start fall damage calculation
        
    elseif newPose == "Landed" then
        print("Character landed!")
        
        -- Calculate fall time
        local fallTime = tick() - (poseTimers.Freefall or tick())
        if fallTime > 1 then
            print("Long fall! Time:", fallTime)
            -- Apply fall damage, dust effect, etc.
        end
        
    elseif newPose == "Swimming" then
        print("Character entered water!")
        -- Play splash effect
        
    elseif newPose == "Climbing" then
        print("Character started climbing!")
        -- Play climb sound
    end
end)

Animation Speed Based on Status

local core = controller.Core
local statusEffects = {}

-- Status effect system
local function addStatusEffect(effectName, settings)
    statusEffects[effectName] = settings
    updateAnimationSpeed()
end

local function removeStatusEffect(effectName)
    statusEffects[effectName] = nil
    updateAnimationSpeed()
end

function updateAnimationSpeed()
    local totalSpeedMult = 1.0
    local totalThresholdMult = 1.0
    
    -- Apply all active status effects
    for _, effect in statusEffects do
        totalSpeedMult = totalSpeedMult * effect.speedMult
        totalThresholdMult = totalThresholdMult * effect.thresholdMult
    end
    
    -- Apply to core
    core.Connections.MoveAnimationSpeedMultiplier = totalSpeedMult
    core.Connections.RunThreshold = 15 * totalThresholdMult
    
    print(string.format("Speed: %.2fx, Threshold: %.1f", 
        totalSpeedMult, core.Connections.RunThreshold))
end

-- Usage
addStatusEffect("SpeedBoost", {speedMult = 1.5, thresholdMult = 1.2})
addStatusEffect("Slow", {speedMult = 0.6, thresholdMult = 0.8})

task.delay(5, function()
    removeStatusEffect("SpeedBoost")
end)

Swimming System Integration

local core = controller.Core

-- Custom swimming configuration
core.Connections.SwimIdleThreshold = 2.5
core.Connections.SwimIdleAnimationSpeed = 0.8
core.Connections.SwimAnimationSpeedMultiplier = 1.1

-- Monitor swimming state
local inWater = false

core.PoseController.PoseChanged:Connect(function(oldPose, newPose)
    if newPose == "Swimming" or newPose == "SwimIdle" then
        if not inWater then
            inWater = true
            print("Entered water")
            
            -- Play splash effect
            local splash = splashEffect:Clone()
            splash.Parent = character.HumanoidRootPart
            splash:Emit(20)
            
            -- Play splash sound
            local sound = splashSound:Clone()
            sound.Parent = character.HumanoidRootPart
            sound:Play()
        end
    else
        if inWater then
            inWater = false
            print("Exited water")
            
            -- Play exit splash
        end
    end
end)

Performance Monitoring

local core = controller.Core
local RunService = game:GetService("RunService")

-- Track animation performance
local animStats = {
    poseChanges = 0,
    lastPoseChange = 0,
    averageFPS = 60
}

core.PoseController.PoseChanged:Connect(function()
    animStats.poseChanges = animStats.poseChanges + 1
    animStats.lastPoseChange = tick()
end)

-- Monitor performance
RunService.Heartbeat:Connect(function()
    animStats.averageFPS = 1 / RunService.Heartbeat:Wait()
    
    -- If performance is poor, reduce animation quality
    if animStats.averageFPS < 30 then
        core.Connections.MoveAnimationSpeedMultiplier = 0.5
        warn("Low FPS, reducing animation quality")
    end
end)

-- Print stats
task.spawn(function()
    while true do
        task.wait(5)
        print("Animation Stats:")
        print("  Pose Changes:", animStats.poseChanges)
        print("  Last Change:", tick() - animStats.lastPoseChange, "seconds ago")
        print("  Average FPS:", math.floor(animStats.averageFPS))
    end
end)

Character Class System

local core = controller.Core

-- Define character classes
local classes = {
    Warrior = {
        runThreshold = 14,
        speedMult = 1.0,
        jumpDuration = 0.2
    },
    Rogue = {
        runThreshold = 16,
        speedMult = 1.3,
        jumpDuration = 0.25
    },
    Mage = {
        runThreshold = 13,
        speedMult = 0.9,
        jumpDuration = 0.18
    },
    Tank = {
        runThreshold = 12,
        speedMult = 0.7,
        jumpDuration = 0.15
    }
}

local function applyClass(className)
    local class = classes[className]
    if not class then return end
    
    core.Connections.RunThreshold = class.runThreshold
    core.Connections.MoveAnimationSpeedMultiplier = class.speedMult
    core.Connections.JumpDuration = class.jumpDuration
    
    print("Applied class:", className)
end

-- Usage
local playerClass = player:GetAttribute("Class") or "Warrior"
applyClass(playerClass)

Best Practices

  1. Access through controller: Always access Core via controller.Core
  2. Configure once: Set Connections properties during initialization
  3. Monitor pose changes: Use PoseChanged event for state-dependent behavior
  4. Don't over-configure: Default settings work well for most cases
  5. Test thoroughly: Animation timing affects gameplay feel
  6. Consider performance: Frequent configuration changes can impact performance
  7. Use meaningful values: Base thresholds and multipliers on game design needs

Common Use Cases

  • Heavy character: Lower RunThreshold, slower SpeedMult
  • Fast character: Higher RunThreshold, faster SpeedMult
  • Injured state: Reduce SpeedMult, lower RunThreshold
  • Speed boost: Temporarily increase SpeedMult
  • Cutscenes: Disable core animations with SetCoreActive(false)
  • Swimming areas: Configure swim thresholds and speeds
  • Climbing challenges: Adjust climb animation speed

PoseController API

The PoseController manages pose transitions and core animation playback. It handles switching between different character states like idle, walking, running, jumping, etc.

Access: controller.Core.PoseController

Events

PoseChanged

Fires when the character's pose changes.

Signature:

PoseChanged: RBXScriptSignal<PoseType, PoseType, AnimationTrack>

Parameters:

  • oldPose: PoseType - The previous pose
  • newPose: PoseType - The new pose
  • track: AnimationTrack - The animation track for the new pose

Examples:

Basic usage:

controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    print(string.format("Changed from %s to %s", oldPose, newPose))
end)

Track specific transitions:

controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    if newPose == "Jumping" then
        print("Character jumped!")
        track:AdjustSpeed(1.5) -- Make jump animation faster
    elseif newPose == "Freefall" then
        print("Character is falling!")
    end
end)

Create visual effects on pose changes:

controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    if newPose == "Landed" then
        -- Create dust particle effect
        local dust = dustEffect:Clone()
        dust.Parent = character.HumanoidRootPart
        dust:Emit(20)
        
        task.delay(1, function()
            dust:Destroy()
        end)
    end
end)

Methods

:SetCoreActive()

Sets whether core animations are active.

Signature:

function SetCoreActive(value: boolean): ()

Parameters:

  • value: boolean - Enable/disable core animations

Examples:

Disable all core animations:

controller.Core.PoseController:SetCoreActive(false)
-- Character will stop animating automatically

Re-enable core animations:

controller.Core.PoseController:SetCoreActive(true)

Temporarily pause during cutscene:

-- Start cutscene
controller.Core.PoseController:SetCoreActive(false)

-- Play cutscene...
task.wait(5)

-- Resume normal animations
controller.Core.PoseController:SetCoreActive(true)

:GetCoreActive()

Gets whether core animations are currently active.

Signature:

function GetCoreActive(): boolean

Returns:

  • boolean - Whether core animations are active

Examples:

local isActive = controller.Core.PoseController:GetCoreActive()
print("Core animations active:", isActive)

Toggle core animations:

local currentState = controller.Core.PoseController:GetCoreActive()
controller.Core.PoseController:SetCoreActive(not currentState)

:SetCoreCanPlayAnims()

Controls whether core animations can play. Used internally during emotes.

Signature:

function SetCoreCanPlayAnims(value: boolean): ()

Parameters:

  • value: boolean - Whether core animations can play

Examples:

-- Prevent core animations temporarily
controller.Core.PoseController:SetCoreCanPlayAnims(false)

-- Allow core animations
controller.Core.PoseController:SetCoreCanPlayAnims(true)

:GetCoreCanPlayAnims()

Gets whether core animations can play.

Signature:

function GetCoreCanPlayAnims(): boolean

Returns:

  • boolean - Whether core animations can play

:GetCurrentTrack()

Gets the currently playing animation track.

Signature:

function GetCurrentTrack(): AnimationTrack

Returns:

  • AnimationTrack - The current animation track

Examples:

local track = controller.Core.PoseController:GetCurrentTrack()
print("Current animation:", track.Animation.AnimationId)
print("Current speed:", track.Speed)
print("Is playing:", track.IsPlaying)

Adjust current animation speed:

local track = controller.Core.PoseController:GetCurrentTrack()
track:AdjustSpeed(2) -- Play at 2x speed

:GetPose()

Gets the current pose type.

Signature:

function GetPose(): PoseType?

Returns:

  • PoseType? - The current pose (e.g., "Idle", "Walk", "Run", "Jumping")

Examples:

local currentPose = controller.Core.PoseController:GetPose()
print("Character is:", currentPose)

Conditional logic based on pose:

local pose = controller.Core.PoseController:GetPose()

if pose == "Idle" then
    print("Character is standing still")
elseif pose == "Run" or pose == "Walk" then
    print("Character is moving")
elseif pose == "Freefall" or pose == "Jumping" then
    print("Character is in the air")
end

:SetPoseEnabled()

Enables or disables a specific pose.

Signature:

function SetPoseEnabled(pose: PoseType, enabled: boolean): ()

Parameters:

  • pose: PoseType - The pose to enable/disable
  • enabled: boolean - Whether the pose should be enabled

Examples:

Disable jumping animation:

controller.Core.PoseController:SetPoseEnabled("Jumping", false)
-- Character can still jump but won't play jump animation

Disable multiple poses:

-- Create a "statue" mode
controller.Core.PoseController:SetPoseEnabled("Walk", false)
controller.Core.PoseController:SetPoseEnabled("Run", false)
controller.Core.PoseController:SetPoseEnabled("Jumping", false)

Re-enable poses:

controller.Core.PoseController:SetPoseEnabled("Jumping", true)

:GetCoreAnimInfos()

Gets the animation information array for a specific pose.

Signature:

function GetCoreAnimInfos(pose: PoseType): {AnimInfo}

Parameters:

  • pose: PoseType - The pose to get animations for

Returns:

  • {AnimInfo} - Array of animation info structures

Examples:

local idleAnims = controller.Core.PoseController:GetCoreAnimInfos("Idle")

for i, info in idleAnims do
    print(string.format("Idle anim %d: %s (weight: %d)", i, info.id, info.weight))
end

Check how many walk animations exist:

local walkAnims = controller.Core.PoseController:GetCoreAnimInfos("Walk")
print("Number of walk animations:", #walkAnims)

:ChangeCoreAnim()

Changes a core animation for a specific pose.

Signature:

function ChangeCoreAnim(
    pose: PoseType,
    index: number,
    new: Animation | string | AnimationTrack | AnimInfo
): AnimationTrack

Parameters:

  • pose: PoseType - The pose to change
  • index: number - The index of the animation to change (1-based)
  • new - The new animation (can be AnimInfo table, AnimationId string, Animation instance, or AnimationTrack)

Returns:

  • AnimationTrack - The new animation track

Examples:

Change using animation ID:

controller.Core.PoseController:ChangeCoreAnim(
    "Idle",
    1,
    "rbxassetid://123456789"
)

Change using Animation instance:

local anim = Instance.new("Animation")
anim.AnimationId = "rbxassetid://123456789"

controller.Core.PoseController:ChangeCoreAnim("Run", 1, anim)

Change using AnimInfo table:

local newAnimInfo = {
    id = "rbxassetid://123456789",
    weight = 15,
    speed = 1.2,
    fadeTime = 0.3
}

controller.Core.PoseController:ChangeCoreAnim("Walk", 1, newAnimInfo)

Change multiple animations:

-- Replace all idle animations
local idleAnims = {
    "rbxassetid://111111",
    "rbxassetid://222222",
    "rbxassetid://333333"
}

for i, animId in idleAnims do
    controller.Core.PoseController:ChangeCoreAnim("Idle", i, animId)
end

:GetRandomCoreAnim()

Gets a random animation track for the specified pose, weighted by animation weights.

Signature:

function GetRandomCoreAnim(pose: PoseType): AnimationTrack

Parameters:

  • pose: PoseType - The pose to get an animation for

Returns:

  • AnimationTrack - A randomly selected animation track

Examples:

local randomIdle = controller.Core.PoseController:GetRandomCoreAnim("Idle")
print("Selected idle animation:", randomIdle.Animation.AnimationId)

:StopCoreAnimations()

Stops all currently playing core animations.

Signature:

function StopCoreAnimations(fadeTime: number?): ()

Parameters:

  • fadeTime: number? - Optional fade time for stopping animations (default: instant)

Examples:

Stop immediately:

controller.Core.PoseController:StopCoreAnimations()

Stop with fade:

controller.Core.PoseController:StopCoreAnimations(0.5) -- Fade out over 0.5 seconds

Use before playing custom animation:

-- Stop all core animations before custom sequence
controller.Core.PoseController:StopCoreAnimations(0.2)

local customTrack = animator:LoadAnimation(customAnimation)
customTrack:Play()

:PlayCoreAnimation()

Plays an animation for the specified pose.

Signature:

function PlayCoreAnimation(
    pose: PoseType,
    looped: boolean?,
    speed: number?,
    fadeTime: number?
): AnimInfo

Parameters:

  • pose: PoseType - The pose to play
  • looped: boolean? - Whether to loop (default: true)
  • speed: number? - Playback speed (default: 1)
  • fadeTime: number? - Fade time for transition (default: 0.1)

Returns:

  • AnimInfo - The animation info that was played

Examples:

Play idle animation:

controller.Core.PoseController:PlayCoreAnimation("Idle")

Play with custom speed:

controller.Core.PoseController:PlayCoreAnimation("Run", true, 1.5)

Play non-looping:

controller.Core.PoseController:PlayCoreAnimation("Jumping", false, 1, 0.1)

:ChangePose()

Changes the character's pose.

Signature:

function ChangePose(pose: PoseType, speed: number?, isCore: boolean?): ()

Parameters:

  • pose: PoseType - The pose to change to
  • speed: number? - Optional playback speed (default: 1)
  • isCore: boolean? - Whether this is a core pose change (default: false)

Examples:

Force character to idle:

controller.Core.PoseController:ChangePose("Idle")

Change pose with custom speed:

controller.Core.PoseController:ChangePose("Walk", 1.5)

Note: Usually you don't need to call this manually as the Connections module handles pose changes automatically based on humanoid state.

Connections API

The Connections module handles humanoid event connections and automatic pose changes based on character state. It responds to movement, jumping, climbing, swimming, and other state changes.

Access: controller.Core.Connections

Properties

Configuration Properties

These properties control animation behavior and speed multipliers:

  • JumpDuration: number - Duration before falling animation plays (default: 0.2)
  • SwimIdleAnimationSpeed: number - Speed for swim idle animation (default: 1)
  • IdleAnimationSpeed: number - Speed for idle animation (default: 1)
  • RunThreshold: number - Speed threshold between walk and run (default: 15.5)
  • SwimIdleThreshold: number - Speed threshold for swim idle vs swimming (default: 2)
  • MoveAnimationSpeedMultiplier: number - Multiplier for walk/run animations (default: 1)
  • ClimbAnimationSpeedMultiplier: number - Multiplier for climb animations (default: 1)
  • SwimAnimationSpeedMultiplier: number - Multiplier for swim animations (default: 1)
  • WalkSpeed: number - Reference walk speed (default: humanoid's WalkSpeed)
  • AutoAdjustSpeedMultipliers: boolean - Automatically adjust multipliers based on WalkSpeed (default: true)
  • UseWalkSpeedForAnimSpeed: boolean - Use WalkSpeed for animation speed calculations (default: true)

Examples

Adjusting Run Threshold

Control when the character switches from walking to running:

-- Default is 15.5, lower value = switches to run earlier
controller.Core.Connections.RunThreshold = 10

-- Now character will run at speeds above 10

Custom Animation Speeds

-- Make idle animation slower for dramatic effect
controller.Core.Connections.IdleAnimationSpeed = 0.7

-- Make running animation faster
controller.Core.Connections.MoveAnimationSpeedMultiplier = 1.5

Adjusting Jump Timing

-- Make character fall faster (shorter jump animation)
controller.Core.Connections.JumpDuration = 0.1

-- Make character stay in jump animation longer
controller.Core.Connections.JumpDuration = 0.5

Manual Speed Multipliers

Disable auto-adjustment and set custom multipliers:

local connections = controller.Core.Connections

-- Disable automatic adjustment
connections.AutoAdjustSpeedMultipliers = false

-- Set custom multipliers
connections.MoveAnimationSpeedMultiplier = 2.0   -- Walk/run at 2x speed
connections.ClimbAnimationSpeedMultiplier = 1.5  -- Climb at 1.5x speed
connections.SwimAnimationSpeedMultiplier = 0.8   -- Swim at 0.8x speed

Swimming Configuration

local connections = controller.Core.Connections

-- Adjust swim idle threshold
connections.SwimIdleThreshold = 3 -- Idle when speed < 3

-- Adjust swim idle animation speed
connections.SwimIdleAnimationSpeed = 0.5 -- Slower treading water

-- Adjust swimming animation speed
connections.SwimAnimationSpeedMultiplier = 1.2

Speed-Based Animation System

Create a system where animation speed matches character speed:

local connections = controller.Core.Connections

-- Enable speed-based animation
connections.UseWalkSpeedForAnimSpeed = true
connections.AutoAdjustSpeedMultipliers = true

-- Update walk speed reference
local humanoid = character:FindFirstChildWhichIsA("Humanoid")

humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
    connections.WalkSpeed = humanoid.WalkSpeed
end)

Custom Speed Zones

Create areas with different animation behaviors:

local connections = controller.Core.Connections

local slowZone = workspace.SlowZone
local fastZone = workspace.FastZone

slowZone.Touched:Connect(function(hit)
    if hit.Parent == character then
        -- Slow down animations in this zone
        connections.MoveAnimationSpeedMultiplier = 0.5
        connections.ClimbAnimationSpeedMultiplier = 0.5
    end
end)

fastZone.Touched:Connect(function(hit)
    if hit.Parent == character then
        -- Speed up animations in this zone
        connections.MoveAnimationSpeedMultiplier = 2.0
        connections.ClimbAnimationSpeedMultiplier = 2.0
    end
end)

Dynamic Animation Speed

Adjust animation speed based on game state:

local connections = controller.Core.Connections

-- Normal mode
local function setNormalSpeed()
    connections.MoveAnimationSpeedMultiplier = 1.0
    connections.IdleAnimationSpeed = 1.0
end

-- Combat mode - faster, more alert animations
local function setCombatSpeed()
    connections.MoveAnimationSpeedMultiplier = 1.3
    connections.IdleAnimationSpeed = 1.2
end

-- Exhausted mode - slower animations
local function setExhaustedSpeed()
    connections.MoveAnimationSpeedMultiplier = 0.7
    connections.IdleAnimationSpeed = 0.8
end

-- Switch modes based on stamina
local stamina = 100

game:GetService("RunService").Heartbeat:Connect(function()
    if stamina > 50 then
        setNormalSpeed()
    elseif stamina > 20 then
        setCombatSpeed()
    else
        setExhaustedSpeed()
    end
end)

Disable Auto-Speed Adjustment

For more control, disable automatic speed calculations:

local connections = controller.Core.Connections

-- Disable auto-adjustment
connections.AutoAdjustSpeedMultipliers = false
connections.UseWalkSpeedForAnimSpeed = false

-- Now multipliers won't change automatically
-- Set them manually as needed
connections.MoveAnimationSpeedMultiplier = 1.5

Custom Thresholds for Different Characters

-- Heavy character - slower threshold
local heavyConnections = heavyController.Core.Connections
heavyConnections.RunThreshold = 12
heavyConnections.MoveAnimationSpeedMultiplier = 0.8

-- Light character - faster threshold  
local lightConnections = lightController.Core.Connections
lightConnections.RunThreshold = 18
lightConnections.MoveAnimationSpeedMultiplier = 1.2

Monitoring Animation State

local connections = controller.Core.Connections

-- Check current settings
print("Run Threshold:", connections.RunThreshold)
print("Move Speed Multiplier:", connections.MoveAnimationSpeedMultiplier)
print("Auto Adjust:", connections.AutoAdjustSpeedMultipliers)

-- Monitor changes
task.spawn(function()
    while true do
        task.wait(1)
        print("Current speed multiplier:", connections.MoveAnimationSpeedMultiplier)
    end
end)

Complete Movement System

A comprehensive example combining multiple settings:

local connections = controller.Core.Connections

-- Configure all movement settings
connections.RunThreshold = 14
connections.SwimIdleThreshold = 2.5
connections.JumpDuration = 0.25

-- Configure animation speeds
connections.IdleAnimationSpeed = 1.0
connections.SwimIdleAnimationSpeed = 0.8

-- Configure multipliers
connections.AutoAdjustSpeedMultipliers = true
connections.UseWalkSpeedForAnimSpeed = true

-- Set base walk speed
connections.WalkSpeed = character.Humanoid.WalkSpeed

-- Create speed boost power-up
local function applySpeedBoost(duration)
    local originalMultiplier = connections.MoveAnimationSpeedMultiplier
    
    connections.MoveAnimationSpeedMultiplier = 2.0
    connections.AutoAdjustSpeedMultipliers = false
    
    task.delay(duration, function()
        connections.MoveAnimationSpeedMultiplier = originalMultiplier
        connections.AutoAdjustSpeedMultipliers = true
    end)
end

-- Use it
applySpeedBoost(5) -- 5 second speed boost

Notes

  • The Connections module automatically updates itself based on humanoid events
  • Most users won't need to modify these properties unless they want custom animation behavior
  • Changes to these properties take effect immediately
  • AutoAdjustSpeedMultipliers recalculates multipliers every frame based on WalkSpeed
  • Setting AutoAdjustSpeedMultipliers to false gives you full manual control

Action API

The Action module manages emotes and custom action animations. It handles one-off animations that interrupt core animations temporarily.

Access: controller.Action

Methods

:CreateAction()

Creates a new action animation and adds it to the actions collection.

Signature:

function CreateAction(
    key: any,
    template: {AnimInfo},
    doPreload: boolean?,
    priority: Enum.AnimationPriority?,
    looped: boolean?
): {AnimInfo}

Parameters:

  • key: any - The key identifier for the action (can be string, number, etc.)
  • template: {AnimInfo} - Array of animation info structures
  • doPreload: boolean? - Whether to preload animations (default: true)
  • priority: Enum.AnimationPriority? - Animation priority (default: Enum.AnimationPriority.Action)
  • looped: boolean? - Whether animations loop (default: false)

Returns:

  • {AnimInfo} - The preloaded/created animation info array

Examples:

Create a simple action:

local waveAction = {
    {
        id = "rbxassetid://123456789",
        weight = 10,
        speed = 1,
        fadeTime = 0.2
    }
}

controller.Action:CreateAction("customWave", waveAction)

Create action with multiple variants:

local danceAction = {
    {
        id = "rbxassetid://111111111",
        weight = 10,
        speed = 1
    },
    {
        id = "rbxassetid://222222222",
        weight = 5, -- Less likely to be selected
        speed = 1.2
    },
    {
        id = "rbxassetid://333333333",
        weight = 15, -- Most likely to be selected
        speed = 0.9
    }
}

controller.Action:CreateAction("dance", danceAction)

Create with custom priority:

controller.Action:CreateAction(
    "importantAction",
    actionTemplate,
    true,
    Enum.AnimationPriority.Action4 -- Higher priority
)

Create looped action:

local sitAction = {
    {
        id = "rbxassetid://987654321",
        weight = 10
    }
}

controller.Action:CreateAction("sit", sitAction, true, nil, true) -- Looped

:BulkCreateAction()

Creates multiple actions at once from an animations list.

Signature:

function BulkCreateAction(
    animsList: AnimationsList,
    doPreload: boolean?,
    priority: Enum.AnimationPriority?
): AnimationsList

Parameters:

  • animsList: AnimationsList - Dictionary of action names to animation arrays
  • doPreload: boolean? - Whether to preload animations (default: true)
  • priority: Enum.AnimationPriority? - Animation priority (default: Enum.AnimationPriority.Action)

Returns:

  • AnimationsList - The created animations list

Examples:

Create multiple actions at once:

local customActions = {
    wave = {
        {id = "rbxassetid://111", weight = 10}
    },
    dance = {
        {id = "rbxassetid://222", weight = 10},
        {id = "rbxassetid://333", weight = 5}
    },
    point = {
        {id = "rbxassetid://444", weight = 10}
    }
}

controller.Action:BulkCreateAction(customActions)

-- Now you can play any of them
controller.Action:PlayAction("wave")
controller.Action:PlayAction("dance")
controller.Action:PlayAction("point")

:RemoveAction()

Removes and destroys an action by key.

Signature:

function RemoveAction(key: any): ()

Parameters:

  • key: any - The key of the action to remove

Examples:

-- Remove an action
controller.Action:RemoveAction("customWave")

-- Action is now destroyed and can't be played

Remove multiple actions:

local actionsToRemove = {"action1", "action2", "action3"}

for _, actionKey in actionsToRemove do
    controller.Action:RemoveAction(actionKey)
end

:StopAllActions()

Stops all currently playing animations for a specific action.

Signature:

function StopAllActions(key: any): ()

Parameters:

  • key: any - The key of the action to stop

Examples:

-- Stop all dance animations
controller.Action:StopAllActions("dance")

Stop action after delay:

controller.Action:PlayAction("dance")

task.delay(3, function()
    controller.Action:StopAllActions("dance")
end)

:GetAction()

Gets an action's animation info array by key.

Signature:

function GetAction(key: any): {AnimInfo}?

Parameters:

  • key: any - The key of the action to get

Returns:

  • {AnimInfo}? - The animation info array or nil if not found

Examples:

local waveInfo = controller.Action:GetAction("wave")

if waveInfo then
    print("Wave action has", #waveInfo, "animations")
    
    for i, info in waveInfo do
        print(string.format("Animation %d: %s (weight: %d)", i, info.id, info.weight))
    end
end

Check if action exists:

if controller.Action:GetAction("customAction") then
    print("Custom action exists!")
else
    print("Custom action not found")
end

:GetRandomActionAnim()

Gets a random animation track from an action's animations using weighted selection.

Signature:

function GetRandomActionAnim(key: any): AnimationTrack

Parameters:

  • key: any - The key of the action

Returns:

  • AnimationTrack - A randomly selected animation track

Examples:

local danceTrack = controller.Action:GetRandomActionAnim("dance")
print("Selected animation:", danceTrack.Animation.AnimationId)

-- Play it manually
danceTrack:Play()

:PlayRandomActionAnim()

Plays a random animation from an action and returns the track.

Signature:

function PlayRandomActionAnim(key: any): AnimationTrack

Parameters:

  • key: any - The key of the action to play

Returns:

  • AnimationTrack - The animation track that was played

Examples:

Basic usage:

local track = controller.Action:PlayRandomActionAnim("wave")

Wait for animation to finish:

local track = controller.Action:PlayRandomActionAnim("dance")

track.Stopped:Wait()
print("Dance finished!")

Play with custom adjustments:

local track = controller.Action:PlayRandomActionAnim("emote")
track:AdjustSpeed(1.5) -- Speed up
track:AdjustWeight(2)  -- Increase weight

Chain animations:

local function playEmoteSequence()
    local wave = controller.Action:PlayRandomActionAnim("wave")
    wave.Stopped:Wait()
    
    task.wait(0.5)
    
    local dance = controller.Action:PlayRandomActionAnim("dance")
    dance.Stopped:Wait()
    
    task.wait(0.5)
    
    local point = controller.Action:PlayRandomActionAnim("point")
end

playEmoteSequence()

:PlayAction()

Shorter alias for :PlayRandomActionAnim().

Signature:

function PlayAction(key: any): AnimationTrack

Parameters:

  • key: any - The key of the action to play

Returns:

  • AnimationTrack - The animation track that was played

Examples:

-- These are equivalent
local track1 = controller.Action:PlayAction("wave")
local track2 = controller.Action:PlayRandomActionAnim("wave")

Quick emote playing:

-- Play various emotes
controller.Action:PlayAction("wave")
task.wait(2)
controller.Action:PlayAction("dance")
task.wait(2)
controller.Action:PlayAction("point")

:SetEmoteBindable()

Sets up the emote bindable function for handling emote requests. This is used internally to connect with Roblox's emote system.

Signature:

function SetEmoteBindable(e: BindableFunction): ()

Parameters:

  • e: BindableFunction - The bindable function to set up

Examples:

local emoteBindable = Instance.new("BindableFunction")
controller.Action:SetEmoteBindable(emoteBindable)

-- Now the emote system is connected
-- Players can use the emote menu to trigger animations

Custom emote handler:

local emoteBindable = Instance.new("BindableFunction")
controller.Action:SetEmoteBindable(emoteBindable)

-- Monitor emote usage
local originalInvoke = emoteBindable.OnInvoke

function emoteBindable.OnInvoke(emoteName)
    print(player.Name, "played emote:", emoteName)
    return originalInvoke(emoteName)
end

Complete Examples

Custom Emote System

-- Define custom emotes
local customEmotes = {
    wave = {
        {id = "rbxassetid://111", weight = 10, fadeTime = 0.2}
    },
    dance1 = {
        {id = "rbxassetid://222", weight = 10, fadeTime = 0.3}
    },
    dance2 = {
        {id = "rbxassetid://333", weight = 10, fadeTime = 0.3}
    },
    sit = {
        {id = "rbxassetid://444", weight = 10, fadeTime = 0.5}
    }
}

-- Create all emotes
controller.Action:BulkCreateAction(customEmotes)

-- Create UI buttons to trigger emotes
local function createEmoteButton(emoteName)
    local button = Instance.new("TextButton")
    button.Text = emoteName
    button.MouseButton1Click:Connect(function()
        controller.Action:PlayAction(emoteName)
    end)
    return button
end

for emoteName in customEmotes do
    local button = createEmoteButton(emoteName)
    button.Parent = emoteGui
end

Timed Actions

-- Create a timed buff action
local buffAction = {
    {id = "rbxassetid://555", weight = 10}
}

controller.Action:CreateAction("buff", buffAction, true, nil, true) -- Looped

-- Play buff animation for 10 seconds
controller.Action:PlayAction("buff")

task.delay(10, function()
    controller.Action:StopAction("buff")
end)

Custom Animations Guide

This guide covers how to use custom animations with SimpleAnimate.

Animation Info Structure

All animations in SimpleAnimate are defined using the AnimInfo structure:

{
    id = "rbxassetid://123456789", -- Animation asset ID
    weight = 10,                    -- Selection weight (default: 10)
    speed = 1,                      -- Playback speed (default: 1)
    fadeTime = 0.1,                 -- Fade in time (default: 0.1)
    stopFadeTime = 0.1             -- Fade out time (optional)
}

Creating Custom Core Animations

Method 1: Modify Default Animations

Start with default animations and modify them:

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Get a copy of R15 animations
local coreAnims, emoteAnims = SimpleAnimate.getCopyOfAnims("R15")

-- Replace idle animation
coreAnims.Idle = {
    {
        id = "rbxassetid://YOUR_IDLE_ANIM",
        weight = 10,
        fadeTime = 0.2
    }
}

-- Replace walk animation
coreAnims.Walk = {
    {
        id = "rbxassetid://YOUR_WALK_ANIM",
        weight = 10
    }
}

-- Create controller with custom animations
local controller = SimpleAnimate.new(character, true, coreAnims, emoteAnims)

Method 2: Build From Scratch

Create a complete animation set from scratch:

local customCoreAnims = {
    Idle = {
        {id = "rbxassetid://123", weight = 10, fadeTime = 0.1}
    },
    Walk = {
        {id = "rbxassetid://124", weight = 10}
    },
    Run = {
        {id = "rbxassetid://125", weight = 10}
    },
    Jump = {
        {id = "rbxassetid://126", weight = 10}
    },
    Fall = {
        {id = "rbxassetid://127", weight = 10}
    },
    Climb = {
        {id = "rbxassetid://128", weight = 10}
    },
    Sit = {
        {id = "rbxassetid://129", weight = 10}
    }
}

local controller = SimpleAnimate.new(character, true, customCoreAnims)

Multiple Animation Variants

Add multiple variants of the same animation with different weights:

local coreAnims = SimpleAnimate.getCopyOfAnims("R15")

-- Multiple idle animations
coreAnims.Idle = {
    {
        id = "rbxassetid://IDLE_1",
        weight = 10,  -- Common idle
        fadeTime = 0.2
    },
    {
        id = "rbxassetid://IDLE_2",
        weight = 3,   -- Rare idle (stretching)
        fadeTime = 0.3
    },
    {
        id = "rbxassetid://IDLE_3",
        weight = 2,   -- Very rare idle (looking around)
        fadeTime = 0.3
    }
}

-- Multiple run animations
coreAnims.Run = {
    {
        id = "rbxassetid://RUN_NORMAL",
        weight = 10,
        speed = 1.0
    },
    {
        id = "rbxassetid://RUN_URGENT",
        weight = 5,
        speed = 1.2
    }
}

Animation Speed Control

Control playback speed for different effects:

local coreAnims = SimpleAnimate.getCopyOfAnims("R15")

-- Slow idle for tired character
coreAnims.Idle[1].speed = 0.7

-- Fast run for energetic character
coreAnims.Run[1].speed = 1.3

-- Slow climb for heavy character
coreAnims.Climb[1].speed = 0.8

Fade Time Customization

Control how animations blend together:

local coreAnims = SimpleAnimate.getCopyOfAnims("R15")

-- Quick transition for responsive movement
coreAnims.Walk[1].fadeTime = 0.05

-- Smooth transition for idle
coreAnims.Idle[1].fadeTime = 0.3
coreAnims.Idle[1].stopFadeTime = 0.3

-- Instant jump
coreAnims.Jump[1].fadeTime = 0

Runtime Animation Changes

Change animations while the game is running:

-- Change a single animation
controller.Core.PoseController:ChangeCoreAnim(
    "Idle",
    1,
    "rbxassetid://NEW_IDLE_ANIM"
)

-- The new animation will play immediately if character is idle

Change with AnimInfo for more control:

controller.Core.PoseController:ChangeCoreAnim(
    "Run",
    1,
    {
        id = "rbxassetid://NEW_RUN",
        weight = 10,
        speed = 1.2,
        fadeTime = 0.1
    }
)

Character-Specific Animations

Different animations for different character types:

local function getAnimationsForCharacter(characterType)
    if characterType == "Warrior" then
        return {
            Idle = {{id = "rbxassetid://WARRIOR_IDLE", weight = 10}},
            Walk = {{id = "rbxassetid://WARRIOR_WALK", weight = 10}},
            Run = {{id = "rbxassetid://WARRIOR_RUN", weight = 10}}
        }
    elseif characterType == "Mage" then
        return {
            Idle = {{id = "rbxassetid://MAGE_IDLE", weight = 10}},
            Walk = {{id = "rbxassetid://MAGE_WALK", weight = 10}},
            Run = {{id = "rbxassetid://MAGE_RUN", weight = 10}}
        }
    else
        return SimpleAnimate.getCopyOfAnims("R15")
    end
end

-- Usage
local characterType = player:GetAttribute("CharacterType")
local anims = getAnimationsForCharacter(characterType)
local controller = SimpleAnimate.new(character, true, anims)

Conditional Animation System

Change animations based on game state:

local controller = SimpleAnimate.new(character)

-- Normal animations
local normalAnims = {
    Idle = {{id = "rbxassetid://NORMAL_IDLE", weight = 10}},
    Walk = {{id = "rbxassetid://NORMAL_WALK", weight = 10}}
}

-- Combat animations
local combatAnims = {
    Idle = {{id = "rbxassetid://COMBAT_IDLE", weight = 10}},
    Walk = {{id = "rbxassetid://COMBAT_WALK", weight = 10}}
}

-- Switch to combat mode
local function enterCombat()
    for pose, anims in combatAnims do
        controller.Core.PoseController:ChangeCoreAnim(pose, 1, anims[1])
    end
end

-- Switch to normal mode
local function exitCombat()
    for pose, anims in normalAnims do
        controller.Core.PoseController:ChangeCoreAnim(pose, 1, anims[1])
    end
end

-- Use in game
player:GetAttributeChangedSignal("InCombat"):Connect(function()
    if player:GetAttribute("InCombat") then
        enterCombat()
    else
        exitCombat()
    end
end)

Animation Sets System

Create a reusable animation sets system:

local AnimationSets = {}

AnimationSets.Default = SimpleAnimate.getCopyOfAnims("R15")

AnimationSets.Zombie = {
    Idle = {{id = "rbxassetid://ZOMBIE_IDLE", weight = 10, speed = 0.7}},
    Walk = {{id = "rbxassetid://ZOMBIE_WALK", weight = 10, speed = 0.6}},
    Run = {{id = "rbxassetid://ZOMBIE_RUN", weight = 10, speed = 0.8}}
}

AnimationSets.Robot = {
    Idle = {{id = "rbxassetid://ROBOT_IDLE", weight = 10}},
    Walk = {{id = "rbxassetid://ROBOT_WALK", weight = 10, speed = 1.1}},
    Run = {{id = "rbxassetid://ROBOT_RUN", weight = 10, speed = 1.2}}
}

-- Apply animation set
local function applyAnimationSet(controller, setName)
    local animSet = AnimationSets[setName]
    
    for pose, anims in animSet do
        controller.Core.PoseController:ChangeCoreAnim(pose, 1, anims[1])
    end
end

-- Usage
local controller = SimpleAnimate.new(character)
applyAnimationSet(controller, "Zombie")

Mixing Player and Custom Animations

Use player's animations but override specific poses:

-- Get player's animations
local playerAnims = SimpleAnimate.getAnimPackageAsync(player)

-- Override specific animations with custom ones
playerAnims.Idle = {
    {id = "rbxassetid://CUSTOM_IDLE", weight = 10}
}

playerAnims.Run = {
    {id = "rbxassetid://CUSTOM_RUN", weight = 10}
}

-- Create controller with mixed animations
local controller = SimpleAnimate.new(character, true, playerAnims)

Animation Preloading

Control when animations are loaded:

-- Preload immediately (default)
local controller1 = SimpleAnimate.new(character, true, customAnims)

-- Don't preload (load on-demand)
local controller2 = SimpleAnimate.new(character, false, customAnims)

-- Manual preloading
local animator = character:FindFirstChildWhichIsA("Animator", true)
local preloadedAnims = SimpleAnimate.Preload.preloadAnimList(
    animator,
    customAnims,
    "core",
    Enum.AnimationPriority.Core,
    true
)

local controller3 = SimpleAnimate.new(character, false, preloadedAnims)

Best Practices

  1. Always provide fallbacks: Include all required poses even if using defaults
  2. Test weight distributions: Higher weights = more frequent selection
  3. Consider fade times: Smooth transitions improve animation quality
  4. Use appropriate speeds: Match animation speed to character movement
  5. Organize animation IDs: Keep animation IDs in a centralized configuration
  6. Test variants: Multiple animation variants add life to characters
  7. Profile performance: Too many variants can impact performance

Example: Complete Custom System

local AnimationConfig = {
    HeavyWarrior = {
        Idle = {
            {id = "rbxassetid://001", weight = 10, speed = 0.8, fadeTime = 0.3}
        },
        Walk = {
            {id = "rbxassetid://002", weight = 10, speed = 0.9}
        },
        Run = {
            {id = "rbxassetid://003", weight = 10, speed = 0.85}
        },
        Jump = {
            {id = "rbxassetid://004", weight = 10, speed = 0.7}
        }
    },
    LightRogue = {
        Idle = {
            {id = "rbxassetid://101", weight = 8, speed = 1.1},
            {id = "rbxassetid://102", weight = 2, speed = 1.0} -- Rare variant
        },
        Walk = {
            {id = "rbxassetid://103", weight = 10, speed = 1.2}
        },
        Run = {
            {id = "rbxassetid://104", weight = 10, speed = 1.3}
        },
        Jump = {
            {id = "rbxassetid://105", weight = 10, speed = 1.2}
        }
    }
}

-- Create character with appropriate animations
local function createCharacter(player, class)
    player.CharacterAdded:Connect(function(character)
        local anims = AnimationConfig[class]
        local controller = SimpleAnimate.new(character, true, anims)
        
        print(player.Name, "loaded as", class)
    end)
end

createCharacter(player, "HeavyWarrior")

Working with Emotes

State Machines Guide

This guide covers how to use custom state machines with SimpleAnimate instead of the default Humanoid.

What is a State Machine?

A state machine in SimpleAnimate is any object that provides the same events and properties as a Humanoid:

  • Running event
  • Jumping event
  • Climbing event
  • Swimming event
  • StateChanged event
  • WalkSpeed property

By default, SimpleAnimate uses the character's Humanoid, but you can provide a custom state machine for:

  • Custom character controllers
  • Non-humanoid characters
  • Special movement systems
  • Network-replicated animation states

Required Interface

Your custom state machine must implement:

type StateMachine = {
    Running: RBXScriptSignal<number>,      -- Fires with speed
    Jumping: RBXScriptSignal<()>,          -- Fires on jump
    Climbing: RBXScriptSignal<number>,     -- Fires with climb speed
    Swimming: RBXScriptSignal<number>,     -- Fires with swim speed
    StateChanged: RBXScriptSignal<Enum.HumanoidStateType, Enum.HumanoidStateType>,
    WalkSpeed: number
}

Basic Custom State Machine

Simple Implementation

local Signal = require(game.ReplicatedStorage.Signal) -- Use any signal library

local CustomStateMachine = {}
CustomStateMachine.__index = CustomStateMachine

function CustomStateMachine.new()
    local self = setmetatable({}, CustomStateMachine)
    
    -- Create signals
    self.Running = Signal.new()
    self.Jumping = Signal.new()
    self.Climbing = Signal.new()
    self.Swimming = Signal.new()
    self.StateChanged = Signal.new()
    
    -- Properties
    self.WalkSpeed = 16
    self._currentState = Enum.HumanoidStateType.Running
    
    return self
end

function CustomStateMachine:SetState(newState)
    local oldState = self._currentState
    self._currentState = newState
    self.StateChanged:Fire(oldState, newState)
end

function CustomStateMachine:FireRunning(speed)
    self.Running:Fire(speed)
end

function CustomStateMachine:FireJumping()
    self.Jumping:Fire()
end

return CustomStateMachine

Using with SimpleAnimate

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)
local CustomStateMachine = require(game.ReplicatedStorage.CustomStateMachine)

-- Create custom state machine
local stateMachine = CustomStateMachine.new()

-- Create controller with custom state machine
local controller = SimpleAnimate.new(
    character,
    true,
    nil,
    nil,
    stateMachine
)

-- Now control animations through your state machine
stateMachine:FireRunning(10) -- Triggers walk animation
stateMachine:FireRunning(20) -- Triggers run animation
stateMachine:FireJumping()   -- Triggers jump animation

Vehicle State Machine

Control character animations while in a vehicle:

local VehicleStateMachine = {}
VehicleStateMachine.__index = VehicleStateMachine

function VehicleStateMachine.new(vehicle)
    local self = setmetatable({}, VehicleStateMachine)
    
    self.Running = Signal.new()
    self.Jumping = Signal.new()
    self.Climbing = Signal.new()
    self.Swimming = Signal.new()
    self.StateChanged = Signal.new()
    
    self.WalkSpeed = 0
    self._vehicle = vehicle
    self._currentState = Enum.HumanoidStateType.Seated
    
    -- Monitor vehicle speed
    game:GetService("RunService").Heartbeat:Connect(function()
        local velocity = vehicle.AssemblyLinearVelocity
        local speed = velocity.Magnitude
        
        -- Map vehicle speed to animation speed
        if speed > 5 then
            self.Running:Fire(speed)
        else
            self.Running:Fire(0)
        end
    end)
    
    return self
end

return VehicleStateMachine

Usage:

-- When player enters vehicle
local vehicleStateMachine = VehicleStateMachine.new(vehicle)

-- Create controller with vehicle state machine
local controller = SimpleAnimate.new(
    character,
    true,
    drivingAnimations, -- Custom driving animations
    nil,
    vehicleStateMachine
)

-- Animations now respond to vehicle movement

Network-Replicated State Machine

Synchronize animation states across server and clients:

local ReplicatedStateMachine = {}
ReplicatedStateMachine.__index = ReplicatedStateMachine

function ReplicatedStateMachine.new(character)
    local self = setmetatable({}, ReplicatedStateMachine)
    
    self.Running = Signal.new()
    self.Jumping = Signal.new()
    self.Climbing = Signal.new()
    self.Swimming = Signal.new()
    self.StateChanged = Signal.new()
    
    self.WalkSpeed = 16
    
    -- Create remote events for replication
    local remoteFolder = Instance.new("Folder")
    remoteFolder.Name = "AnimationStates"
    remoteFolder.Parent = character
    
    local runningRemote = Instance.new("RemoteEvent")
    runningRemote.Name = "Running"
    runningRemote.Parent = remoteFolder
    
    local jumpingRemote = Instance.new("RemoteEvent")
    jumpingRemote.Name = "Jumping"
    jumpingRemote.Parent = remoteFolder
    
    -- Server: Fire to clients
    if game:GetService("RunService"):IsServer() then
        self.Running:Connect(function(speed)
            runningRemote:FireAllClients(speed)
        end)
        
        self.Jumping:Connect(function()
            jumpingRemote:FireAllClients()
        end)
    end
    
    -- Client: Listen from server
    if game:GetService("RunService"):IsClient() then
        runningRemote.OnClientEvent:Connect(function(speed)
            self.Running:Fire(speed)
        end)
        
        jumpingRemote.OnClientEvent:Connect(function()
            self.Jumping:Fire()
        end)
    end
    
    return self
end

return ReplicatedStateMachine

Custom Movement Controller

Integrate with a custom character controller:

local CustomController = {}
CustomController.__index = CustomController

function CustomController.new(character)
    local self = setmetatable({}, CustomController)
    
    self.Character = character
    self.StateMachine = {
        Running = Signal.new(),
        Jumping = Signal.new(),
        Climbing = Signal.new(),
        Swimming = Signal.new(),
        StateChanged = Signal.new(),
        WalkSpeed = 16
    }
    
    self._velocity = Vector3.zero
    self._isJumping = false
    
    return self
end

function CustomController:Update(dt)
    -- Custom movement logic
    local moveDirection = self:GetMoveDirection()
    self._velocity = moveDirection * self.StateMachine.WalkSpeed
    
    -- Update character position
    local hrp = self.Character.HumanoidRootPart
    hrp.CFrame += self._velocity * dt
    
    -- Fire running event with speed
    local speed = self._velocity.Magnitude
    self.StateMachine.Running:Fire(speed)
end

function CustomController:Jump()
    if not self._isJumping then
        self._isJumping = true
        self.StateMachine.Jumping:Fire()
        
        task.delay(0.5, function()
            self._isJumping = false
        end)
    end
end

function CustomController:GetMoveDirection()
    -- Your input handling here
    return Vector3.zero
end

return CustomController

Usage:

local CustomController = require(game.ReplicatedStorage.CustomController)
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Create custom controller
local controller = CustomController.new(character)

-- Create animation controller with custom state machine
local animController = SimpleAnimate.new(
    character,
    true,
    nil,
    nil,
    controller.StateMachine
)

-- Update loop
game:GetService("RunService").Heartbeat:Connect(function(dt)
    controller:Update(dt)
end)

Swimming State Machine

Custom swimming mechanics with state machine:

local SwimStateMachine = {}
SwimStateMachine.__index = SwimStateMachine

function SwimStateMachine.new(character)
    local self = setmetatable({}, SwimStateMachine)
    
    self.Running = Signal.new()
    self.Jumping = Signal.new()
    self.Climbing = Signal.new()
    self.Swimming = Signal.new()
    self.StateChanged = Signal.new()
    
    self.WalkSpeed = 16
    self._inWater = false
    self._swimSpeed = 0
    
    -- Detect water regions
    local hrp = character:WaitForChild("HumanoidRootPart")
    
    hrp.Touched:Connect(function(hit)
        if hit:IsA("Part") and hit.Name == "Water" then
            self._inWater = true
            self:SetState(Enum.HumanoidStateType.Swimming)
        end
    end)
    
    hrp.TouchEnded:Connect(function(hit)
        if hit:IsA("Part") and hit.Name == "Water" then
            self._inWater = false
            self:SetState(Enum.HumanoidStateType.Running)
        end
    end)
    
    return self
end

function SwimStateMachine:SetState(state)
    self.StateChanged:Fire(self._currentState or state, state)
    self._currentState = state
end

function SwimStateMachine:Update(velocity)
    if self._inWater then
        self._swimSpeed = velocity.Magnitude
        self.Swimming:Fire(self._swimSpeed)
    else
        self.Running:Fire(velocity.Magnitude)
    end
end

return SwimStateMachine

Flying State Machine

For characters that can fly:

local FlyStateMachine = {}
FlyStateMachine.__index = FlyStateMachine

function FlyStateMachine.new()
    local self = setmetatable({}, FlyStateMachine)
    
    self.Running = Signal.new()
    self.Jumping = Signal.new()
    self.Climbing = Signal.new()
    self.Swimming = Signal.new()
    self.StateChanged = Signal.new()
    
    self.WalkSpeed = 16
    self._isFlying = false
    self._flySpeed = 0
    
    return self
end

function FlyStateMachine:StartFlying()
    self._isFlying = true
    self.StateChanged:Fire(
        Enum.HumanoidStateType.Running,
        Enum.HumanoidStateType.Flying
    )
end

function FlyStateMachine:StopFlying()
    self._isFlying = false
    self.StateChanged:Fire(
        Enum.HumanoidStateType.Flying,
        Enum.HumanoidStateType.Freefall
    )
end

function FlyStateMachine:UpdateFlySpeed(speed)
    if self._isFlying then
        self._flySpeed = speed
        self.Swimming:Fire(speed) -- Reuse swimming for flying
    else
        self.Running:Fire(speed)
    end
end

return FlyStateMachine

AI State Machine

For NPC characters with AI:

local AIStateMachine = {}
AIStateMachine.__index = AIStateMachine

function AIStateMachine.new(npc)
    local self = setmetatable({}, AIStateMachine)
    
    self.Running = Signal.new()
    self.Jumping = Signal.new()
    self.Climbing = Signal.new()
    self.Swimming = Signal.new()
    self.StateChanged = Signal.new()
    
    self.WalkSpeed = 16
    self._npc = npc
    self._currentBehavior = "idle"
    
    return self
end

function AIStateMachine:SetBehavior(behavior)
    self._currentBehavior = behavior
    
    if behavior == "patrol" then
        self:StartPatrol()
    elseif behavior == "chase" then
        self:StartChase()
    elseif behavior == "idle" then
        self:StopMovement()
    end
end

function AIStateMachine:StartPatrol()
    self.Running:Fire(8) -- Walk speed
end

function AIStateMachine:StartChase()
    self.Running:Fire(20) -- Run speed
end

function AIStateMachine:StopMovement()
    self.Running:Fire(0)
end

function AIStateMachine:DoAction(action)
    if action == "jump" then
        self.Jumping:Fire()
    end
end

return AIStateMachine

Usage with AI:

local aiStateMachine = AIStateMachine.new(npc)
local controller = SimpleAnimate.new(npc, true, nil, nil, aiStateMachine)

-- AI behavior
while true do
    task.wait(5)
    
    local player = findNearestPlayer(npc)
    
    if player and (player.Character.HumanoidRootPart.Position - npc.HumanoidRootPart.Position).Magnitude < 50 then
        aiStateMachine:SetBehavior("chase")
    else
        aiStateMachine:SetBehavior("patrol")
    end
end

State Machine Debugging

Helper to debug state machine events:

local function debugStateMachine(stateMachine, name)
    print("=== Debugging State Machine:", name, "===")
    
    stateMachine.Running:Connect(function(speed)
        print("[Running]", speed)
    end)
    
    stateMachine.Jumping:Connect(function()
        print("[Jumping]")
    end)
    
    stateMachine.Climbing:Connect(function(speed)
        print("[Climbing]", speed)
    end)
    
    stateMachine.Swimming:Connect(function(speed)
        print("[Swimming]", speed)
    end)
    
    stateMachine.StateChanged:Connect(function(old, new)
        print("[StateChanged]", old.Name, "->", new.Name)
    end)
end

-- Usage
debugStateMachine(customStateMachine, "CustomController")

Best Practices

  1. Implement all required events: Even if unused, provide them for compatibility
  2. Fire events consistently: Match Humanoid behavior for predictable results
  3. Handle edge cases: Test state transitions thoroughly
  4. Provide sensible defaults: Set reasonable default values
  5. Document your interface: Clearly document what your state machine does
  6. Test with SimpleAnimate: Ensure animations trigger as expected
  7. Consider replication: Decide if states need to sync across network

Common Pitfalls

Missing Events

-- ❌ BAD: Missing events
local stateMachine = {
    Running = Signal.new(),
    WalkSpeed = 16
}
-- Will error when SimpleAnimate tries to connect to missing events

-- ✅ GOOD: All events present
local stateMachine = {
    Running = Signal.new(),
    Jumping = Signal.new(),
    Climbing = Signal.new(),
    Swimming = Signal.new(),
    StateChanged = Signal.new(),
    WalkSpeed = 16
}

Wrong Signal Signatures

-- ❌ BAD: Wrong signature
self.Running:Fire() -- Missing speed parameter

-- ✅ GOOD: Correct signature
self.Running:Fire(16) -- Includes speed

Not Updating WalkSpeed

-- ❌ BAD: Static WalkSpeed
self.WalkSpeed = 16 -- Never changes

-- ✅ GOOD: Dynamic WalkSpeed
function StateMachine:SetSpeed(speed)
    self.WalkSpeed = speed
    self.Running:Fire(speed)
end

Complete Example

Full custom state machine implementation:

local Signal = require(game.ReplicatedStorage.Signal)

local CustomStateMachine = {}
CustomStateMachine.__index = CustomStateMachine

function CustomStateMachine.new(character)
    local self = setmetatable({}, CustomStateMachine)
    
    -- Required signals
    self.Running = Signal.new()
    self.Jumping = Signal.new()
    self.Climbing = Signal.new()
    self.Swimming = Signal.new()
    self.StateChanged = Signal.new()
    
    -- Required properties
    self.WalkSpeed = 16
    
    -- Internal state
    self._character = character
    self._currentState = Enum.HumanoidStateType.Running
    self._velocity = Vector3.zero
    
    return self
end

function CustomStateMachine:Update(dt)
    -- Update movement and fire events
    local speed = self._velocity.Magnitude
    self.Running:Fire(speed)
end

function CustomStateMachine:Jump()
    self.Jumping:Fire()
    self:SetState(Enum.HumanoidStateType.Jumping)
end

function CustomStateMachine:SetState(newState)
    local oldState = self._currentState
    self._currentState = newState
    self.StateChanged:Fire(oldState, newState)
end

function CustomStateMachine:SetVelocity(velocity)
    self._velocity = velocity
end

return CustomStateMachine

Animation Packages Guide

This guide covers how to work with Roblox animation packages and player-equipped animations in SimpleAnimate.

What are Animation Packages?

Animation packages are collections of animations that players can equip from the Roblox catalog. They include:

  • Movement animations (idle, walk, run, jump)
  • Emote animations (wave, dance, laugh, etc.)
  • Custom character animations based on player preferences

SimpleAnimate can automatically load these player-equipped animations.

Loading Player Animations

Server-Side Loading

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        -- Wait for animator
        local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
        
        -- Load player's animation package
        local coreAnims = SimpleAnimate.getAnimPackageAsync(player)
        local emoteAnims = SimpleAnimate.getEmotePackageAsync(player)
        
        -- Create controller with player's animations
        local controller = SimpleAnimate.new(
            character,
            true,
            coreAnims,
            emoteAnims
        )
        
        print(player.Name, "loaded with custom animations")
    end)
end)

Client-Side Loading

-- LocalScript in StarterPlayer.StarterCharacterScripts
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)
local character = script.Parent

-- Get local player's animations (no player parameter needed on client)
local coreAnims = SimpleAnimate.getAnimPackageAsync()
local emoteAnims = SimpleAnimate.getEmotePackageAsync()

-- Create controller
local controller = SimpleAnimate.new(
    character,
    true,
    coreAnims,
    emoteAnims
)

Fallback Animations

When loading animation packages, provide fallbacks for missing animations:

Using Default Fallbacks

-- If player doesn't have custom animations, R15 defaults are used
local coreAnims = SimpleAnimate.getAnimPackageAsync(player)
-- Missing animations automatically filled with R15 defaults

Custom Fallbacks

-- Define your custom fallback animations
local customDefaults = {
    Idle = {{id = "rbxassetid://YOUR_IDLE", weight = 10}},
    Walk = {{id = "rbxassetid://YOUR_WALK", weight = 10}},
    Run = {{id = "rbxassetid://YOUR_RUN", weight = 10}}
}

-- Use custom fallbacks
local coreAnims = SimpleAnimate.getAnimPackageAsync(player, customDefaults)

-- Now if player doesn't have custom animations, your defaults are used

Selective Fallbacks

-- Get player's animations
local coreAnims = SimpleAnimate.getAnimPackageAsync(player)

-- Override specific animations with your own
coreAnims.Idle = {{id = "rbxassetid://YOUR_SPECIAL_IDLE", weight = 10}}

-- Keep player's other animations, but force your idle
local controller = SimpleAnimate.new(character, true, coreAnims)

Mixing Player and Custom Animations

Player Animations with Custom Emotes

-- Use player's movement animations
local coreAnims = SimpleAnimate.getAnimPackageAsync(player)

-- But use your custom emotes
local customEmotes = {
    wave = {{id = "rbxassetid://CUSTOM_WAVE", weight = 10}},
    dance = {{id = "rbxassetid://CUSTOM_DANCE", weight = 10}},
    cheer = {{id = "rbxassetid://CUSTOM_CHEER", weight = 10}}
}

local controller = SimpleAnimate.new(
    character,
    true,
    coreAnims,
    customEmotes
)

Custom Animations with Player Emotes

-- Use your custom movement animations
local customCore = {
    Idle = {{id = "rbxassetid://YOUR_IDLE", weight = 10}},
    Walk = {{id = "rbxassetid://YOUR_WALK", weight = 10}},
    Run = {{id = "rbxassetid://YOUR_RUN", weight = 10}}
}

-- But keep player's emotes
local emoteAnims = SimpleAnimate.getEmotePackageAsync(player)

local controller = SimpleAnimate.new(
    character,
    true,
    customCore,
    emoteAnims
)

Handling Different Rig Types

R6 vs R15 Detection

local function getRigType(character)
    local humanoid = character:FindFirstChildWhichIsA("Humanoid")
    if humanoid then
        return humanoid.RigType == Enum.HumanoidRigType.R6 and "R6" or "R15"
    end
    return "R15" -- Default
end

local function setupCharacter(player, character)
    local rigType = getRigType(character)
    
    -- Get appropriate defaults
    local defaultCore, defaultEmotes = SimpleAnimate.getCopyOfAnims(rigType)
    
    -- Load player's animations with appropriate fallbacks
    local coreAnims = SimpleAnimate.getAnimPackageAsync(player, defaultCore)
    local emoteAnims = SimpleAnimate.getEmotePackageAsync(player, defaultEmotes)
    
    local controller = SimpleAnimate.new(character, true, coreAnims, emoteAnims)
end

Force Specific Rig Type

-- Always use R15 animations regardless of player's equipped package
local r15Core, r15Emotes = SimpleAnimate.getCopyOfAnims("R15")

local controller = SimpleAnimate.new(
    character,
    true,
    r15Core,
    r15Emotes
)

Caching Animation Packages

Cache player animation packages to avoid repeated API calls:

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Cache player animations
local playerAnimCache = {}

local function getPlayerAnimations(player)
    -- Check cache first
    if playerAnimCache[player.UserId] then
        return playerAnimCache[player.UserId]
    end
    
    -- Load from API
    local coreAnims = SimpleAnimate.getAnimPackageAsync(player)
    local emoteAnims = SimpleAnimate.getEmotePackageAsync(player)
    
    -- Cache for future use
    playerAnimCache[player.UserId] = {
        core = coreAnims,
        emotes = emoteAnims
    }
    
    return playerAnimCache[player.UserId]
end

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local anims = getPlayerAnimations(player)
        
        local controller = SimpleAnimate.new(
            character,
            true,
            anims.core,
            anims.emotes
        )
    end)
end)

-- Clear cache when player leaves
Players.PlayerRemoving:Connect(function(player)
    playerAnimCache[player.UserId] = nil
end)

Validating Animation Packages

Check if animations loaded successfully:

local function validateAnimationPackage(anims, packageName)
    local requiredPoses = {"Idle", "Walk", "Run", "Jump"}
    
    for _, pose in requiredPoses do
        if not anims[pose] or #anims[pose] == 0 then
            warn(packageName, "missing", pose, "animation")
            return false
        end
    end
    
    return true
end

-- Usage
local coreAnims = SimpleAnimate.getAnimPackageAsync(player)

if validateAnimationPackage(coreAnims, "Core Animations") then
    print("Animation package valid")
else
    -- Use defaults instead
    coreAnims = SimpleAnimate.getCopyOfAnimsList("R15", "Animations")
end

Overriding Specific Animations

Keep Player Package, Override One Pose

local coreAnims = SimpleAnimate.getAnimPackageAsync(player)

-- Player has custom animations, but we want a special idle
coreAnims.Idle = {
    {
        id = "rbxassetid://SPECIAL_IDLE",
        weight = 10,
        speed = 0.8,
        fadeTime = 0.3
    }
}

local controller = SimpleAnimate.new(character, true, coreAnims)

Blend Custom with Player Animations

local coreAnims = SimpleAnimate.getAnimPackageAsync(player)

-- Add variants to player's animations
table.insert(coreAnims.Idle, {
    id = "rbxassetid://EXTRA_IDLE_VARIANT",
    weight = 3, -- Lower weight = less common
    fadeTime = 0.3
})

-- Now player has their animations PLUS your variant
local controller = SimpleAnimate.new(character, true, coreAnims)

Animation Package Metadata

Store and retrieve animation package information:

local AnimationPackages = {
    Stylish = {
        name = "Stylish",
        description = "Cool and modern animations",
        animations = {
            Idle = {{id = "rbxassetid://001", weight = 10}},
            Walk = {{id = "rbxassetid://002", weight = 10}},
            Run = {{id = "rbxassetid://003", weight = 10}}
        }
    },
    Classic = {
        name = "Classic",
        description = "Traditional Roblox animations",
        animations = SimpleAnimate.getCopyOfAnims("R15")
    }
}

local function applyPackage(player, packageName)
    local package = AnimationPackages[packageName]
    
    if not package then
        warn("Package not found:", packageName)
        return
    end
    
    player.CharacterAdded:Connect(function(character)
        local controller = SimpleAnimate.new(
            character,
            true,
            package.animations
        )
        
        print("Applied", package.name, "to", player.Name)
    end)
end

-- Usage
applyPackage(player, "Stylish")

Dynamic Package Switching

Allow players to switch animation packages at runtime:

local currentControllers = {}

local function switchAnimationPackage(player, packageName)
    local character = player.Character
    if not character then return end
    
    -- Destroy old controller
    if currentControllers[player] then
        currentControllers[player]:Destroy()
    end
    
    -- Get new package
    local package = AnimationPackages[packageName]
    
    -- Create new controller
    local controller = SimpleAnimate.new(
        character,
        true,
        package.animations
    )
    
    currentControllers[player] = controller
    
    print(player.Name, "switched to", packageName)
end

-- Remote event for switching
local switchPackageRemote = game.ReplicatedStorage.SwitchAnimPackage

switchPackageRemote.OnServerEvent:Connect(function(player, packageName)
    switchAnimationPackage(player, packageName)
end)

Error Handling

Robust error handling for animation package loading:

local function safeLoadAnimations(player)
    local success, coreAnims = pcall(function()
        return SimpleAnimate.getAnimPackageAsync(player)
    end)
    
    if not success then
        warn("Failed to load player animations:", coreAnims)
        -- Use defaults
        coreAnims = SimpleAnimate.getCopyOfAnimsList("R15", "Animations")
    end
    
    local success2, emoteAnims = pcall(function()
        return SimpleAnimate.getEmotePackageAsync(player)
    end)
    
    if not success2 then
        warn("Failed to load player emotes:", emoteAnims)
        emoteAnims = SimpleAnimate.getCopyOfAnimsList("R15", "Emotes")
    end
    
    return coreAnims, emoteAnims
end

-- Usage
Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local coreAnims, emoteAnims = safeLoadAnimations(player)
        
        local controller = SimpleAnimate.new(
            character,
            true,
            coreAnims,
            emoteAnims
        )
    end)
end)

Package Selection UI

Create a UI for players to choose animation packages:

-- Client Script
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

-- Create UI
local screenGui = Instance.new("ScreenGui")
screenGui.Parent = playerGui

local frame = Instance.new("Frame")
frame.Size = UDim2.new(0, 300, 0, 400)
frame.Position = UDim2.new(0.5, -150, 0.5, -200)
frame.Parent = screenGui

-- List of packages
local packages = {"Default", "Stylish", "Athletic", "Ninja", "Robot"}

for i, packageName in packages do
    local button = Instance.new("TextButton")
    button.Size = UDim2.new(1, -20, 0, 50)
    button.Position = UDim2.new(0, 10, 0, (i - 1) * 60 + 10)
    button.Text = packageName
    button.Parent = frame
    
    button.MouseButton1Click:Connect(function()
        -- Request package change
        ReplicatedStorage.SwitchAnimPackage:FireServer(packageName)
    end)
end

Pre-Loading Animation Packages

Pre-load animation packages before character spawns:

local loadedPackages = {}

-- Pre-load all packages on server start
local function preLoadPackages()
    for name, package in AnimationPackages do
        print("Pre-loading package:", name)
        loadedPackages[name] = package.animations
    end
end

preLoadPackages()

-- Instant assignment when player spawns
Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local packageName = player:GetAttribute("AnimPackage") or "Default"
        local anims = loadedPackages[packageName]
        
        local controller = SimpleAnimate.new(character, true, anims)
    end)
end)

Best Practices

  1. Always provide fallbacks: Handle cases where player animations fail to load
  2. Cache when possible: Avoid repeated API calls for the same player
  3. Validate packages: Check that required animations exist
  4. Handle errors gracefully: Use pcall for API calls
  5. Test with different rigs: Ensure compatibility with R6 and R15
  6. Consider performance: Pre-load packages when possible
  7. Respect player choice: Let players use their equipped animations when appropriate
  8. Provide options: Give players ability to override with custom packages

Common Issues

Animations Not Loading

-- ❌ Problem: Server call without player parameter
local anims = SimpleAnimate.getAnimPackageAsync() -- Error on server!

-- ✅ Solution: Always pass player on server
local anims = SimpleAnimate.getAnimPackageAsync(player)

Missing Animations

-- ❌ Problem: Not providing fallbacks
local anims = SimpleAnimate.getAnimPackageAsync(player)
-- Some poses might be missing

-- ✅ Solution: Provide fallbacks
local defaults = SimpleAnimate.getCopyOfAnims("R15")
local anims = SimpleAnimate.getAnimPackageAsync(player, defaults)

Repeated Loading

-- ❌ Problem: Loading on every character spawn
player.CharacterAdded:Connect(function(character)
    local anims = SimpleAnimate.getAnimPackageAsync(player) -- Loads every time
end)

-- ✅ Solution: Cache the results
local playerAnims = SimpleAnimate.getAnimPackageAsync(player)

player.CharacterAdded:Connect(function(character)
    local controller = SimpleAnimate.new(character, true, playerAnims)
end)

Basic Character Setup

Simple Setup (Server)

The most basic setup for handling all players:

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        -- Wait for animator to load
        local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
        
        -- Create controller
        local controller = SimpleAnimate.new(character)
        
        print(player.Name, "animation controller ready!")
    end)
end)

With Player Animation Packages

Load each player's equipped animations:

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
        
        -- Get player's animation package
        local coreAnims = SimpleAnimate.getAnimPackageAsync(player)
        local emoteAnims = SimpleAnimate.getEmotePackageAsync(player)
        
        -- Create controller with player's animations
        local controller = SimpleAnimate.new(
            character,
            true,
            coreAnims,
            emoteAnims
        )
    end)
end)

Client-Side Setup

Setup on the client for the local player:

-- LocalScript in StarterPlayer.StarterCharacterScripts
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)
local character = script.Parent

-- Create controller
local controller = SimpleAnimate.new(character)

-- Listen for pose changes
controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose)
    print("Pose changed:", oldPose, "->", newPose)
end)

With Custom Default Animations

Replace default animations with your own:

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Define custom animations
local customAnims = {
    Idle = {
        {id = "rbxassetid://123456789", weight = 10, fadeTime = 0.1}
    },
    Walk = {
        {id = "rbxassetid://111111111", weight = 10}
    },
    Run = {
        {id = "rbxassetid://222222222", weight = 10}
    },
    Jump = {
        {id = "rbxassetid://333333333", weight = 10}
    },
    -- Add more poses...
}

local customEmotes = {
    wave = {
        {id = "rbxassetid://444444444", weight = 10}
    },
    dance = {
        {id = "rbxassetid://555555555", weight = 10}
    }
}

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
        
        local controller = SimpleAnimate.new(
            character,
            true,
            customAnims,
            customEmotes
        )
    end)
end)

With Emote Menu Integration

Set up the emote menu to work with SimpleAnimate:

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
        local humanoid = character:WaitForChild("Humanoid")
        
        local controller = SimpleAnimate.new(character)
        
        -- Hook up emote bindable
        local emoteBindable = Instance.new("BindableFunction")
        emoteBindable.Parent = character
        
        controller.Action:SetEmoteBindable(emoteBindable)
        
        -- Set the humanoid's emote bindable
        humanoid:SetEmoteBindableFunction(emoteBindable)
    end)
end)

Character Respawn Handling

Properly handle character respawns:

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

local playerControllers = {}

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        -- Clean up old controller if it exists
        if playerControllers[player] then
            playerControllers[player]:Destroy()
        end
        
        local animator = character:WaitForChild("Humanoid"):WaitForChild("Animator")
        
        -- Create new controller
        local controller = SimpleAnimate.new(character)
        playerControllers[player] = controller
    end)
    
    player.CharacterRemoving:Connect(function()
        if playerControllers[player] then
            playerControllers[player]:Destroy()
            playerControllers[player] = nil
        end
    end)
end)

Players.PlayerRemoving:Connect(function(player)
    playerControllers[player] = nil
end)

With Animation Preloading Disabled

Disable preloading if you want animations to load on-demand:

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

-- Preloading disabled (3rd parameter = false)
local controller = SimpleAnimate.new(
    character,
    false -- Don't preload
)

-- Animations will load when first played

Multiple Characters (NPCs)

Set up animation controllers for NPC characters:

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

local function setupNPC(npc)
    local animator = npc:WaitForChild("Humanoid"):WaitForChild("Animator")
    
    -- Create controller for NPC
    local controller = SimpleAnimate.new(npc)
    
    -- Store reference if needed
    npc:SetAttribute("HasAnimController", true)
    
    return controller
end

-- Setup all NPCs in workspace
for _, npc in workspace.NPCs:GetChildren() do
    if npc:IsA("Model") and npc:FindFirstChild("Humanoid") then
        setupNPC(npc)
    end
end

-- Setup new NPCs as they're added
workspace.NPCs.ChildAdded:Connect(function(npc)
    if npc:IsA("Model") then
        npc:WaitForChild("Humanoid")
        setupNPC(npc)
    end
end)

With Pose Change Monitoring

Monitor and log all pose changes:

local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

local character = player.Character or player.CharacterAdded:Wait()
local controller = SimpleAnimate.new(character)

-- Track pose changes
local poseHistory = {}

controller.Core.PoseController.PoseChanged:Connect(function(oldPose, newPose, track)
    table.insert(poseHistory, {
        from = oldPose,
        to = newPose,
        time = tick(),
        animationId = track.Animation.AnimationId
    })
    
    print(string.format("[%.2f] %s -> %s", tick(), oldPose, newPose))
end)

-- Function to get pose statistics
local function getPoseStats()
    local stats = {}
    
    for _, entry in poseHistory do
        stats[entry.to] = (stats[entry.to] or 0) + 1
    end
    
    return stats
end

Error Handling

Robust error handling for animation setup:

local Players = game:GetService("Players")
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function(character)
        local success, controller = pcall(function()
            local animator = character:WaitForChild("Humanoid", 5):WaitForChild("Animator", 5)
            return SimpleAnimate.new(character)
        end)
        
        if success then
            print(player.Name, "animation controller created successfully")
        else
            warn(player.Name, "failed to create animation controller:", controller)
        end
    end)
end)

Waiting for Existing Controllers

Use awaitController in other scripts:

-- Script A: Creates controller
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)
local controller = SimpleAnimate.new(character)

-- Script B: Waits for controller
local SimpleAnimate = require(game.ReplicatedStorage.SimpleAnimate)

local controller = SimpleAnimate.awaitController(character, 10)

if controller then
    print("Got controller!")
    controller.Action:PlayAction("wave")
else
    warn("Controller not found within timeout")
end