How to get pathfinding AI to play custom walking animation with script?

Hello,

So I watched a YouTube tutorial by GnomeCode that covered how to script pathfinding onto a horror AI. I successfully got that part to work. He later showed how to animate it but didn’t really leave clear enough explanation for me to fully understand. I’ve looked at multiple threads that has to do with this issue and the GnomeCode tutorial but they weren’t helpful enough for my situation.

Video of pathfinding working:

The script to it:

local Igor = script.Parent
local humanoid = Igor.Humanoid
Igor.PrimaryPart:SetNetworkOwner(nil)

local function canSeeTarget(target)
	local origin = Igor.HumanoidRootPart.Position
	local direction = (target.HumanoidRootPart.Position - Igor.HumanoidRootPart.Position).unit * 40
	local ray = Ray.new(origin, direction)
	
	local hit, pos = workspace:FindPartOnRay(ray, Igor)
	
	
	if hit then
		if hit:IsDescendantOf(target) then
			return true
		end
	else
		return false
	end
end

local function findTarget()
	local players = game.Players:GetPlayers()
	local maxDistance = 40
	local nearestTarget
	
	for index, player in pairs(players) do
		if player.Character then
			local target = player.Character
			local distance = (Igor.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude
			
			if distance < maxDistance and canSeeTarget(target) then
				nearestTarget = target
				maxDistance = distance
			end
		end
	end
	
	return nearestTarget
end

local function getPath(destination)
	local PathfindingService = game:GetService("PathfindingService")
	
	local pathParams = {
		["AgentHeight"] = 8,
		["AgentRadius"] = 3,
		["AgentCanJump"] = false
	}
	
	local path = PathfindingService:CreatePath(pathParams)
	
	path:ComputeAsync(Igor.HumanoidRootPart.Position, destination.Position)
	
	return path
end

local function attack(target)
	local distance = (Igor.HumanoidRootPart.Position - target.HumanoidRootPart.Position).Magnitude

	if distance > 2 then
		humanoid:MoveTo(target.HumanoidRootPart.Position)
	else
		local attackAnim = humanoid:LoadAnimation(script.Attack)
		attackAnim:Play()
		target.Humanoid.Health = 0
	end
end

local function walkTo(destination)
	
	local path = getPath(destination)
	
	if path.Status == Enum.PathStatus.Success then
		for index, waypoint in pairs(path:GetWaypoints()) do
			local target = findTarget()
			if target and target.Humanoid.Health > 0 then
				print("TARGET FOUND", target.Name)
				attack(target)
				break
			else
				print("Moving to ", waypoint.Position)
				humanoid:MoveTo(waypoint.Position)
				humanoid.MoveToFinished:Wait()
			end
		end
	else
		humanoid:MoveTo(destination.Position - (Igor.HumanoidRootPart.CFrame.LookVector * 10))
	end
end

function patrol()
	local waypoints = workspace.waypoints:GetChildren()
	local randomNum = math.random(1, #waypoints)
	walkTo(waypoints[randomNum])
end

while wait(0.25) do
	patrol()
end


The animation I need it to play while moving:

The animation script I was provided in the tutorial model:

local Figure = script.Parent
local Torso = Figure:WaitForChild("Torso")
local RightShoulder = Torso:WaitForChild("Right Shoulder")
local LeftShoulder = Torso:WaitForChild("Left Shoulder")
local RightHip = Torso:WaitForChild("Right Hip")
local LeftHip = Torso:WaitForChild("Left Hip")
local Neck = Torso:WaitForChild("Neck")
local Humanoid = Figure:WaitForChild("Humanoid")
local pose = "Standing"

local currentAnim = ""
local currentAnimInstance = nil
local currentAnimTrack = nil
local currentAnimKeyframeHandler = nil
local currentAnimSpeed = 1.0
local animTable = {}
local animNames = { 
	run = 	{	
		     { id = "run.xml", weight = 10 } 
	
--				{ id = "slash.xml", weight = 10 } 
			},
	toollunge = {
				{ id = "http://www.roblox.com/asset/?id=129967478", weight = 10 } 
			},
	wave = {
				{ id = "http://www.roblox.com/asset/?id=128777973", weight = 10 } 
			},
	point = {
				{ id = "http://www.roblox.com/asset/?id=128853357", weight = 10 } 
			},
	dance1 = {
				{ id = "http://www.roblox.com/asset/?id=182435998", weight = 10 }, 
				{ id = "http://www.roblox.com/asset/?id=182491037", weight = 10 }, 
				{ id = "http://www.roblox.com/asset/?id=182491065", weight = 10 } 
			},
	dance2 = {
				{ id = "http://www.roblox.com/asset/?id=182436842", weight = 10 }, 
				{ id = "http://www.roblox.com/asset/?id=182491248", weight = 10 }, 
				{ id = "http://www.roblox.com/asset/?id=182491277", weight = 10 } 
			},
	dance3 = {
				{ id = "http://www.roblox.com/asset/?id=182436935", weight = 10 }, 
				{ id = "http://www.roblox.com/asset/?id=182491368", weight = 10 }, 
				{ id = "http://www.roblox.com/asset/?id=182491423", weight = 10 } 
			},
	laugh = {
				{ id = "http://www.roblox.com/asset/?id=129423131", weight = 10 } 
			},
	cheer = {
				{ id = "http://www.roblox.com/asset/?id=129423030", weight = 10 } 
			},
}
local dances = {"dance1", "dance2", "dance3"}

-- Existance in this list signifies that it is an emote, the value indicates if it is a looping emote
local emoteNames = { wave = false, point = false, dance1 = true, dance2 = true, dance3 = true, laugh = false, cheer = false}

function configureAnimationSet(name, fileList)
	if (animTable[name] ~= nil) then
		for _, connection in pairs(animTable[name].connections) do
			connection:disconnect()
		end
	end
	animTable[name] = {}
	animTable[name].count = 0
	animTable[name].totalWeight = 0	
	animTable[name].connections = {}

	-- check for config values
	local config = script:FindFirstChild(name)
	if (config ~= nil) then
--		print("Loading anims " .. name)
		table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
		table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
		local idx = 1
		for _, childPart in pairs(config:GetChildren()) do
			if (childPart:IsA("Animation")) then
				table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
				animTable[name][idx] = {}
				animTable[name][idx].anim = childPart
				local weightObject = childPart:FindFirstChild("Weight")
				if (weightObject == nil) then
					animTable[name][idx].weight = 1
				else
					animTable[name][idx].weight = weightObject.Value
				end
				animTable[name].count = animTable[name].count + 1
				animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
	--			print(name .. " [" .. idx .. "] " .. animTable[name][idx].anim.AnimationId .. " (" .. animTable[name][idx].weight .. ")")
				idx = idx + 1
			end
		end
	end

	-- fallback to defaults
	if (animTable[name].count <= 0) then
		for idx, anim in pairs(fileList) do
			animTable[name][idx] = {}
			animTable[name][idx].anim = Instance.new("Animation")
			animTable[name][idx].anim.Name = name
			animTable[name][idx].anim.AnimationId = anim.id
			animTable[name][idx].weight = anim.weight
			animTable[name].count = animTable[name].count + 1
			animTable[name].totalWeight = animTable[name].totalWeight + anim.weight
--			print(name .. " [" .. idx .. "] " .. anim.id .. " (" .. anim.weight .. ")")
		end
	end
end

-- Setup animation objects
function scriptChildModified(child)
	local fileList = animNames[child.Name]
	if (fileList ~= nil) then
		configureAnimationSet(child.Name, fileList)
	end	
end

script.ChildAdded:connect(scriptChildModified)
script.ChildRemoved:connect(scriptChildModified)


for name, fileList in pairs(animNames) do 
	configureAnimationSet(name, fileList)
end	

-- ANIMATION

-- declarations
local toolAnim = "None"
local toolAnimTime = 0

local jumpAnimTime = 0
local jumpAnimDuration = 0.3

local toolTransitionTime = 0.1
local fallTransitionTime = 0.3
local jumpMaxLimbVelocity = 0.75

-- functions

function stopAllAnimations()
	local oldAnim = currentAnim

	-- return to idle if finishing an emote
	if (emoteNames[oldAnim] ~= nil and emoteNames[oldAnim] == false) then
		oldAnim = "idle"
	end

	currentAnim = ""
	currentAnimInstance = nil
	if (currentAnimKeyframeHandler ~= nil) then
		currentAnimKeyframeHandler:disconnect()
	end

	if (currentAnimTrack ~= nil) then
		currentAnimTrack:Stop()
		currentAnimTrack:Destroy()
		currentAnimTrack = nil
	end
	return oldAnim
end

function setAnimationSpeed(speed)
	if speed ~= currentAnimSpeed then
		currentAnimSpeed = speed
		currentAnimTrack:AdjustSpeed(currentAnimSpeed)
	end
end

function keyFrameReachedFunc(frameName)
	if (frameName == "End") then

		local repeatAnim = currentAnim
		-- return to idle if finishing an emote
		if (emoteNames[repeatAnim] ~= nil and emoteNames[repeatAnim] == false) then
			repeatAnim = "idle"
		end
		
		local animSpeed = currentAnimSpeed
		playAnimation(repeatAnim, 0.0, Humanoid)
		setAnimationSpeed(animSpeed)
	end
end

-- Preload animations
function playAnimation(animName, transitionTime, humanoid) 
		
	local roll = math.random(1, animTable[animName].totalWeight) 
	local origRoll = roll
	local idx = 1
	while (roll > animTable[animName][idx].weight) do
		roll = roll - animTable[animName][idx].weight
		idx = idx + 1
	end
--		print(animName .. " " .. idx .. " [" .. origRoll .. "]")
	local anim = animTable[animName][idx].anim

	-- switch animation		
	if (anim ~= currentAnimInstance) then
		
		if (currentAnimTrack ~= nil) then
			currentAnimTrack:Stop(transitionTime)
			currentAnimTrack:Destroy()
		end

		currentAnimSpeed = 1.0
	
		-- load it to the humanoid; get AnimationTrack
		currentAnimTrack = humanoid:LoadAnimation(anim)
		currentAnimTrack.Priority = Enum.AnimationPriority.Core
		 
		-- play the animation
		currentAnimTrack:Play(transitionTime)
		currentAnim = animName
		currentAnimInstance = anim

		-- set up keyframe name triggers
		if (currentAnimKeyframeHandler ~= nil) then
			currentAnimKeyframeHandler:disconnect()
		end
		currentAnimKeyframeHandler = currentAnimTrack.KeyframeReached:connect(keyFrameReachedFunc)
		
	end

end

-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------

local toolAnimName = ""
local toolAnimTrack = nil
local toolAnimInstance = nil
local currentToolAnimKeyframeHandler = nil

function toolKeyFrameReachedFunc(frameName)
	if (frameName == "End") then
--		print("Keyframe : ".. frameName)	
		playToolAnimation(toolAnimName, 0.0, Humanoid)
	end
end


function playToolAnimation(animName, transitionTime, humanoid, priority)	 
		
		local roll = math.random(1, animTable[animName].totalWeight) 
		local origRoll = roll
		local idx = 1
		while (roll > animTable[animName][idx].weight) do
			roll = roll - animTable[animName][idx].weight
			idx = idx + 1
		end
--		print(animName .. " * " .. idx .. " [" .. origRoll .. "]")
		local anim = animTable[animName][idx].anim

		if (toolAnimInstance ~= anim) then
			
			if (toolAnimTrack ~= nil) then
				toolAnimTrack:Stop()
				toolAnimTrack:Destroy()
				transitionTime = 0
			end
					
			-- load it to the humanoid; get AnimationTrack
			toolAnimTrack = humanoid:LoadAnimation(anim)
			if priority then
				toolAnimTrack.Priority = priority
			end
			 
			-- play the animation
			toolAnimTrack:Play(transitionTime)
			toolAnimName = animName
			toolAnimInstance = anim

			currentToolAnimKeyframeHandler = toolAnimTrack.KeyframeReached:connect(toolKeyFrameReachedFunc)
		end
end

function stopToolAnimations()
	local oldAnim = toolAnimName

	if (currentToolAnimKeyframeHandler ~= nil) then
		currentToolAnimKeyframeHandler:disconnect()
	end

	toolAnimName = ""
	toolAnimInstance = nil
	if (toolAnimTrack ~= nil) then
		toolAnimTrack:Stop()
		toolAnimTrack:Destroy()
		toolAnimTrack = nil
	end


	return oldAnim
end

-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------


function onRunning(speed)
	if speed > 0.01 then
		playAnimation("walk", 0.1, Humanoid)
		if currentAnimInstance and currentAnimInstance.AnimationId == "http://www.roblox.com/asset/?id=180426354" then
			setAnimationSpeed(speed / 14.5)
		end
		pose = "Running"
	else
		if emoteNames[currentAnim] == nil then
			playAnimation("idle", 0.1, Humanoid)
			pose = "Standing"
		end
	end
end

function onDied()
	pose = "Dead"
end

function onJumping()
	playAnimation("jump", 0.1, Humanoid)
	jumpAnimTime = jumpAnimDuration
	pose = "Jumping"
end

function onClimbing(speed)
	playAnimation("climb", 0.1, Humanoid)
	setAnimationSpeed(speed / 12.0)
	pose = "Climbing"
end

function onGettingUp()
	pose = "GettingUp"
end

function onFreeFall()
	if (jumpAnimTime <= 0) then
		playAnimation("fall", fallTransitionTime, Humanoid)
	end
	pose = "FreeFall"
end

function onFallingDown()
	pose = "FallingDown"
end

function onSeated()
	pose = "Seated"
end

function onPlatformStanding()
	pose = "PlatformStanding"
end

function onSwimming(speed)
	if speed > 0 then
		pose = "Running"
	else
		pose = "Standing"
	end
end

function getTool()	
	for _, kid in ipairs(Figure:GetChildren()) do
		if kid.className == "Tool" then return kid end
	end
	return nil
end

function getToolAnim(tool)
	for _, c in ipairs(tool:GetChildren()) do
		if c.Name == "toolanim" and c.className == "StringValue" then
			return c
		end
	end
	return nil
end

function animateTool()
	
	if (toolAnim == "None") then
		playToolAnimation("toolnone", toolTransitionTime, Humanoid, Enum.AnimationPriority.Idle)
		return
	end

	if (toolAnim == "Slash") then
		playToolAnimation("toolslash", 0, Humanoid, Enum.AnimationPriority.Action)
		return
	end

	if (toolAnim == "Lunge") then
		playToolAnimation("toollunge", 0, Humanoid, Enum.AnimationPriority.Action)
		return
	end
end

function moveSit()
	RightShoulder.MaxVelocity = 0.15
	LeftShoulder.MaxVelocity = 0.15
	RightShoulder:SetDesiredAngle(3.14 /2)
	LeftShoulder:SetDesiredAngle(-3.14 /2)
	RightHip:SetDesiredAngle(3.14 /2)
	LeftHip:SetDesiredAngle(-3.14 /2)
end

local lastTick = 0

function move(time)
	local amplitude = 1
	local frequency = 1
  	local deltaTime = time - lastTick
  	lastTick = time

	local climbFudge = 0
	local setAngles = false

  	if (jumpAnimTime > 0) then
  		jumpAnimTime = jumpAnimTime - deltaTime
  	end

	if (pose == "FreeFall" and jumpAnimTime <= 0) then
		playAnimation("fall", fallTransitionTime, Humanoid)
	elseif (pose == "Seated") then
		playAnimation("sit", 0.5, Humanoid)
		return
	elseif (pose == "Running") then
		playAnimation("walk", 0.1, Humanoid)
	elseif (pose == "Dead" or pose == "GettingUp" or pose == "FallingDown" or pose == "Seated" or pose == "PlatformStanding") then
--		print("Wha " .. pose)
		stopAllAnimations()
		amplitude = 0.1
		frequency = 1
		setAngles = true
	end

	if (setAngles) then
		local desiredAngle = amplitude * math.sin(time * frequency)

		RightShoulder:SetDesiredAngle(desiredAngle + climbFudge)
		LeftShoulder:SetDesiredAngle(desiredAngle - climbFudge)
		RightHip:SetDesiredAngle(-desiredAngle)
		LeftHip:SetDesiredAngle(-desiredAngle)
	end

	-- Tool Animation handling
	local tool = getTool()
	if tool and tool:FindFirstChild("Handle") then
	
		local animStringValueObject = getToolAnim(tool)

		if animStringValueObject then
			toolAnim = animStringValueObject.Value
			-- message recieved, delete StringValue
			animStringValueObject.Parent = nil
			toolAnimTime = time + .3
		end

		if time > toolAnimTime then
			toolAnimTime = 0
			toolAnim = "None"
		end

		animateTool()		
	else
		stopToolAnimations()
		toolAnim = "None"
		toolAnimInstance = nil
		toolAnimTime = 0
	end
end

-- connect events
Humanoid.Died:connect(onDied)
Humanoid.Running:connect(onRunning)
Humanoid.Jumping:connect(onJumping)
Humanoid.Climbing:connect(onClimbing)
Humanoid.GettingUp:connect(onGettingUp)
Humanoid.FreeFalling:connect(onFreeFall)
Humanoid.FallingDown:connect(onFallingDown)
Humanoid.Seated:connect(onSeated)
Humanoid.PlatformStanding:connect(onPlatformStanding)
Humanoid.Swimming:connect(onSwimming)

-- main program

-- initialize to idle
playAnimation("idle", 0.1, Humanoid)
pose = "Standing"

while Figure.Parent ~= nil do
	local _, time = wait(0.1)
	move(time)
end

At the top of the script, I tried changing the

run = 	{	
		     { id = "run.xml", weight = 10 } 

part to

run = 	{	
		     { id = "http://www.roblox.com/asset/?id=(theid)", weight = 10 } 

as some other stuff below it was listed like this but it didn’t do anything for it.

I’d appreciate any help on how to get that animation to play with the animation script as I’m not very sure how I’ll go about this and this literally the last thing I need to get working to finish part 1/3 of my horror game after several months!

1 Like

Hey!

First, I’d like to say your character looks very cool.

Is the HumanoidRootPart on your character unanchored? For me, that usually solves the issue.

3 Likes

Thanks for the compliment!

Yes, the HumanoidRootPart of the character is unanchored.

image

Not so sure it has to do with that but I don’t know what I’m doing wrong with the animation script that it doesn’t want to play the walking animation I made for it. Anything else you may think is happening?

2 Likes

Alright, good to know!

So, in the GnomeCode tutorial model, the character that GnomeCode built is actually in R6. And, it looks like your character is in R15.

There are separate animations scripts for each rig. And, since GnomeCode is using an R6 rig, I don’t believe that it will work for your R15 rig.

Try replacing your animation script with this and changing the IDs to the ones you desire for your character:

-- I grabbed this from my R15 character
local Character = script.Parent
local Humanoid = Character:WaitForChild("Humanoid")
local pose = "Standing"

local userNoUpdateOnLoopSuccess, userNoUpdateOnLoopValue = pcall(function() return UserSettings():IsUserFeatureEnabled("UserNoUpdateOnLoop") end)
local userNoUpdateOnLoop = userNoUpdateOnLoopSuccess and userNoUpdateOnLoopValue

local userEmoteToRunThresholdChange do
    local success, value = pcall(function()
        return UserSettings():IsUserFeatureEnabled("UserEmoteToRunThresholdChange")
    end)
    userEmoteToRunThresholdChange = success and value
end

local userPlayEmoteByIdAnimTrackReturn do
    local success, value = pcall(function()
        return UserSettings():IsUserFeatureEnabled("UserPlayEmoteByIdAnimTrackReturn2")
    end)
    userPlayEmoteByIdAnimTrackReturn = success and value
end

local AnimationSpeedDampeningObject = script:FindFirstChild("ScaleDampeningPercent")
local HumanoidHipHeight = 2

local EMOTE_TRANSITION_TIME = 0.1

local currentAnim = ""
local currentAnimInstance = nil
local currentAnimTrack = nil
local currentAnimKeyframeHandler = nil
local currentAnimSpeed = 1.0

local runAnimTrack = nil
local runAnimKeyframeHandler = nil

local PreloadedAnims = {}

local animTable = {}
local animNames = {  -- Replace my IDs with yours of course lol
    idle =     {    
                { id = "http://www.roblox.com/asset/?id=507766666", weight = 1 },
                { id = "http://www.roblox.com/asset/?id=507766951", weight = 1 },
                { id = "http://www.roblox.com/asset/?id=507766388", weight = 9 }
            },
    walk =     {     
                { id = "http://www.roblox.com/asset/?id=507777826", weight = 10 } 
            }, 
    run =     {
                { id = "http://www.roblox.com/asset/?id=507767714", weight = 10 } 
            }, 
    swim =     {
                { id = "http://www.roblox.com/asset/?id=507784897", weight = 10 } 
            }, 
    swimidle =     {
                { id = "http://www.roblox.com/asset/?id=507785072", weight = 10 } 
            }, 
    jump =     {
                { id = "http://www.roblox.com/asset/?id=507765000", weight = 10 } 
            }, 
    fall =     {
                { id = "http://www.roblox.com/asset/?id=507767968", weight = 10 } 
            }, 
    climb = {
                { id = "http://www.roblox.com/asset/?id=507765644", weight = 10 } 
            }, 
    sit =     {
                { id = "http://www.roblox.com/asset/?id=2506281703", weight = 10 } 
            },    
    toolnone = {
                { id = "http://www.roblox.com/asset/?id=507768375", weight = 10 } 
            },
    toolslash = {
                { id = "http://www.roblox.com/asset/?id=522635514", weight = 10 } 
            },
    toollunge = {
                { id = "http://www.roblox.com/asset/?id=522638767", weight = 10 } 
            },
    wave = {
                { id = "http://www.roblox.com/asset/?id=507770239", weight = 10 } 
            },
    point = {
                { id = "http://www.roblox.com/asset/?id=507770453", weight = 10 } 
            },
    dance = {
                { id = "http://www.roblox.com/asset/?id=507771019", weight = 10 }, 
                { id = "http://www.roblox.com/asset/?id=507771955", weight = 10 }, 
                { id = "http://www.roblox.com/asset/?id=507772104", weight = 10 } 
            },
    dance2 = {
                { id = "http://www.roblox.com/asset/?id=507776043", weight = 10 }, 
                { id = "http://www.roblox.com/asset/?id=507776720", weight = 10 }, 
                { id = "http://www.roblox.com/asset/?id=507776879", weight = 10 } 
            },
    dance3 = {
                { id = "http://www.roblox.com/asset/?id=507777268", weight = 10 }, 
                { id = "http://www.roblox.com/asset/?id=507777451", weight = 10 }, 
                { id = "http://www.roblox.com/asset/?id=507777623", weight = 10 } 
            },
    laugh = {
                { id = "http://www.roblox.com/asset/?id=507770818", weight = 10 } 
            },
    cheer = {
                { id = "http://www.roblox.com/asset/?id=507770677", weight = 10 } 
            },
}

-- Existance in this list signifies that it is an emote, the value indicates if it is a looping emote
local emoteNames = { wave = false, point = false, dance = true, dance2 = true, dance3 = true, laugh = false, cheer = false}

math.randomseed(tick())

function findExistingAnimationInSet(set, anim)
    if set == nil or anim == nil then
        return 0
    end
    
    for idx = 1, set.count, 1 do 
        if set[idx].anim.AnimationId == anim.AnimationId then
            return idx
        end
    end
    
    return 0
end

function configureAnimationSet(name, fileList)
    if (animTable[name] ~= nil) then
        for _, connection in pairs(animTable[name].connections) do
            connection:disconnect()
        end
    end
    animTable[name] = {}
    animTable[name].count = 0
    animTable[name].totalWeight = 0    
    animTable[name].connections = {}

    local allowCustomAnimations = true

    local success, msg = pcall(function() allowCustomAnimations = game:GetService("StarterPlayer").AllowCustomAnimations end)
    if not success then
        allowCustomAnimations = true
    end

    -- check for config values
    local config = script:FindFirstChild(name)
    if (allowCustomAnimations and config ~= nil) then
        table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
        table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
        
        local idx = 0
        for _, childPart in pairs(config:GetChildren()) do
            if (childPart:IsA("Animation")) then
                local newWeight = 1
                local weightObject = childPart:FindFirstChild("Weight")
                if (weightObject ~= nil) then
                    newWeight = weightObject.Value
                end
                animTable[name].count = animTable[name].count + 1
                idx = animTable[name].count
                animTable[name][idx] = {}
                animTable[name][idx].anim = childPart
                animTable[name][idx].weight = newWeight
                animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
                table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
                table.insert(animTable[name].connections, childPart.ChildAdded:connect(function(property) configureAnimationSet(name, fileList) end))
                table.insert(animTable[name].connections, childPart.ChildRemoved:connect(function(property) configureAnimationSet(name, fileList) end))
            end
        end
    end
    
    -- fallback to defaults
    if (animTable[name].count <= 0) then
        for idx, anim in pairs(fileList) do
            animTable[name][idx] = {}
            animTable[name][idx].anim = Instance.new("Animation")
            animTable[name][idx].anim.Name = name
            animTable[name][idx].anim.AnimationId = anim.id
            animTable[name][idx].weight = anim.weight
            animTable[name].count = animTable[name].count + 1
            animTable[name].totalWeight = animTable[name].totalWeight + anim.weight
        end
    end
    
    -- preload anims
    for i, animType in pairs(animTable) do
        for idx = 1, animType.count, 1 do
            if PreloadedAnims[animType[idx].anim.AnimationId] == nil then
                Humanoid:LoadAnimation(animType[idx].anim)
                PreloadedAnims[animType[idx].anim.AnimationId] = true
            end                
        end
    end
end

------------------------------------------------------------------------------------------------------------

function configureAnimationSetOld(name, fileList)
    if (animTable[name] ~= nil) then
        for _, connection in pairs(animTable[name].connections) do
            connection:disconnect()
        end
    end
    animTable[name] = {}
    animTable[name].count = 0
    animTable[name].totalWeight = 0    
    animTable[name].connections = {}

    local allowCustomAnimations = true

    local success, msg = pcall(function() allowCustomAnimations = game:GetService("StarterPlayer").AllowCustomAnimations end)
    if not success then
        allowCustomAnimations = true
    end

    -- check for config values
    local config = script:FindFirstChild(name)
    if (allowCustomAnimations and config ~= nil) then
        table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
        table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
        local idx = 1
        for _, childPart in pairs(config:GetChildren()) do
            if (childPart:IsA("Animation")) then
                table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
                animTable[name][idx] = {}
                animTable[name][idx].anim = childPart
                local weightObject = childPart:FindFirstChild("Weight")
                if (weightObject == nil) then
                    animTable[name][idx].weight = 1
                else
                    animTable[name][idx].weight = weightObject.Value
                end
                animTable[name].count = animTable[name].count + 1
                animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
                idx = idx + 1
            end
        end
    end

    -- fallback to defaults
    if (animTable[name].count <= 0) then
        for idx, anim in pairs(fileList) do
            animTable[name][idx] = {}
            animTable[name][idx].anim = Instance.new("Animation")
            animTable[name][idx].anim.Name = name
            animTable[name][idx].anim.AnimationId = anim.id
            animTable[name][idx].weight = anim.weight
            animTable[name].count = animTable[name].count + 1
            animTable[name].totalWeight = animTable[name].totalWeight + anim.weight
            -- print(name .. " [" .. idx .. "] " .. anim.id .. " (" .. anim.weight .. ")")
        end
    end
    
    -- preload anims
    for i, animType in pairs(animTable) do
        for idx = 1, animType.count, 1 do 
            Humanoid:LoadAnimation(animType[idx].anim)
        end
    end
end

-- Setup animation objects
function scriptChildModified(child)
    local fileList = animNames[child.Name]
    if (fileList ~= nil) then
        configureAnimationSet(child.Name, fileList)
    end    
end

script.ChildAdded:connect(scriptChildModified)
script.ChildRemoved:connect(scriptChildModified)


for name, fileList in pairs(animNames) do 
    configureAnimationSet(name, fileList)
end    

-- ANIMATION

-- declarations
local toolAnim = "None"
local toolAnimTime = 0

local jumpAnimTime = 0
local jumpAnimDuration = 0.31

local toolTransitionTime = 0.1
local fallTransitionTime = 0.2

local currentlyPlayingEmote = false

-- functions

function stopAllAnimations()
    local oldAnim = currentAnim

    -- return to idle if finishing an emote
    if (emoteNames[oldAnim] ~= nil and emoteNames[oldAnim] == false) then
        oldAnim = "idle"
    end
    
    if currentlyPlayingEmote then
        oldAnim = "idle"
        currentlyPlayingEmote = false
    end

    currentAnim = ""
    currentAnimInstance = nil
    if (currentAnimKeyframeHandler ~= nil) then
        currentAnimKeyframeHandler:disconnect()
    end

    if (currentAnimTrack ~= nil) then
        currentAnimTrack:Stop()
        currentAnimTrack:Destroy()
        currentAnimTrack = nil
    end

    -- clean up walk if there is one
    if (runAnimKeyframeHandler ~= nil) then
        runAnimKeyframeHandler:disconnect()
    end
    
    if (runAnimTrack ~= nil) then
        runAnimTrack:Stop()
        runAnimTrack:Destroy()
        runAnimTrack = nil
    end
    
    return oldAnim
end

function getHeightScale()
    if Humanoid then
        if not Humanoid.AutomaticScalingEnabled then
            return 1
        end
        
        local scale = Humanoid.HipHeight / HumanoidHipHeight
        if AnimationSpeedDampeningObject == nil then
            AnimationSpeedDampeningObject = script:FindFirstChild("ScaleDampeningPercent")
        end
        if AnimationSpeedDampeningObject ~= nil then
            scale = 1 + (Humanoid.HipHeight - HumanoidHipHeight) * AnimationSpeedDampeningObject.Value / HumanoidHipHeight
        end
        return scale
    end    
    return 1
end

local function rootMotionCompensation(speed)
    local speedScaled = speed * 1.25
    local heightScale = getHeightScale()
    local runSpeed = speedScaled / heightScale
    return runSpeed
end

local smallButNotZero = 0.0001
local function setRunSpeed(speed)
    local normalizedWalkSpeed = 0.5 -- established empirically using current `913402848` walk animation
    local normalizedRunSpeed  = 1
    local runSpeed = rootMotionCompensation(speed)

    local walkAnimationWeight = smallButNotZero
    local runAnimationWeight = smallButNotZero
    local walkAnimationTimewarp = runSpeed/normalizedWalkSpeed
    local runAnimationTimerwarp = runSpeed/normalizedRunSpeed

    if runSpeed <= normalizedWalkSpeed then
        walkAnimationWeight = 1
    elseif runSpeed < normalizedRunSpeed then
        local fadeInRun = (runSpeed - normalizedWalkSpeed)/(normalizedRunSpeed - normalizedWalkSpeed)
        walkAnimationWeight = 1 - fadeInRun
        runAnimationWeight  = fadeInRun
        walkAnimationTimewarp = 1
        runAnimationTimerwarp = 1
    else
        runAnimationWeight = 1
    end
    currentAnimTrack:AdjustWeight(walkAnimationWeight)
    runAnimTrack:AdjustWeight(runAnimationWeight)
    currentAnimTrack:AdjustSpeed(walkAnimationTimewarp)
    runAnimTrack:AdjustSpeed(runAnimationTimerwarp)
end

function setAnimationSpeed(speed)
    if currentAnim == "walk" then
            setRunSpeed(speed)
    else
        if speed ~= currentAnimSpeed then
            currentAnimSpeed = speed
            currentAnimTrack:AdjustSpeed(currentAnimSpeed)
        end
    end
end

function keyFrameReachedFunc(frameName)
    if (frameName == "End") then
        if currentAnim == "walk" then
            if userNoUpdateOnLoop == true then
                if runAnimTrack.Looped ~= true then
                    runAnimTrack.TimePosition = 0.0
                end
                if currentAnimTrack.Looped ~= true then
                    currentAnimTrack.TimePosition = 0.0
                end
            else
                runAnimTrack.TimePosition = 0.0
                currentAnimTrack.TimePosition = 0.0
            end
        else
            local repeatAnim = currentAnim
            -- return to idle if finishing an emote
            if (emoteNames[repeatAnim] ~= nil and emoteNames[repeatAnim] == false) then
                repeatAnim = "idle"
            end
            
            if currentlyPlayingEmote then
                if currentAnimTrack.Looped then
                    -- Allow the emote to loop
                    return
                end
                
                repeatAnim = "idle"
                currentlyPlayingEmote = false
            end
            
            local animSpeed = currentAnimSpeed
            playAnimation(repeatAnim, 0.15, Humanoid)
            setAnimationSpeed(animSpeed)
        end
    end
end

function rollAnimation(animName)
    local roll = math.random(1, animTable[animName].totalWeight) 
    local origRoll = roll
    local idx = 1
    while (roll > animTable[animName][idx].weight) do
        roll = roll - animTable[animName][idx].weight
        idx = idx + 1
    end
    return idx
end

local function switchToAnim(anim, animName, transitionTime, humanoid)
    -- switch animation        
    if (anim ~= currentAnimInstance) then
        
        if (currentAnimTrack ~= nil) then
            currentAnimTrack:Stop(transitionTime)
            currentAnimTrack:Destroy()
        end

        if (runAnimTrack ~= nil) then
            runAnimTrack:Stop(transitionTime)
            runAnimTrack:Destroy()
            if userNoUpdateOnLoop == true then
                runAnimTrack = nil
            end
        end

        currentAnimSpeed = 1.0
    
        -- load it to the humanoid; get AnimationTrack
        currentAnimTrack = humanoid:LoadAnimation(anim)
        currentAnimTrack.Priority = Enum.AnimationPriority.Core
         
        -- play the animation
        currentAnimTrack:Play(transitionTime)
        currentAnim = animName
        currentAnimInstance = anim

        -- set up keyframe name triggers
        if (currentAnimKeyframeHandler ~= nil) then
            currentAnimKeyframeHandler:disconnect()
        end
        currentAnimKeyframeHandler = currentAnimTrack.KeyframeReached:connect(keyFrameReachedFunc)
        
        -- check to see if we need to blend a walk/run animation
        if animName == "walk" then
            local runAnimName = "run"
            local runIdx = rollAnimation(runAnimName)

            runAnimTrack = humanoid:LoadAnimation(animTable[runAnimName][runIdx].anim)
            runAnimTrack.Priority = Enum.AnimationPriority.Core
            runAnimTrack:Play(transitionTime)        
            
            if (runAnimKeyframeHandler ~= nil) then
                runAnimKeyframeHandler:disconnect()
            end
            runAnimKeyframeHandler = runAnimTrack.KeyframeReached:connect(keyFrameReachedFunc)    
        end
    end
end

function playAnimation(animName, transitionTime, humanoid)     
    local idx = rollAnimation(animName)
    local anim = animTable[animName][idx].anim

    switchToAnim(anim, animName, transitionTime, humanoid)
    currentlyPlayingEmote = false
end

function playEmote(emoteAnim, transitionTime, humanoid)
    switchToAnim(emoteAnim, emoteAnim.Name, transitionTime, humanoid)
    currentlyPlayingEmote = true
end

-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------

local toolAnimName = ""
local toolAnimTrack = nil
local toolAnimInstance = nil
local currentToolAnimKeyframeHandler = nil

function toolKeyFrameReachedFunc(frameName)
    if (frameName == "End") then
        playToolAnimation(toolAnimName, 0.0, Humanoid)
    end
end


function playToolAnimation(animName, transitionTime, humanoid, priority)             
        local idx = rollAnimation(animName)
        local anim = animTable[animName][idx].anim

        if (toolAnimInstance ~= anim) then
            
            if (toolAnimTrack ~= nil) then
                toolAnimTrack:Stop()
                toolAnimTrack:Destroy()
                transitionTime = 0
            end
                    
            -- load it to the humanoid; get AnimationTrack
            toolAnimTrack = humanoid:LoadAnimation(anim)
            if priority then
                toolAnimTrack.Priority = priority
            end
             
            -- play the animation
            toolAnimTrack:Play(transitionTime)
            toolAnimName = animName
            toolAnimInstance = anim

            currentToolAnimKeyframeHandler = toolAnimTrack.KeyframeReached:connect(toolKeyFrameReachedFunc)
        end
end

function stopToolAnimations()
    local oldAnim = toolAnimName

    if (currentToolAnimKeyframeHandler ~= nil) then
        currentToolAnimKeyframeHandler:disconnect()
    end

    toolAnimName = ""
    toolAnimInstance = nil
    if (toolAnimTrack ~= nil) then
        toolAnimTrack:Stop()
        toolAnimTrack:Destroy()
        toolAnimTrack = nil
    end

    return oldAnim
end

-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- STATE CHANGE HANDLERS

function onRunning(speed)
    local movedDuringEmote =
        userEmoteToRunThresholdChange and currentlyPlayingEmote and Humanoid.MoveDirection == Vector3.new(0, 0, 0)
    local speedThreshold = movedDuringEmote and Humanoid.WalkSpeed or 0.75
    if speed > speedThreshold then
        local scale = 16.0
        playAnimation("walk", 0.2, Humanoid)
        setAnimationSpeed(speed / scale)
        pose = "Running"
    else
        if emoteNames[currentAnim] == nil and not currentlyPlayingEmote then
            playAnimation("idle", 0.2, Humanoid)
            pose = "Standing"
        end
    end
end

function onDied()
    pose = "Dead"
end

function onJumping()
    playAnimation("jump", 0.1, Humanoid)
    jumpAnimTime = jumpAnimDuration
    pose = "Jumping"
end

function onClimbing(speed)
    local scale = 5.0
    playAnimation("climb", 0.1, Humanoid)
    setAnimationSpeed(speed / scale)
    pose = "Climbing"
end

function onGettingUp()
    pose = "GettingUp"
end

function onFreeFall()
    if (jumpAnimTime <= 0) then
        playAnimation("fall", fallTransitionTime, Humanoid)
    end
    pose = "FreeFall"
end

function onFallingDown()
    pose = "FallingDown"
end

function onSeated()
    pose = "Seated"
end

function onPlatformStanding()
    pose = "PlatformStanding"
end

-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------

function onSwimming(speed)
    if speed > 1.00 then
        local scale = 10.0
        playAnimation("swim", 0.4, Humanoid)
        setAnimationSpeed(speed / scale)
        pose = "Swimming"
    else
        playAnimation("swimidle", 0.4, Humanoid)
        pose = "Standing"
    end
end

function animateTool()
    if (toolAnim == "None") then
        playToolAnimation("toolnone", toolTransitionTime, Humanoid, Enum.AnimationPriority.Idle)
        return
    end

    if (toolAnim == "Slash") then
        playToolAnimation("toolslash", 0, Humanoid, Enum.AnimationPriority.Action)
        return
    end

    if (toolAnim == "Lunge") then
        playToolAnimation("toollunge", 0, Humanoid, Enum.AnimationPriority.Action)
        return
    end
end

function getToolAnim(tool)
    for _, c in ipairs(tool:GetChildren()) do
        if c.Name == "toolanim" and c.className == "StringValue" then
            return c
        end
    end
    return nil
end

local lastTick = 0

function stepAnimate(currentTime)
    local amplitude = 1
    local frequency = 1
      local deltaTime = currentTime - lastTick
      lastTick = currentTime

    local climbFudge = 0
    local setAngles = false

      if (jumpAnimTime > 0) then
          jumpAnimTime = jumpAnimTime - deltaTime
      end

    if (pose == "FreeFall" and jumpAnimTime <= 0) then
        playAnimation("fall", fallTransitionTime, Humanoid)
    elseif (pose == "Seated") then
        playAnimation("sit", 0.5, Humanoid)
        return
    elseif (pose == "Running") then
        playAnimation("walk", 0.2, Humanoid)
    elseif (pose == "Dead" or pose == "GettingUp" or pose == "FallingDown" or pose == "Seated" or pose == "PlatformStanding") then
        stopAllAnimations()
        amplitude = 0.1
        frequency = 1
        setAngles = true
    end

    -- Tool Animation handling
    local tool = Character:FindFirstChildOfClass("Tool")
    if tool and tool:FindFirstChild("Handle") then
        local animStringValueObject = getToolAnim(tool)

        if animStringValueObject then
            toolAnim = animStringValueObject.Value
            -- message recieved, delete StringValue
            animStringValueObject.Parent = nil
            toolAnimTime = currentTime + .3
        end

        if currentTime > toolAnimTime then
            toolAnimTime = 0
            toolAnim = "None"
        end

        animateTool()        
    else
        stopToolAnimations()
        toolAnim = "None"
        toolAnimInstance = nil
        toolAnimTime = 0
    end
end

-- connect events
Humanoid.Died:connect(onDied)
Humanoid.Running:connect(onRunning)
Humanoid.Jumping:connect(onJumping)
Humanoid.Climbing:connect(onClimbing)
Humanoid.GettingUp:connect(onGettingUp)
Humanoid.FreeFalling:connect(onFreeFall)
Humanoid.FallingDown:connect(onFallingDown)
Humanoid.Seated:connect(onSeated)
Humanoid.PlatformStanding:connect(onPlatformStanding)
Humanoid.Swimming:connect(onSwimming)

-- emote bindable hook
script:WaitForChild("PlayEmote").OnInvoke = function(emote)
    -- Only play emotes when idling
    if pose ~= "Standing" then
        return
    end

    if emoteNames[emote] ~= nil then
        -- Default emotes
        playAnimation(emote, EMOTE_TRANSITION_TIME, Humanoid)
        
        if userPlayEmoteByIdAnimTrackReturn then
            return true, currentAnimTrack
        else
            return true
        end
    elseif typeof(emote) == "Instance" and emote:IsA("Animation") then
        -- Non-default emotes
        playEmote(emote, EMOTE_TRANSITION_TIME, Humanoid)

        if userPlayEmoteByIdAnimTrackReturn then
            return true, currentAnimTrack
        else
            return true
        end
    end
    
    -- Return false to indicate that the emote could not be played
    return false
end

if Character.Parent ~= nil then
    -- initialize to idle
    playAnimation("idle", 0.1, Humanoid)
    pose = "Standing"
end

-- loop to handle timed state transitions and tool animations
while Character.Parent ~= nil do
    local _, currentGameTime = wait(0.1)
    stepAnimate(currentGameTime)
end

I feel you!! I started pathfinding a few months back and had the same issue! So glad I could help!

3 Likes

Wow I would’ve never figured if it wasn’t for you hahaha. I’ve literally been stuck on this for weeks, you’re a legend!!

2 Likes