Animation wont play for infected game on player

Hello guys, I’m making a game based on infectious smile, and I’m working on the infection system, its quite simple and works good enough, except I made an animation, but the animation wont play when I infect the characters, I need help from you guys

2 Likes

We will need to see some code to be able to help you with this

local Players           = game:GetService("Players")
local RunService        = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- references that clients can also see
local assetsFolder      = game.ReplicatedStorage:WaitForChild("InfectionAssets")
local ANIMATION         = assetsFolder:WaitForChild("InfectionAnimation")

local RE_StartAnim      = game.ReplicatedStorage.InfectionAssets:WaitForChild("StartInfectionAnim")
local RE_StopAnim       = game.ReplicatedStorage.InfectionAssets:WaitForChild("StopInfectionAnim")

------------------------------------------------------------------
-- game tuning
local LATCH_RANGE    = 5
local INFECTION_TIME = 3

------------------------------------------------------------------
-- bookkeeping tables
local infectedHumanoids = {}   -- [Humanoid] = true
local activeLatches     = {}   -- [Humanoid] = { targetChar, startTime, weld, npcTrack }

------------------------------------------------------------------
-- Cache variables for getAllCharacters function
local _cachedChars = {}
local _nextRefresh = 0

------------------------------------------------------------------
-- utilities
local function getPlayerFromCharacter(char : Model) : Player?
	return Players:GetPlayerFromCharacter(char)
end

local function getAllCharacters()
	if time() < _nextRefresh then
		return _cachedChars
	end

	local list = {}

	for _, p in ipairs(Players:GetPlayers()) do
		local c = p.Character
		if c and c:FindFirstChildOfClass("Humanoid") then
			table.insert(list, c)
		end
	end
	for _, m in ipairs(workspace:GetChildren()) do
		if m:IsA("Model") and not Players:GetPlayerFromCharacter(m) then
			if m:FindFirstChildOfClass("Humanoid") then
				table.insert(list, m)
			end
		end
	end

	_cachedChars  = list
	_nextRefresh  = time() + .5
	return list
end

------------------------------------------------------------------
-- visual infection (colour, light, walkspeed buff)
local function markInfected(character : Model)
	local hum = character:FindFirstChildOfClass("Humanoid")
	if not hum or infectedHumanoids[hum] then return end

	infectedHumanoids[hum] = true
	hum.WalkSpeed = 20

	for _, d in ipairs(character:GetDescendants()) do
		if d:IsA("BasePart") and d.Name ~= "HumanoidRootPart" then
			pcall(function() d.BrickColor = BrickColor.new("Really red") end)
		end
	end
	local root = character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso")
	if root and not root:FindFirstChild("InfectionGlow") then
		local l = Instance.new("PointLight")
		l.Name, l.Color, l.Brightness, l.Range = "InfectionGlow", Color3.new(1,0,0), 2, 10
		l.Parent = root
	end
	if not character:FindFirstChild("Infected") then
		Instance.new("BoolValue", character).Name = "Infected"
	end
end

------------------------------------------------------------------
-- latch helpers  (server-only things)
local function createLatch(fromChar : Model, toChar : Model, latchTable)
	local fromRoot = fromChar:FindFirstChild("HumanoidRootPart")
	local toRoot   = toChar:FindFirstChild("HumanoidRootPart")
	if not (fromRoot and toRoot) then return end

	local weld = Instance.new("Motor6D")
	weld.Name, weld.Part0, weld.Part1 = "InfectionLatchMotor", fromRoot, toRoot
	weld.C0 = fromRoot.CFrame:Inverse() * toRoot.CFrame
	weld.Parent = fromRoot
	latchTable.weld = weld

	local toHum = toChar:FindFirstChildOfClass("Humanoid")
	if toHum and not infectedHumanoids[toHum] then
		toHum.WalkSpeed = 8
	end
	for _, p in ipairs(toChar:GetDescendants()) do
		if p:IsA("BasePart") then p.CanCollide = false end
	end

	local fromPlayer = getPlayerFromCharacter(fromChar)
	if fromPlayer then
		-- RESEARCH-BASED PRACTICE: For player characters, fire a remote to the client. The client has network ownership and should handle its own animations for responsiveness. This is the correct method. [4, 7]
		RE_StartAnim:FireClient(fromPlayer, ANIMATION)
	else -- NPC – server plays, and the engine replicates it to all clients. [1]
		local hum = fromChar:FindFirstChildOfClass("Humanoid")
		if not hum or hum.Health <= 0 then return end

		-- RESEARCH-BASED FIX #1: Ensure an Animator exists. The documentation confirms animations cannot load without it. [11, 8]
		local animator = hum:FindFirstChildOfClass("Animator")
		if not animator then
			animator = Instance.new("Animator", hum)
		end

		local track = animator:LoadAnimation(ANIMATION)

		-- RESEARCH-BASED FIX #2: Set AnimationPriority to Action. This is the most critical step. [2, 3]
		-- It prevents the default Idle/Walk animations from overriding our custom one.
		track.Priority = Enum.AnimationPriority.Action
		track.Looped = true
		track:Play(0.1)

		latchTable.npcTrack = track -- Store the track so it can be properly stopped later
	end
end

local function releaseLatch(latchTbl, infectedHum : Humanoid)
	if not latchTbl then return end

	local ownerPlr = Players:GetPlayerFromCharacter(infectedHum.Parent)
	if ownerPlr then
		RE_StopAnim:FireClient(ownerPlr)
	elseif latchTbl.npcTrack and typeof(latchTbl.npcTrack.Stop) == "function" then
		-- Defensive check to ensure npcTrack is a valid animation track before calling Stop()
		pcall(function() 
			latchTbl.npcTrack:Stop()
			latchTbl.npcTrack:Destroy() 
		end)
	end

	if latchTbl.weld and latchTbl.weld.Parent then
		latchTbl.weld:Destroy()
	end

	local targetChar = latchTbl.targetChar
	if targetChar and targetChar.Parent then
		for _, p in ipairs(targetChar:GetDescendants()) do
			if p:IsA("BasePart") then p.CanCollide = true end
		end
		local tHum = targetChar:FindFirstChildOfClass("Humanoid")
		if tHum and not infectedHumanoids[tHum] then
			tHum.WalkSpeed = 16
		end
	end
end

------------------------------------------------------------------
-- main think loop
RunService.Heartbeat:Connect(function()
	for hum in pairs(infectedHumanoids) do
		if hum.Health <= 0 or not hum.Parent or not hum:IsDescendantOf(workspace) then
			releaseLatch(activeLatches[hum], hum)
			infectedHumanoids[hum], activeLatches[hum] = nil, nil
			continue
		end

		local char  = hum.Parent
		local root  = char:FindFirstChild("HumanoidRootPart")
		if not root then
			releaseLatch(activeLatches[hum], hum)
			activeLatches[hum] = nil
			continue
		end

		local latch = activeLatches[hum]
		if latch then
			if time() - latch.startTime >= INFECTION_TIME then
				markInfected(latch.targetChar)
				releaseLatch(latch, hum)
				activeLatches[hum] = nil
			elseif not latch.targetChar or not latch.targetChar.Parent then
				releaseLatch(latch, hum)
				activeLatches[hum] = nil
			end
		else
			for _, candidate in ipairs(getAllCharacters()) do
				if candidate ~= char then
					local cHum = candidate:FindFirstChildOfClass("Humanoid")
					if cHum and cHum.Health > 0 and not infectedHumanoids[cHum] then
						local cRoot = candidate:FindFirstChild("HumanoidRootPart")
						if cRoot and (root.Position - cRoot.Position).Magnitude <= LATCH_RANGE then
							local newLatch = {
								targetChar = candidate;
								startTime  = time();
							}
							activeLatches[hum] = newLatch
							createLatch(char, candidate, newLatch)
							break
						end
					end
				end
			end
		end
	end
end)

------------------------------------------------------------------
-- infection entry point (touch-brick)
local infectionStartPart = workspace:WaitForChild("InfectionStart")
infectionStartPart.Touched:Connect(function(hit)
	local char = hit.Parent
	while char and not char:FindFirstChildOfClass("Humanoid") do
		char = char.Parent
	end
	if char then
		markInfected(char)
	end
end)

------------------------------------------------------------------
-- cleanup on player leave / model destroy
local function fullCleanup(humanoid : Humanoid)
	releaseLatch(activeLatches[humanoid], humanoid)
	infectedHumanoids[humanoid], activeLatches[humanoid] = nil, nil
end

Players.PlayerRemoving:Connect(function(p)
	local c = p.Character
	if c then
		local h = c:FindFirstChildOfClass("Humanoid")
		if h then fullCleanup(h) end
	end
end)

workspace.DescendantRemoving:Connect(function(d)
	if d:IsA("Humanoid") then
		fullCleanup(d)
	end
end)

print("✅ Infection system ready (server)")
3 Likes

Is the animation replication really needed using a remote event? Normally Animations played on a Humanoid’s Animator are replicated automatically

Id recommend putting print methods at different parts of the script to see where the functions are reaching and what is being left out, have you checked if the Animation is being loaded at all for example?

i tried doing it, there was no prints, the animation is definitely valid, i tested it on npcs, the animation is r6 and the game, so no rig incompatibilites, I’m just genuinely confused why the animation wont play

When are your functions called and do you have a snippet of the code thats handling the function calls?

when a player is within 5 studs of any uninfected humanoids they latch on using weld constraints, but the animation wont play, in the code i tried to make the animation even try to run on the clients via remote events but it didnt work until i decided to post on this forum for help

And that function prints something when it should call?

it prints but the animation doesnt play, i re-implemented printing, take a look

heres the localscript aswell

--[[ Plays / stops the infection animation when the server asks ]]
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players           = game:GetService("Players")

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

local assets            = ReplicatedStorage:WaitForChild("InfectionAssets")
local ANIMATION         = assets:WaitForChild("InfectionAnimation")

local RE_Start          = assets:WaitForChild("StartInfectionAnim")
local RE_Stop           = assets:WaitForChild("StopInfectionAnim")

local currentTrack

local function ensureAnimator(humanoid)
	local anim = humanoid:FindFirstChildOfClass("Animator")
	if not anim then
		anim = Instance.new("Animator", humanoid)
	end
	return anim
end

RE_Start.OnClientEvent:Connect(function(animationObj)
	local hum = (player.Character or player.CharacterAdded:Wait()):WaitForChild("Humanoid")
	local animator   = ensureAnimator(hum)

	-- stop any previous loop
	if currentTrack then
		currentTrack:Stop()
		currentTrack:Destroy()
		currentTrack = nil
	end

	currentTrack    = animator:LoadAnimation(animationObj)
	currentTrack.Priority = Enum.AnimationPriority.Action
	currentTrack.Looped   = true
	print("Animation is playing")
	currentTrack:Play(0)
	print("Animation is still playing")
end)

RE_Stop.OnClientEvent:Connect(function()
	if currentTrack then
		currentTrack:Stop()
		currentTrack:Destroy()
		currentTrack = nil
	end
end)

-- safety: destroy if we respawn
player.CharacterAdded:Connect(function()
	if currentTrack then
		currentTrack:Stop()
		currentTrack:Destroy()
		currentTrack = nil
	end
end)

The only reason I could still think of is that the Animation itself isn’t actually being Loaded

I was gonna say its the weld but i don’t think that’s it. Just incase disable the weld and re-run this to see if it works