How can I optimize 2d character replication

I created a 2D sprite character system for a top-down 2D game I’m working on. Everything works as expected and the character system replicates across clients correctly. But I’m concerned that it might cause too much lag on the server, especially if theres multiple players.

heartbeat = game:GetService("RunService").Heartbeat:Connect(function()
		local root = char:FindFirstChild("HumanoidRootPart")
		if root then
			updateSpritePosition(char, CharacterModel, root)
		end
	end)

Inside the movement script, there’s a function that uses a heartbeat connection to continuously update the sprite. This works well for a small number of players, but because the updateSpritePosition function involves several calculations, it could lead to performance issues as the player count increases.

Here is the updateSpritePosition function:

	if root then
		-- Update the position of the character model to follow the root part's X and Z position
		CharacterModel.Position = Vector3.new(root.Position.X, 0.003, root.Position.Z)

		-- Get the direction the player is facing
		local facingDirection = root.CFrame.LookVector
		local angle = math.deg(math.atan2(facingDirection.X, facingDirection.Z))
		local directions = {"Right", "UpRight", "Up", "UpLeft", "Left", "DownLeft", "Down", "DownRight"}

		-- Normalize the angle to fall between 0 and 360 degrees
		angle = (angle + 360) % 360

		-- Find the index of the direction based on the angle
		local directionIndex = math.floor((angle + 22.5) / 45) % 8 + 1
		local direction = directions[directionIndex]

		-- Update the X and Y indices based on the direction
		local XIndex = SpriteData.XAxis.DirectionXIndex[direction]
		local YIndex = SpriteData.YAxis.MovementFrameYs[1]

		-- Calculate the X and Y offsets for the sprite sheet
		local spriteXOffset = XIndex * SpriteData.XAxis.XMultiplier
		local spriteYOffset = YIndex

		local velocity = root.Velocity
		local velocityMagnitude = velocity.Magnitude

		-- Check if the player is moving or idle based on velocity magnitude. Also checks if the character model still exists to prevent errors
		if velocityMagnitude > movementThreshold and CharacterModel then
			if CharacterModel:FindFirstChild("SurfaceGui") then
				local currentTime = tick()
				local frameIndex = math.floor(currentTime * 6) % 2 + 1 
				local spriteYOffset = SpriteData.YAxis.MovementFrameYs[frameIndex]
				local spriteXOffset = SpriteData.XAxis.DirectionXIndex[direction] * SpriteData.XAxis.XMultiplier

				CharacterModel.SurfaceGui.ImageLabel.ImageRectOffset = Vector2.new(spriteXOffset, spriteYOffset)
			end
		else
			if CharacterModel:FindFirstChild("SurfaceGui") then
				local spriteXOffset = SpriteData.XAxis.DirectionXIndex[direction] * SpriteData.XAxis.XMultiplier
				local spriteYOffset = SpriteData.YAxis.IdleFrameY

				CharacterModel.SurfaceGui.ImageLabel.ImageRectOffset = Vector2.new(spriteXOffset, spriteYOffset)
			end
		end

	else
		if game:GetService("RunService"):IsClient() then
			heartbeat:Disconnect() -- Disconnect the heartbeat connection if no root found
		end
		return
	end
end

I tried making other player’s movement and sprite animations run on the client using remote events, but everything I’ve tried causes the entire character replications to break.

Have you tried using networking modules that would make things faster? Besides this, nothing much you can do tbf. Although, I’ve seen other 2D games and they seem to work fine even with other plrs. First thing first you might want not to update the sprite every heartbeat, but more like every new input. Eventually do idle animations and such on every client, so you check if an input is being sent over, if yes then the client sees what the server sends, else apply a default anim or whatever. Get me?

Its hard to tell exactly what effect you are going for without a video but here is how I’d approach the problem:

Rather than using math to calculate movement each frame, just add a rigid constraint to the RootAttachment and the SurfaceGuiPart. Adjust the attachments as needed and run the orientation math on the client. It would let you minimize the amount of CFrame calculations done each frame (only adjusting orientation rather than both position and orientation)

This will let you have large servers and keep performance high even for low end devices

Good luck, this looks like an awesome project!