Soccer Ball Latency

Hello!

In my game, Mini Cup, there are around 16 players in game. They are divided into 4 teams of 4 and play soccer against each other in turns. And in like any soccer (football) game, they try to kick the ball into the oppositions net.

However it is hard to play with much precision because the latency makes it so that the opposing players look far away from the ball when they kick it, when in reality, on their end of the game, on their screen, they are already touching the ball. But to you, you are actually closer to the ball and they are still miles away.

On the other hand, when you kick the ball, it takes a while before the server actually received the input and performs the kick.

If you play one match in this game you will see the issue yourself:

So now my issue is how do I fix this?
I’ve been trying for months but have come to no solution.
(I’ve changed velocity types, added a cooldown, set network owner to nil and everything I’ve heard of)

This is the current server script which is parented to the ball and is replicated from ServerStorage to the Workspace as needed.

local Players = game:GetService("Players") -- Get the Players service
local Debris = game:GetService("Debris") -- Get the Debris service

local Ball = script.Parent -- Get the ball object
local KickSound = Ball:WaitForChild("Kick") -- Get the kick sound
local bounce = false -- Set a flag to prevent multiple touches from being registered
local gameModule = require(game.ServerStorage.Modules.GameModule)

Ball.Touched:Connect(function(t) -- Connect to the Touched event on the ball
	if t.Name == "PlayerHitBox" then
	local character = t.Parent -- Get the character that touched the ball
	if not character then return end -- If there is no character, return

	local player = Players:GetPlayerFromCharacter(character) -- Get the player associated with the character
	local humanoid = character:FindFirstChildOfClass("Humanoid") -- Get the humanoid of the character
	local rootPart = character:FindFirstChild("HumanoidRootPart") -- Get the root part of the character
	if not player or not humanoid or humanoid.Health <= 0 or not rootPart then return end -- If any of these are missing or the character is not alive, return

	local ballVelocity = Ball.AssemblyLinearVelocity -- Check if the ball already has a velocity

	if bounce == false and not player:GetAttribute("kicked") then -- If the flag is set and no other touches have been registered
		player:SetAttribute("kicked", true)
		print("Bounce is true")
		bounce = true -- Set the bounce flag to prevent further touches from being registered

		local direction = rootPart.CFrame.LookVector -- Get the direction the character is facing
		if direction.Magnitude < 0.001 then return end -- If the direction is very small, return

		Ball:SetNetworkOwner()
		
	
		local kickForce = humanoid.WalkSpeed -- Get the character's walk speed
		
		Ball.AssemblyLinearVelocity = (direction.Unit * kickForce * 1.95) + Vector3.new(0, kickForce * 1.15, 0) -- Set the velocity of the ball based on the direction and walk speed of the character
		
		KickSound:Play() -- Play the kick sound

		-- Set the attributes for the last and second-to-last player to touch the ball
		if Ball:GetAttribute('LastTouch') ~= nil then
			if Ball:GetAttribute('LastTouch') ~= player.Name then
				local previousTouch = Ball:GetAttribute('LastTouch')
				Ball:SetAttribute('LastTouch', player.Name)
				Ball:SetAttribute('SecondTouch', previousTouch)
			else
				-- Do nothing if the current player is the same as the last player to touch the ball
			end
		else
			Ball:SetAttribute('LastTouch', player.Name)
		end
		print("Sending For KickTimer")
		gameModule.kickTimer(player)
		print("Kicktimer Returned")
		task.wait(0.6) -- Wait for 1/2 second
		print("Debounce is false")
		bounce = false -- Reset the bounce flag to allow for future touches to be registered

		-- Increment the touch count for the player's team in the "Standings" folder
		local teamName = player:GetAttribute('TeamName')
		if game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName) ~= nil and game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName):GetAttribute("Touches") ~= nil then
			game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName):SetAttribute('Touches', game.ServerStorage.GameFolder.Standings:FindFirstChild(teamName):GetAttribute('Touches') + 1)
		end
		end
	end
end)

At this point I think it requires an advance solution which I do not have the knowledge to implement. Any ideas or answers would help greatly!

I am also willing to hire an expert who can solve (or at least greatly remedy) the issue as a one time commission.

Basically you have to change the ball simulation. Everything must be done locally and using mathematics.
So with the ball initially standing still, one player kicks it. For that player you should start to simulate the movement of the ball, the same as you would do with parabolic movement in physics. At the same time, at the instant of the kick, you must send all the data necessary to perform the simulation to the other players/clients. You do the same every time a player kicks the ball.

Here is something very important. When you send the data to the other players, each player must receive two extra pieces of data, the ping of the player who made the kick and the ping of the current player. When the other players start the simulation, they must do so in a time equal to the sum of these two pings to be synchronised.
Measuring the ping is probably also a problem since roblox doesn’t give us that data and we have to calculate it ourselves.

By the way, this method can generate artefacts, but the result is quite good.

5 Likes

I dont think Im smart enough to implement something that advanced :sob:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.