LinearVelocity LineDirection Will Not Replicate Accurately?

I have a linear velocity constraint on the server moving the character in the wrong direction. It only happens after I use a dash move. In the video, I’ve already used the dash, the next time I activate the constraint it uses the wrong direction- except printing the properties shows its close to the right direction? The first time I punch moves in the wrong direction, the second time it move in the correct direction.

Image from Gyazo

The strange thing is- the properties show that the properties at the time of the incorrect direction are similar to that of the correct direction? The incorrect direction seems to be locked to an axis.

Dash Properties (Happens before the error):

Error Properties (This moves in the incorrect direction?):

Second-Attempt Properties (This is the second punch that shows correct direction):

Constraint Function:

function Constraints.LinearVelocity:Start(
	instance: BasePart, 
	timer: number?, 
	direction: Vector3, 
	force: number, 
	relativity: Enum.ActuatorRelativeTo?, 
	mode: Enum.VelocityConstraintMode?
): number
	local timestamp = os.clock()

	local humanoid = instance.Parent:FindFirstChildOfClass("Humanoid")

	if not humanoid or humanoid then

		local constraint = instance:FindFirstChild(prefix.."LinearVelocity") :: LinearVelocity
		if not constraint then return 0 end
		
		constraint.VelocityConstraintMode = mode or Enum.VelocityConstraintMode.Vector
		constraint.RelativeTo = relativity or Enum.ActuatorRelativeTo.World
		
		if direction ~= nil and force ~= nil then
			if constraint.VelocityConstraintMode == Enum.VelocityConstraintMode.Vector then
				constraint.VectorVelocity = direction.Unit * force
			elseif constraint.VelocityConstraintMode == Enum.VelocityConstraintMode.Line then
				constraint.LineDirection = direction.Unit
				constraint.LineVelocity = force
			elseif constraint.VelocityConstraintMode == Enum.VelocityConstraintMode.Plane then
				constraint.PlaneVelocity = Vector2.new(direction.Unit.X, direction.Unit.Z) * force
			end

			if constraint.VelocityConstraintMode == Enum.VelocityConstraintMode.Line then
				constraint.Attachment0.Axis = direction
			else
				constraint.Attachment0.Axis = Vector3.new(1, 0, 0)
			end
		end

		if timer ~= 0 then
			constraint.MaxForce = instance.AssemblyMass * maxForce * workspace.Gravity
			constraint.Enabled = true
			
			if game.Players:GetPlayerFromCharacter(instance.Parent) then
				print("-----------------------\nLinear Velocity Properties: ")
				print("Attachment0 Axis: ", constraint.Attachment0.Axis)
				print("VelocityConstraintMode: ", constraint.VelocityConstraintMode)
				print("RelativeTo: ", constraint.RelativeTo)
				if constraint.VelocityConstraintMode == Enum.VelocityConstraintMode.Vector then
					print("VectorVelocity: ", constraint.VectorVelocity)
				elseif constraint.VelocityConstraintMode == Enum.VelocityConstraintMode.Line then
					print("LineDirection: ", constraint.LineDirection)
					print("LineVelocity: ", constraint.LineVelocity)
				elseif constraint.VelocityConstraintMode == Enum.VelocityConstraintMode.Plane then
					print("PlaneVelocity: ", constraint.PlaneVelocity)
				end
			end
			
			Constraints.LinearVelocity.Timestamps[instance] = timestamp

			if timer ~= nil and timer > 0 then
				task.delay(timer, function()
					if Constraints.LinearVelocity.Timestamps[instance] == timestamp then 
						Constraints.LinearVelocity:Stop(instance, timestamp)
					end
				end)
			end
		end
	end

	return timestamp
end

Drag Function (Calls Constraint Function):

function Mechanics.Drag(Attacker: Model, Victims: {Model}, Distance: number)
	local averagePosition = Vector3.zero
	local roots = {Attacker.PrimaryPart}
	for i,Victim in Victims do
		averagePosition = averagePosition + Victim.PrimaryPart.Position
		table.insert(roots, Victim.PrimaryPart)
	end
	averagePosition = averagePosition / #Victims

	local dragTime, dragRatio = .2, 1--4/5

	local damagePosition = Attacker.PrimaryPart.Position

	local direction = (averagePosition - Attacker.PrimaryPart.Position).Unit
	direction = Vector3.new(direction.X, 0, direction.Z) * Distance
	
	if true and (averagePosition - damagePosition).Magnitude > (Attacker.PrimaryPart.Size.Z * 4)
		or true -- always
	then
		task.spawn(function()
			Constraints.LinearVelocity:Start(Attacker.PrimaryPart, dragTime, direction, Distance, nil, Enum.VelocityConstraintMode.Line)
		end)
	end

	for i,victim in pairs(Victims) do
		task.spawn(function()
			Constraints.LinearVelocity:Start(victim.PrimaryPart, dragTime, direction, Distance * dragRatio, nil, Enum.VelocityConstraintMode.Line)
		end)
	end
end

I think the issue may be coming from reusing the same LinearVelocity constraint for different movement types, especially because dash and drag/punch are using different reference modes.

In the dash screenshot, the constraint is using:

RelativeTo = Enum.ActuatorRelativeTo.Attachment0

But in the later punch/drag movement it’s using:

RelativeTo = Enum.ActuatorRelativeTo.World

You’re also changing:

constraint.Attachment0.Axis = direction

for line mode.

I would avoid changing Attachment0.Axis for this. If you’re using RelativeTo = World then your LineDirection should already be enough. Changing the attachment axis can make the constraint’s reference frame behave differently, especially if the same constraint was previously used with RelativeTo = Attachment0.

For a normal dash / drag / knockback, I would probably use VectorVelocity instead of LineVelocity. It is simpler and avoids the attachment-axis confusion:

local flatDirection = Vector3.new(direction.X, 0, direction.Z)

if flatDirection.Magnitude < 0.001 then
	return
end

constraint.Enabled = false
constraint.VelocityConstraintMode = Enum.VelocityConstraintMode.Vector
constraint.RelativeTo = Enum.ActuatorRelativeTo.World
constraint.VectorVelocity = flatDirection.Unit * force
constraint.MaxForce = instance.AssemblyMass * maxForce * workspace.Gravity
constraint.Enabled = true

If you still want to use Line mode, I would do it like this:

local flatDirection = Vector3.new(direction.X, 0, direction.Z)

if flatDirection.Magnitude < 0.001 then
	return
end

constraint.Enabled = false
constraint.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
constraint.RelativeTo = Enum.ActuatorRelativeTo.World
constraint.LineDirection = flatDirection.Unit
constraint.LineVelocity = force
constraint.MaxForce = instance.AssemblyMass * maxForce * workspace.Gravity
constraint.Enabled = true

And I’d remove this part:

constraint.Attachment0.Axis = direction

Another thing I noticed is this line:

if not humanoid or humanoid then

That condition is always true. It should probably just be:

if humanoid then

or removed depending on what you intended

Also in your drag code, you’re doing

direction = Vector3.new(direction.X, 0, direction.Z) * Distance

Then later you pass both direction and Distance into the constraint. Since the constraint function does direction.Unit * force, you should keep direction and force separate:

local offset = averagePosition - Attacker.PrimaryPart.Position
local direction = Vector3.new(offset.X, 0, offset.Z)

if direction.Magnitude <= 0.001 then
	return
end

direction = direction.Unit
Constraints.LinearVelocity:Start(Attacker.PrimaryPart, dragTime, direction, Distance, nil, Enum.VelocityConstraintMode.Vector)

So the main fixes I would try are

  1. Disable the constraint before changing its properties.
  2. Don’t change Attachment0.Axis for world space movement.
  3. Use VectorVelocity for dash/drag if you just want movement in a direction.
  4. Do not reuse the exact same constraint for dash and punch unless you fully reset all properties each time.
  5. If this is on a player character, also check network ownership, because server-applied physics constraints on client-owned characters can sometimes look delayed or inconsistent.

The printed properties may look correct, but if the attachment axis/reference mode was changed by the previous dash, the physics solver may still behave as if the movement is locked to the previous attachment/reference direction. That matches the “first punch wrong, second punch correct” behavior.

Thank you for the help.

Upon further investigation I noticed that line direction is being set correctly on the server but fails to replicate to the client for some unknown reason. It first shows the attachment axis, then later times show a skewed direction- its indicative of a replication problem, just not sure how.

The only time I see this happening blatantly is when switching the .RelativeTo right before setting LineDirection. Can you let me know if you experience this too?

Server:

Client:

That screenshot helps a lot. If the server shows the correct LineDirection but the client still shows 1, 0, 0 then I’d stop looking at the direction math for now. The math is probably not the main issue

This looks more like either a replication timing issue or something on the client is resetting / seeing the old constraint state.

One important thing to keep in mind is that player characters are usually simulated by the owning client. So even if you’re setting the constraint from the server, the local player’s client is the one that needs the movement immediately. If the constraint is only changed on the server, the client may briefly simulate using the old/default constraint values before the new properties arrive. That would also explain why the first punch goes in the wrong direction but the second one works after the values have caught up

For something as short as a dash / drag, I’d probably set the constraint on the client too, at least for the player who owns that character. The server can still validate the move and replicate it but the local client should apply the movement instantly.

So the flow would be something like

-- server
Remote:FireClient(player, direction, force, timer)
-- server can also apply/validate if needed

Then on the client

constraint.Enabled = false
constraint.RelativeTo = Enum.ActuatorRelativeTo.World
constraint.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
constraint.LineDirection = direction.Unit
constraint.LineVelocity = force
constraint.Enabled = true

I’d also test if another script is resetting it. Put a temporary listener on the client

constraint:GetPropertyChangedSignal("LineDirection"):Connect(function()
	print("Client LineDirection changed to:", constraint.LineDirection)
end)

constraint:GetPropertyChangedSignal("Enabled"):Connect(function()
	print("Client Enabled changed to:", constraint.Enabled)
end)

If you see it change to the correct direction and then back to 1, 0, 0 then some local/client code or preset is resetting it. If it never changes at all, then the server change is just not reaching the client in time for this movement.

Also if you’re enabling the constraint immediately after changing the properties, roblox may replicate those changes across frames. So the client might receive Enabled = true while still having the old LineDirection. For short movement effects, that one-frame delay is enough to make it move the wrong way.

A small test would be

constraint.Enabled = false
constraint.LineDirection = direction.Unit
constraint.LineVelocity = force

task.wait()

constraint.Enabled = true

I wouldnt use that as the final solution for combat movement but it can confirm if this is a replication/order issue.

So yeah, based on the server/client screenshots, I dont think this is really your direction calculation anymore. It looks like the client is using an old/default constraint value, probably because the character is client-owned and the server constraint changes arent arriving atomically/fast enough for a short dash

I just tried that, strange thing is that lengthening the dash has the same effect, and the property signal event is firing once yielding the errored result, and applying a wait before enabling or even after changing each property does not fix it. Starting to wonder if I should move this over to engine bugs.

Your idea about using task.wait() was correct, although I had to place it after setting both VelocityConstraintMode and RelativeTo. Since those enum properties change property visibility of LineDirection it makes sense that changing line direction directly after them causes replication delay.

Thanks for the help! At least we know what the issue is now.

1 Like

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