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
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)")
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