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 ornilif 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 fortimeOut- Timeout in seconds (default:5)
Returns:
AnimationController?- The controller ornilon 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 animationsAnimationsList- 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
- Learn about Custom Animations
- Explore the API Reference
- Check out more Examples
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 ornilif 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 fortimeOut- Timeout in seconds (default:5)
Returns:
AnimationController?- The controller ornilon 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 animationsAnimationsList- 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 playbackConnections- 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 actionPlayAction()- Play an actionRemoveAction()- 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
- Let it auto-cleanup: The controller automatically destroys itself when the character is destroyed
- One controller per character: Don't create multiple controllers for the same character
- Use fromExisting(): Check for existing controllers before creating new ones
- Store references: Keep references to controllers you need to access later
- Handle errors: Use pcall when creating controllers for robustness
- Clean up properly: If you manually destroy, ensure you also clean up your references
- 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
})
Related APIs
- Core API - Core animation system
- PoseController API - Pose management
- Connections API - Event handling configuration
- Action API - Emote and action system
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
- Access through controller: Always access Core via
controller.Core - Configure once: Set Connections properties during initialization
- Monitor pose changes: Use PoseChanged event for state-dependent behavior
- Don't over-configure: Default settings work well for most cases
- Test thoroughly: Animation timing affects gameplay feel
- Consider performance: Frequent configuration changes can impact performance
- 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
Related APIs
- PoseController API - Detailed pose control
- Connections API - Configuration options
- AnimationController API - Main controller
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 posenewPose: PoseType- The new posetrack: 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/disableenabled: 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 changeindex: 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 playlooped: 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 tospeed: 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
AutoAdjustSpeedMultipliersrecalculates multipliers every frame based on WalkSpeed- Setting
AutoAdjustSpeedMultiplierstofalsegives 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 structuresdoPreload: 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 arraysdoPreload: 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 ornilif 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
- Always provide fallbacks: Include all required poses even if using defaults
- Test weight distributions: Higher weights = more frequent selection
- Consider fade times: Smooth transitions improve animation quality
- Use appropriate speeds: Match animation speed to character movement
- Organize animation IDs: Keep animation IDs in a centralized configuration
- Test variants: Multiple animation variants add life to characters
- 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:
RunningeventJumpingeventClimbingeventSwimmingeventStateChangedeventWalkSpeedproperty
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
- Implement all required events: Even if unused, provide them for compatibility
- Fire events consistently: Match Humanoid behavior for predictable results
- Handle edge cases: Test state transitions thoroughly
- Provide sensible defaults: Set reasonable default values
- Document your interface: Clearly document what your state machine does
- Test with SimpleAnimate: Ensure animations trigger as expected
- 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
- Always provide fallbacks: Handle cases where player animations fail to load
- Cache when possible: Avoid repeated API calls for the same player
- Validate packages: Check that required animations exist
- Handle errors gracefully: Use pcall for API calls
- Test with different rigs: Ensure compatibility with R6 and R15
- Consider performance: Pre-load packages when possible
- Respect player choice: Let players use their equipped animations when appropriate
- 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