Cars Predictive Pathing

We can, but it probably won’t look quite right;

RunService.Heartbeat:Connect(function()
    for _, Car in workspace.Cars:GetChildren() do
        if Car.NewPosition.Value ~= Car.PrimaryPart.Position then
            local PlayerPing = Car.Player.Value:GetNetworkPing() + LocalPlayer:GetNetworkPing()
            Car:PivotTo(Car:GetPivot() + (Car.PrimaryPart.AssemblyLinearVelocity * PlayerPing))
            Car.NewPosition = Car.PrimaryPart.Position
        end
    end
end)

(basically pseudocode, I know it won’t work because :GetNetworkPing doesn’t function for other clients - only local player, but if you set everything up then what was described should happen, players should be moved forward by their velocity and ping to be more representative of where they are on their screen).

1 Like

That’s unfortunate, racing games especially need something like this because I’ve just created race positions for my game. I calculate them on the server because if it was calculated on the client people would be confused when it says their first but they finish in second or third. But because it’s calculated on the server, it could be telling them they’re in second when it looks like they’re in first, I’ll try creating my own system but I’m not really good with this kind of stuff. (physics)

Also, @Judgy_Oreo I looked at your code for an example of how to do it and I found this post which lets you see everyone’s pings from the server. I was wondering if I’d need to change anything else or if it would pretty much work with just changing the bit where you check people’s pings?

Edit: I just realised that we need to see others players’ pings from every client separately, I could send everyone’s pings through a remote event though…

Hey there, another developer here struggling with the same issue on collisions in racing games. The way it is now, it is impossible to create a realistic car racing game on Roblox. Collisions without vision lag is necessary to provide an immersive experience.

1 Like

I wonder if this was a problem pre-PGS… Probably.
It’s interesting that this has not been addressed yet in the physics system. Currently, the only solution is to set the network ownership to the server, but input lag will make the gameplay horrible.

Well I don’t see why you can’t account for each of the player’s ping now that workspace:GetServerTimeNow is available. :
Meaning you would have to move the players who are a bit behind the certain intervals accounting how far the difference is between their time and server’s time.

At this point, it’s down to the developer to develop some form of system to “fix” this. That said, it’s an uphill battle.

You’d be better off supporting my other thread, here. Staff responded pointing out that UDP is already how the physics are ran, so forget that part, but there is still currently no good way to get ping in real time to propogate to the clients & make adjustments to where cars should be.

You’re on a right track & I took a stab at it using your code idea with some extra additives to make your psuedocode into proper code.

When reviewing the footage, I measured the difference between me reaching the line and them reaching the line, I got about 6 frames [60 fps]. That’s about .1 of a second but the margin of victory was recorded at about 1/3 of that.

So still not perfect, but it’s closer one could suppose. My thing is, I’m still just not so certain one can get a reliable enough ping of all players, to be readable locally, to output accurate results.

5 Likes

Reviving this topic here…
i dont know how but the guys at Avenue Vance Empire claim that they successfully solved this issue and could do real time collision physics on A-Chassis, no lag at all on their pre release ION Apex Racing, i wanted to know how they did that and if its really true, because the beta game costs 500 robux and i dont have the money to buy the game to test

1 Like

I’ve seen it in the videos and I can hazard a guess at how it’s done but it still has some drawbacks considering this isn’t actually great ways to obtain ping still. (GetPing is still not a perfect function for this use case)

2 Likes

Something I noticed with this lag is that the physics system seems to “tween” the last seen position of the vehicle (sent by that driver’s client) to the next seen position, which when owned by the network of the player can cause the car to suddenly stop if not having a stable connection with the client, or if the server cannot handle too much physics items.

A built in system for predictive physics would be very beneficial for competitive racing, especially those with collisions and destruction physics. George in the forum explains very well how said predicting system would work, and I think it would be quite effective for vehicles, but will present some rubber-banding issues.

However, I noticed a phenomenon when dealing with this; The velocity of the car would still be present, and you could touch the car with an object, and that object would inherit the velocity. I believe that the physics system should allow an option for a client to simulate another client’s vehicle’s movement, without affecting the other player’s gameplay. This should at the least prevent sudden stopping, but will make turning rubber band as presented above.

Another change that might be beneficial for game development would be open-source systems. While this may be proprietary information ROBLOX would want to keep, it will also allow developers to further expand the capabilities of their said system, pushing it to bounds not seen before. But that’s a topic for another forum.

I don’t think there will ever be a solution for server-client connection delay, nor will predictive physics, but I think rendering physics for clients (Without effecting eachother) similarly to a peer-to-peer connection, paired together with server detected inputs will make collisions sync a lot better and make physics-dependant games a lot more fun in general.

Hopefully the future’s hardware and software will allow us to make a better solution to this problem that plagues competitive games, but for now a solution like this would be a great addition for the ROBLOX engine.

4 Likes

that happens because the replication between the server and client has a delay.

Yes, we know this. It’s why we need predictive pathing to get around this rather than reactive updates.

1 Like

How would that even work? how will the system know if i will turn left or right?

1 Like

Here’s a newer thread, about more or less the same thing.

You’d be able to achieve “predictive” pathing with this. The issue is this isn’t a one size fits all thing and I imagine the current issue is due to interpolation rather than extrapolation for physics.
If you want to do it in-engine as of today, you’d need to make your own custom physics simulation, like Chickynoid.

1 Like

tbh there is still vision lag present, and phasing through cars, it’s not great, so tank chassis and simular ones are our only bet

1 Like

Is it possible that this issue is somewhat related to what this person has attempted to fix in this video? He explained the issue in the video description with the following:

Roblox’s interpolation buffer and update rate is a pain to deal with in FPS games, This is why in TC2 and Arsenal you get front stabbed all the time, its not the developers fault.

Why is this the case? Mobile lol, Kids are going to be on the shittiest connections with their router being 2 miles away or using some slow mobile data connection that has a bunch of packet loss and jitter so Roblox just added a HUGE buffer and set a low update rate as to not absolutely drain data caps.

3 Likes

i managed to make a client-side predictive system for kinematics of vehicles
although it’s not for cars, it’s for aircraft, conceptually they should be the same, if anything, aircraft demand higher precision since they fly faster than any car on land

how it works:
the cosmetic plane model that exists in the workspace on clients does not exist on the server
each client controls their own plane, and sends updates to the server regarding the plane’s kinematics (CFrame, linear velocity, linear acceleration, and angular velocity) to the server through an unreliable remote event, 15 times a second
the server receives those values and store them. since this information is inaccurate as considerable time has elapsed since it was sent, so if the server wants to know a more precise approximation of the aircraft’s CFrame, it will take those outdated values along with the elapsed time to calculate (approximate) the current CFrame using basic Calculus
dPos = 1/2 * acceleration * dt^2 + velocity * dt
dRot = angularvelocity * dt
(i did not consider angular acceleration because its usually small enough, whereas linear acceleration can be huge on aircraft, for cars it usually isn’t)
note that dt is not framerate, it is the elapsed time since the kinematic information was last updated from the client

now to replicate the kinematic movement of the aircraft onto other clients, not just the client piloting it, the server sends all the stored kinematic values across all other clients, also at 15 Hz (the rate is arbitrary, you can increase or lower it depending on the strain on the network, although i wouldn’t recommend exceeding 20 Hz)
the client uses the same method to approximate the cframe of other players’ aircraft, the only difference this time is that the elapsed time dt may be much larger (~2x larger), but that’s just the nature of multiplayer games
and to achieve the illusion of smooth movement, the client replicating the plane’s movement assigns the approximated CFrame value to the plane’s physical Model instance every frame in a RunService connection.

local Kinematics = {}
Kinematics.__index = Kinematics

Kinematics.new = function(cframe: CFrame, vel: Vector3?, localAngVel: Vector3?, acc: Vector3?)
	return setmetatable({
		CFrame = cframe,
		LinearVelocity = vel or Vector3.zero,
		LinearAcceleration = acc or Vector3.zero,
		AngularVelocity = localAngVel or Vector3.zero, --LOCAL, NOT WORLDSPACE!!!
		AtTime = Timer.GetTime(), --Timer is a module that syncs time between server and client
		Instance = nil:: typeof(script.ValuesTemplate)?,
	}, Kinematics)
end

function Kinematics:GetCFrameApprox(actualCf: CFrame?, atTime: number?, linearLerp: number?, rotationalLerp: number?): CFrame & number
	local self: KinematicInfo = self
	
	local atTime = atTime or Timer.GetTime() --time of evaluation
	
	local dt = atTime - self.AtTime
	--print(dt)
	local finalPos do
		local dPos = self.LinearVelocity * dt + .5 * self.LinearAcceleration * dt^2
		local shouldBePos = self.CFrame.Position + dPos
		if actualCf then
			--a * t + b * (1 - t)
			--actualCf is just the cosmetic model's CFrame, just smoothing out movement
			local distToActual = shouldBePos - actualCf.Position
			
			shouldBePos = actualCf.Position + distToActual * (linearLerp or 0.1)
		end
		
		finalPos = shouldBePos
	end
	
	local finalRot do
		--local rx, ry, rz = self.CFrame:ToEulerAnglesYXZ()
		local dRot = self.AngularVelocity * dt
		dRot = CFrame.fromEulerAnglesYXZ(dRot.X, dRot.Y, dRot.Z)
		finalRot = self.CFrame * dRot
		
		if actualCf then
			finalRot = actualCf:Lerp(finalRot, rotationalLerp or .1).Rotation
		end
	end
	
	return CFrame.new(finalPos) * finalRot.Rotation, dt
end

snipet from the client:

self._update = RunService.Heartbeat:Connect(function(deltaTime)

	local actualCf = self.Aircraft.Model.PrimaryPart.CFrame
	local approx_cf = self.KinematicInfo:GetCFrameApprox(actualCf)

	self.Aircraft.Model.PrimaryPart.CFrame = approx_cf
	self.Aircraft.Model.PrimaryPart.AssemblyLinearVelocity = self.KinematicInfo.LinearVelocity
	self.Aircraft.Model.PrimaryPart.AssemblyAngularVelocity = self.KinematicInfo.CFrame:VectorToWorldSpace(self.KinematicInfo.AngularVelocity)
end)

basically this is ditching roblox’s default replication system and making your own
it isn’t perfect, it’s a prediction after all, but imo it works pretty well; as you can see the two clients seems to agree where and when collisions occur, even though the two planes are travelling at hundreds of studs per second

12 Likes

Have you tested this much with real players? Because notably you’re dealing with two clients ran locally in studio in your example footage. How does it hold up in live sessions since you’re not actually checking what a player’s ping is and adapting to that factor?

Yes, I have tested it many times with friends
Because Player:GetNetworkPing() always returns 0 in studio, i use a custom method (nothing complicated, I just measure it through a remotefunction), so it’s pretty consistent in studio and live servers
ping is accounted for through a “Timer” module(implementation not shown, but it basically syncs time between client and server, it doesnt have perfect accuracy but its close enough)

of course, the client can lie about their ping (and all the kinematic information), but that’s a compromise I’m willing to make to make vehicle control feel more responsive

2 Likes