Inconsistency With Pathfinding Between the Server and Client

Hello, all.

I’ve been taking a shot at making an RTS style game in Roblox. My most recent roadblock has been managing the movement of units across pathfinding nodes.

I’ve attached a video that attempts to demonstrate the current issues I have. The two biggest things that bother me are:

  1. At times the tank will just stop moving on the server side, while on the client side the tank keeps moving. Sometimes the tank will correct itself after a few seconds, but most of the time the tank remains still while the locally-rendered tank goes off into nowhere; and

  2. There appears to be an error that occurs while indexing the list of waypoints. This is most likely caused by giving the unit a new “Move” command before it has reached its original goal. This was what I was originally trying to fix before the aforementioned issue appeared, and I decided to take this up with the forum to make sure my attempt at an RTS isn’t doomed.

https://youtu.be/jNd-largI5M

I am confident that both these issues are being caused by a fault in my pathfinding code: specifically the way I handle waypoints. Here is all the code that deals with waypoints:

	local GoTo = Function.new("GoTo", function(location, target, usePathfinder)
--	print("GoTo") --DEBUG
		--Stop the unit's current movement (if any)
		--unitFolder:WaitForChild("Halt"):Invoke()
		--Get the unit's position, as well as the objects used to move and turn the unit
		local DISTANCE_THRESHOLD = Model.Value:GetExtentsSize().Y / 2 + 0.2
		local unitPosition = Model.Value.PrimaryPart.Position
		local velocityObject = Model.Value.BodyKit.UnitPart.BodyVelocity
		local gyroObject = Model.Value.BodyKit.UnitPart.BodyGyro
		
		if usePathfinder == true then
			pathSuccess, waypoints = Pathfinder:CreatePath(unitPosition, location)
--			print(pathSuccess, waypoints) --DEBUG
--			print(#waypoints) --DEBUG
			NodeDebugger:ClearAllNodes() --DEBUG
			if pathSuccess == true and #waypoints > 0 then
				for waypoint = 1, #waypoints do
					NodeDebugger:AddNode(waypoints[waypoint]) --DEBUG
				end
				waypointIndex = 1 --We're skipping the first waypoint, as it's where the unit already is.
				--Move to first waypoint
				local distance = math.huge
				local previousDistance = math.huge
				while waypointIndex <= #waypoints and waypoints[waypointIndex] do
					NodeDebugger:HighlightNode(waypoints[waypointIndex]) --DEBUG
					velocityObject.Velocity = (waypoints[waypointIndex] - Model.Value.PrimaryPart.Position).Unit * Speed.Value
					gyroObject.CFrame = CFrame.new(Model.Value.PrimaryPart.Position, waypoints[waypointIndex])
					repeat
						--print(waypoints[waypointIndex], "	", Model.Value.PrimaryPart.Position) --DEBUG
						distance = (waypoints[waypointIndex] - Model.Value.PrimaryPart.Position).magnitude
						--print(distance - previousDistance) --DEBUG
						--if (distance - previousDistance) > 1 then
							--print("Recalibrating") --DEBUG
							velocityObject.Velocity = (waypoints[waypointIndex] - Model.Value.PrimaryPart.Position).Unit * Speed.Value
							gyroObject.CFrame = CFrame.new(Model.Value.PrimaryPart.Position, waypoints[waypointIndex])
						--end
						previousDistance = distance
						wait()
					until distance <= DISTANCE_THRESHOLD
					unitFolder:WaitForChild("WaypointReached"):Fire()
					waypointIndex = waypointIndex + 1
				end
				--Stop the unit
				--velocityObject.Velocity = Vector3.new()
				unitFolder.Halt:Invoke()
			else
				--Here we will send a message to the commander, informing him / her of our failure to find a path.
			end	
			--Stop the unit
			--velocityObject.Velocity = Vector3.new()
		else
			--This is an easy case: we just move the unit in a straight line to the location / target.
			velocityObject.Velocity = (location - Model.Value.PrimaryPart.Position).Unit * Speed.Value
			gyroObject.CFrame = CFrame.new(Model.Value.PrimaryPart.Position, location)
		end
	end, unitFolder)
	--Halt
	local Halt = Function.new("Halt", function()
		--Clear the pathfinding data
		waypoints = {}
		waypointIndex = 0
		--Stop the unit
		--print("Stopping unit") --DEBUG
		local velocityObject = Model.Value.BodyKit.UnitPart.BodyVelocity
		velocityObject.Velocity = Vector3.new()
	end, unitFolder)

Finally, allow me to make some things very clear:

  • I am using a custom Pathfinding module in ServerStorage. All the pathfinding in-game is handled by the server;

  • You may have some questions as to why I use a “Function” class instead of just creating a normal function. Don’t worry about that. It’s not going to change, and it’s not relevant to the issue at hand;

  • There is a part of the pathfinding function that handles a case where the caller just wants to move in a straight line towards the goal. That section is incomplete. What’s being used in this video is the “usePathfinder == true” scenario.

  • Yes, I am aware that the tank shoots itself into the sky when it bumps into a wall. I am also aware that the tank drives upside-down at one point in the video. Believe it or not, these have no effect on the unit’s pathfinding. The code only examines the unit’s distance from the node on the X and Z axes, so however high the hover tank flies or how strange it is oriented has no real effect on waypoints (and I’m going to take care of it);

  • I don’t want feedback on the lack of textures. I don’t want feedback on the tanks’ design. I don’t want feedback on the UI. I just want assistance on the issues I’ve already mentioned. If you have no thoughts on what could be going on, I politely ask that you move on. I’m not trying to be rude: I just don’t like cluttered threads.

Any help with this issue would be greatly appreciated.

are you making a pathfinding service both on client and server?

All pathfinding is handled on the server. The Pathfinding module is in ServerStorage.

I’ll try to be as helpful as I can here.

Have you asserted the following;

  • Are you sure you compile all the way points correctly? (In the array)
  • When you are setting the velocity does it get stuck at one point? (Does the repeat until loop not break)
  • I BELIEVE that it is ( (Vector3) From - (Vector3) To).Magnitude not ( (Vector3) To - (Vector3) From).Magnitude

Also I love the function creation + the actual function it’s pretty neato.