Gun sway issue, Its laggy / Jittery even with BindToRenderStep

Hey guys, I ran into an issue after finally getting the gun sway to work.I’m using springs for it looks quite messed up. I played around with dampening and K for a little and it didn’t really help. Its seems to be laggy and jittery.

Here are some videos:

  • This is what it looks like WITHOUT springs

  • This is what it looks like WITH the springs. Its super glitchy.

Here is my spring script:

local Spring = {};

function Spring.New(Position, Velocity, Target, K, Damper)
	local NewSpring = {
		Position = Position;
		Velocity = Velocity;
		Target = Target;
		K =  0.3;
		Damper =  1;
	};
	
	function NewSpring:Update(Target, K, Damper)
		NewSpring.Target = Target or NewSpring.Target;
		NewSpring.K = K or NewSpring.K;
		NewSpring.Damper = Damper or NewSpring.Damper;
		
		local Distance = (NewSpring.Target - NewSpring.Position);
		local Force = (Distance * NewSpring.K);
		NewSpring.Velocity = ((NewSpring.Velocity * (1 - NewSpring.Damper)) + Force);
		NewSpring.Position = (NewSpring.Position + NewSpring.Velocity);
		
		return NewSpring.Position;
	end;
	
	return NewSpring;
end;

return Spring;

Here is my local script running, making everything work together:

--//Arms Setup
local cam = workspace.CurrentCamera
local arms = game.ReplicatedStorage.Assets.Viewmodel:Clone()
local root = player.Character.HumanoidRootPart
arms.Parent = cam
--//Welding

local function onDied()
	arms.Parent = nil
end

local SpringModule = require(script.Spring)
local spring = SpringModule.New(arms.HumanoidRootPart.CFrame.p, Vector3.new(),((cam.CFrame + (cam.CFrame.RightVector * -0)) - (cam.CFrame.UpVector / 1)) + (cam.CFrame.LookVector * -0.8), 0.1, .5)

local function onUpdate()
	
---//These two have nothing to do with the question
   events.tiltAt:FireServer(math.asin(cam.CFrame.LookVector.y)); --//Makes Server tilt with weapon
   root.CFrame = CFrame.new(root.CFrame.p,root.CFrame.p+Vector3.new(cam.CFrame.lookVector.X,0,cam.CFrame.lookVector.Z)) --//Make torso turn faster

--//Is here the issue?
	local springPosition = ((cam.CFrame.p + (cam.CFrame.RightVector * -0)) - (cam.CFrame.UpVector / 1)) + (cam.CFrame.LookVector * -0.8)
	local update = spring:Update(springPosition)
	local springCFrame = CFrame.new(update) * CFrame.Angles(cam.CFrame:ToEulerAnglesXYZ())

	arms.HumanoidRootPart.CFrame = springCFrame


   --arms.HumanoidRootPart.CFrame = ((cam.CFrame + (cam.CFrame.RightVector * -0)) - (cam.CFrame.UpVector / 1)) + (cam.CFrame.LookVector * -0.8)

end


local hum = character.Humanoid	
hum.Died:Connect(onDied)
game:GetService("RunService"):BindToRenderStep("Camera Functions", Enum.RenderPriority.Camera.Value, onUpdate)

Any ideas on how to smoothen it out? Thanks for reading :slight_smile:

3 Likes

Try rendering before the camera update, like this:

game:GetService("RunService"):BindToRenderStep("Camera Functions", Enum.RenderPriority.Camera.Value - 1, onUpdate)

I didn’t read through everything; are you sure that the server isn’t touching anything that could affect positions / forces? They should only be manipulated client side.

2 Likes

Everything is client side, in a localscript

I took the time to read through it.

While the Spring code you have may work in some other places, in Roblox and for this specific use case it would be better to include a delta time variable, dt, to scale the velocity. Velocity is distance per a second; so technically the equations work out when the updates are each considered to be uniformly one unit of time between each other and the other variables account for the ratio between seconds and the unit of time, but I wouldn’t recommend it.

Thinking more about it, I don’t think the behavior you want is actually a linear spring on the position of the arms. Correct me if I am wrong, but I think what you are looking for is a spring on the angle of the arms, a Torsion Spring on the axis of rotation from one look vector to another (presumably always the Y axis).

suggestion

This

local springPosition = ((cam.CFrame.p + (cam.CFrame.RightVector * -0)) - (cam.CFrame.UpVector / 1)) + (cam.CFrame.LookVector * -0.8)

Is this

local springPosition = cam.CFrame * Vector3.new(0, -1, -0.8)

And this

local springCFrame = CFrame.new(update) * CFrame.Angles(cam.CFrame:ToEulerAnglesXYZ())

is this

local springCFrame = cam.CFrame - cam.CFrame.p + update
2 Likes

First things first, you should most definitely not be firing a remoteEvent every frame…

3 Likes

about the tiltAt remote
yes, i see some of it is picked up from egomoose’s fps article, in which in the end he explains that sending an event every frame is really gonna mess with the network, because you’re sending data around every 1/60 seconds, he used a while loop to send in the event every 0.1 or 0.2 seconds and then have the server interpolate between those to still look smooth.

about the jitter on sway
not really sure, but im guessing its about calculating the position of the viewmodel, my game had similar jitter until i completely reworked the way it calculates sway, not with cameras look vector, but rather the mouse look delta (how much the mouse has moved)

1 Like

I’d probably go a step further and just tell each client about each update and have them interpolate it, rather than the server.

that could work too, but server would still handle it well and replicate it to the rest of the server, expecially with body forces because they are smooth. which do you think would be efficient? might wanna try that with my game

Well i think this is what im looking for. Your suggestions didn’t work after i replaced it with the original. Although lerp does help, its too slow with updating and the arms drag on for too long.

How would i get the server to interpolate between the data? I have absolutely no idea how to do that. A bool value maybe?

This is what i tried:

local function tiltAt()
events.tiltAt:FireServer(math.asin(cam.CFrame.LookVector.y));	
wait(0.2)
end
game:GetService("RunService").RenderStepped:Connect(tiltAt)

first of all, you shouldn’t bind that to renderstep, if you are sending in data every 0.2 seconds its pretty useless to fire this function every frame, instead use a while true loop, and wait(0.2) for its delay time.

this is egomoose’s article, on the very bottom you can see how he goes with it, he creates an invisible tilt part inside the player which receives the tiltat information and rotates the torso smoothly, using bodygyro.

I think this behavior is more like what you are looking for:

This is what a torsion spring looks like. Walking doesn’t cause the arms to lag behind. However, I still see the roughness that you were talking about. From what I can tell, immediately setting the arms to where they should be has no stuttering because it doesn’t get the artifacts from imprecise mouse movement. Since frames are so short and mouse movements so small, a single pixel in mouse movement is a huge percentage difference from frame to frame. To fix this I would decrease the amount the camera moves per a pixel of mouse movement, thus allowing higher resolution camera changes. Using the last couple frames to calculate the target instead of a single frame would also help.

Here is the script I used:

local RunService = game:GetService 'RunService'
local acos = math.acos
local cam = workspace.CurrentCamera

-- This is the arm seen in the video
local p = Instance.new 'Part'
p.Anchored = true
p.Size = Vector3.new(0.5, 0.5, 5)
p.CanCollide = false
p.TopSurface = Enum.SurfaceType.Smooth
p.Parent = workspace

-- Projects a vector onto the XZ plane
local function onXZ(v)
	return Vector3.new(v.X, 0, v.Z).Unit
end

-- Simulation constants
local offset = Vector3.new(0, 1, 0)
local expectedFPS = 60
local k = 0.4 * expectedFPS
local d = 0.3 * expectedFPS
local up = Vector3.new(0, 1, 0)

-- Simulation variables
local rotY = 0
local look = onXZ(cam.CFrame.LookVector)
local dT = 0
while true do
	local trg = onXZ(cam.CFrame.LookVector)
	rotY = dT * d * rotY + dT * k * look:Cross(trg).Y * acos(look:Dot(trg))
	look = CFrame.fromOrientation(0, rotY, 0) * look
	p.CFrame = CFrame.fromMatrix(cam.CFrame.p - offset, up:Cross(look), up, look)
	dT = RunService.RenderStepped:Wait()
end
3 Likes

I see, ill implement it after i fix the springs

The stutter is still pretty bad. I dont mind the fact that the hands move forwards its just hte stutter that ruins everything. I will implement parts of your code though.

I don’t know how much this will help, but I also have had some trouble with BindToRenderStepped. From my tests, even when BindToRenderStepped has its priority set to the lowest possible value, its speed is much slower than the speed of using RunService.RenderStepped. Perhaps use the RenderStepped event instead?

I think the issue is that you are relying on the server to do the rotation. You should do all of this on the client. The movement will replicate to the server by default as long as you aren’t using locally cloned body parts.

Edit: Character movement (in player’s character’s humanoids) automatically replicates to the server. This is why server side movement logic checks are so imperative for anti exploits.

No, this was on the client. Besides, you can’t bind to render stepped on the server, the server doesn’t render and attempts to do so throw an error.

I thought he was sending a remote event to set the CFrame on the server, looks like that is for tilt? I’m not sure what that is for.

I assume the remote event you mentioned is from the original post in the second script at this line?

events.tiltAt:FireServer(math.asin(cam.CFrame.LookVector.y));

You must have seen the line above and comment beside:

---//These two have nothing to do with the question
   ... --//Makes Server tilt with weapon

The value being sent is the sine of the y axis (up) of the lookvector of the camera. Another way to think of this value is the angle of elevation of the lookvector. The developer is sending the pitch of the gun to the server. It cannot be used to calculate yaw. This may be used for checks or something else, it doesn’t really matter as mentioned in the comments. And as we see later:

	arms.HumanoidRootPart.CFrame = springCFrame

The humanoid root part is being CFramed in the local script. In addition, the script and video I posted on Aug 10th was completely run on the client, without remote events.

All of that being said, server sided movement is not bad. When you understand and know how to use it, server side movement is a powerful tool.

Possible Solution: Set the render priority to 200 or Enum.RenderPriority.Camera.Value, you are most likely rendering it before the camera itself renders, resulting in the jitteriness. I had the same problem.