So I know that playing animations on client is better as it lowers resources needed on server but sometimes ping and other factors make replication kind of wonky. When I’m sending a remote event to the player there’s always a delay and when I need each animation to play one after another perfectly, its visually not appealing because of the delay. It just worth it to just play animations on the server? But also idk if animations are like tweens were if played on the server, it replicates jittery to the client. (Also my preloading doesn’t seem to work)
Here is a snippet of some replication code
Server:
--These functions don't do anything special,
--they just sends the params inputted through a remote event
m_replicationManager.ReplicateAnimation(
animator, --Animator: Animator
m_furnitureAnimations.vendingMachine.shake --AnimationId: number
)
task.wait(0.5)
m_replicationManager.StopAnimation(
animator, --Animator: Animator
m_furnitureAnimations.vendingMachine.shake, --AnimationId: number
0.25 --FadeOutTime: Animator
)
m_replicationManager.ReplicateAnimation(
animator,
m_furnitureAnimations.vendingMachine.dispenseItem
)
Client:
local animationTracks: {[Animator]:
{[number]: AnimationTrack}
} = {}
local function GarbageCollectAnimations(animator: Animator)
animator.Destroying:Once(function()
local preloadedTracks = animationTracks[animator]
if not preloadedTracks then return end
for animationId, animationTrack: AnimationTrack in animationTracks[animator] do
animationTrack:Destroy()
preloadedTracks[animationId] = nil
end
animationTracks[animator] = nil
end)
end
m_networker:HookEvent("ReplicateAnimation", function(
animator: Animator,
animationId,
trackProperties
)
local animationTrack: AnimationTrack
local preloadedTracks = animationTracks[animator]
if preloadedTracks and preloadedTracks[animationId] then
animationTrack = preloadedTracks[animationId]
else
if not preloadedTracks then
animationTracks[animator] = {}
preloadedTracks = animationTracks[animator]
GarbageCollectAnimations(animator)
end
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://" .. animationId
animationTrack = animator:LoadAnimation(animation)
preloadedTracks[animationId] = animationTrack
animation:Destroy()
end
if trackProperties then
for property, value in trackProperties do
animationTrack[property] = value
end
end
animationTrack:Play()
end)
m_networker:HookEvent("PreloadAnimations", function(animator: Animator, animationIds: {number})
local preloadedTracks = animationTracks[animator]
if not preloadedTracks then
animationTracks[animator] = {}
preloadedTracks = animationTracks[animator]
--Clean up saved tracks
GarbageCollectAnimations(animator)
end
for i, animationId in animationIds do
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://" .. animationId
local animationTrack = animator:LoadAnimation(animation)
preloadedTracks[animationId] = animationTrack
animationTrack:Play()
animationTrack:Stop()
animation:Destroy()
end
end)
m_networker:HookEvent("StopAnimation", function(animator: Animator, animationId, fadeOutTime: number)
local preloadedTracks = animationTracks[animator]
if not preloadedTracks then
warn(`No tracks loaded for {animator.Parent.Parent}`)
return
end
local animationTrack: AnimationTrack = preloadedTracks[animationId]
if not animationTrack then return end
animationTrack:Stop(fadeOutTime or 0)
end)
Why not play the sequence on the client? So you task.wait is done on the client.
All that really needs to be done, as far as server->client communication, is to inform each player that a transaction was made, and then each client handles all of the animation logic themselves. This reduces the amount of data you send and makes it so animations will always play perfectly. You can even send a timestamp to try to sync it to server time.
I need to do things on the server between each animation. Like for example when the display door closes, on the server I need to delete the current display item and replace it with a new one, then the display door reopens. If I task.wait on the client between animations when the server does its statement, I’m afraid that it the event wont line up perfectly because of delay from server to client communication.
I also dont want a million different remotes for each model’s replication
That’s fair, but waiting on the server doesn’t guarantee that it will replicate to the client in time, either. You could generate the new item immediately and have the client handle that, too.
The only way you’re going to guarantee that the client sees everything timed right is if you do all the timing on the client. It’s unreasonable to try to rendering stuff over a ping of 100ms.
Depending on how your game is structured, you should be able to just send the relevant Instance in the remote. That was my idea when writing it.
For this type of thing, I would play on the client only. Sent a remote event to play the animation to the other clients, or not, if its not necessary for them to see the same thing as the character interacting with the machine.
In each ‘machine’ have 2 scripts, one with runcontext client and the other runcontext server and a remote event.
If the client script detects or is told the player interacted with the machine, then instantly play the animation for that client, but send a remote event to the server script saying which player interacted with the machine.
Then the server script can either send the event to all clients saying who interacted and to play the animation, and each client script can detect if their local player matches the id, and if not, play a local animation. Or, you can from the server script, just send the events to all players who are NOT of the id of the player who interacted with the machine.
The thing is that I don’t want to create different remotes for each replication type. For example, I dont want to have one remote that might dispense the item while a different one closes the display door. It just creates to many remotes to handle and might be complex in the future.
I wasn’t implying that. My suggestion was to have a single RemoteEvent for when a purchase is made to update the displayed item in the vending machine.
-- Server
-- In the section of code that would register a purchase
if purchaseMade then
local storedItem, price = getNewItem() -- However you generate a new item for the machine
vendingMachineRemote:FireAllClients(vendingMachine, "purchase", storedItem, price)
end
-- Client
-- Will animate whatever the vending machine requires to be animated through a single remote
vendingMachineRemote.OnClientEvent:Connect(function(vendingMachine, action, storedItem, price)
if action == "purchase" then
local animator = vendingMachine.Animator
playAnimation(animator, m_furnitureAnimations.vendingMachine.shake) -- Some function that plays your animation
task.wait(0.5)
stopAnimation(animator, m_furnitureAnimations.vendingMachine.shake, 0.25)
playAnimation(animator, m_furnitureAnimations.vendingMachine.dispenseItem)
task.wait(0.5)
playAnimation(animator, m_furnitureAnimations.vendingMachine.close) -- Whatever you call it..
task.wait(0.5)
setVendingMachineItem(vendingMachine, storedItem, price) -- Some function that swaps the model and price tag
playAnimation(animator, m_furnitureAnimations.vendingMachine.open)
end
end)
My principle is that you let the client do all of the animation/visual/rendering hard work.