FPS unlock update breaking rail grinding

Hello! The release of Roblox’s new FPS unlocking update has created a new bug in my rail-grinding system. Because it’s binded to the client’s framerate, some players will go faster or slower along the rail depending on how high or low their framerate is. Note: This script isn’t mine, I forked it (no offense to the original scripter but it’s documented quite badly) so I have no idea where to put deltaTime to fix the problem.

Below is the script.

Script
--[[

↢---- 【 Axis Code - Open Source 】 ----↣

Location:⟹ StarterPlayer〵StarterCharacterScripts〵RailGrinding․client․lua
Purpose:⟹ Rail Grinding
Author:⟹ Axis \ AxisTab
Fork: Robotstics

Instructions:⟹ 〖

1:⇶ Place The Script In StarterCharacterScripts
2:⇶ Make A Part In Workspace, Name it Rail
3:⇶ Change The Id Of The RailGrind Animation To Your Own
4:⇶ Your Done, Have Fun!

〗

Comment:⟹ Credit is not required but it is appriciated, also do not claim as your own please! :)

]]--

-- ⤹ Services ⤸ --
local run_service = game:GetService("RunService");
local user_input_service = game:GetService("UserInputService");
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- ⤹ Configuartion ⤸ --
local config = {
	-- ⤹ Simple Configuration ⤸ --
	speed_cap = math.huge, --⤾ How Fast Can He Go On The Rail? (Adjust To Fit Your Needs)
	rail_accel = 1.2, --⤾ How Much Do We Accelerate Going Down?
	rail_decel = 0.8, --⤾ How Much Do We Slow Down When Goin Up?
	rail_friction = 0.002, --⤾ How Much We Slow Down Over Time
	turn_rail = false, --⤾ Do We Change Direction When Moving Backwards?
	animation = ReplicatedStorage.Assets.Animations["Grind Tricks"]:WaitForChild("FootGrind"), --⤾ What Animation Plays When We're On A Rail?
	attachment_offset = CFrame.new(0, -2.5, 0), --⤾ Offset For The Rail Particle
	attachment_angle = CFrame.Angles(0, 0, 0), --⤾ Offset Rotation For The Rail Particle
	
	-- ⤹ Advanced ⤸ --
	debounce = 10, --⤾ How Long To Wait Before You Can Get On After Leaving A Rail?
	hip_offset = 3.5, --⤾ Change This For Custom Characters (How Much He Is Above Or Below The Rail)
	turn_spd = 0.05, --⤾ What Is Our Speed Set To When Changing From Forward To Backwards?
	rbx_amplif = 64, --⤾ Used For Velocity Conversion
	min_spd = 0.75, --⤾ Minimum Speed We Can Go? (-1 = None)
	anim_priority = Enum.AnimationPriority.Action --⤾ Priority Of The Animation
};


-- Module -- 
local MiscFunctions = require(ReplicatedStorage.Modules.MiscFunctions)

-- ⤹ Variables ⤸ --
local character = script.Parent;
local humanoid = character:WaitForChild("Humanoid");
local animator = humanoid:WaitForChild("Animator")
local HumanoidRootPart = character:WaitForChild("HumanoidRootPart");

local rail_particle = Instance.new("Attachment");
rail_particle.Parent = HumanoidRootPart;
rail_particle.CFrame = config.attachment_offset * config.attachment_angle;

local load_animation = animator:LoadAnimation(config.animation);
load_animation.Priority = config.anim_priority;

local contact_sound = script:WaitForChild("ContactSound");
local rail_sound = script:WaitForChild("LoopSound");

local GrindBindable = ReplicatedStorage.Remotes:WaitForChild("GrindBindable")

-- ⤹ Attach Particle ⤸ --
local particles = {}
for _, particle in pairs(script:WaitForChild("RailParticles"):GetChildren()) do
	particle.Parent = rail_particle;
	table.insert(particles, particle);
end

-- ⤹ Rail Specific ⤸ --
local rail = nil;
local rail_debounce = 0;
local rail_dir = 0;
local rail_origin = 0;
local rail_ang = CFrame.Angles(0, 0, 0);
local rail_speed = 0
local on_rail = false;
local cur_pos = 0;
local event_fire_attempts = 0 

-- ⤹ Parameters ⤸ --
local params = RaycastParams.new();
params.FilterType = Enum.RaycastFilterType.Exclude;
params.FilterDescendantsInstances = {character};

-- ⤹ Functions ⤸ --
local function GetRailRelative(rail: BasePart)
	local axis = rail.CFrame:VectorToObjectSpace(rail.Position);
	local player_axis = rail.CFrame:VectorToObjectSpace(HumanoidRootPart.Position);
	local offset = player_axis - axis;

	-- ⤹ Return ⤸ --
	return offset, player_axis;
end


-- ⤹ Handle Jumping ⤸ --
user_input_service.JumpRequest:Connect(function()
	-- ⤹ If We Are On A Rail ⤸ --
	if on_rail then
		-- ⤹ Rail Speed ⤸ --
		
		local add = rail.CFrame.LookVector * (rail_speed * config.rbx_amplif);
		if rail_dir == -1 then
			-- ⤹ Flip Cause We Are Going Backwards ⤸ --
			add = -add;
		end
		
		-- ⤹ Set Velocity & Debounce ⤸ --
		local jump_vel = rail.CFrame.UpVector * humanoid.JumpPower
		HumanoidRootPart.AssemblyLinearVelocity = add + jump_vel;
		rail_debounce = config.debounce;
		
		-- ⤹ Jump & Reset Rail Variable ⤸ --
		humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
		on_rail = false;
	end
end)

-- ⤹ Rail Physics Bind ⤸ --
run_service:BindToRenderStep("__RailUpdate.core", Enum.RenderPriority.Camera.Value, function(dt)
	-- ⤹ Rail Debounce Timer ⤸ --
	rail_debounce = math.max(0, rail_debounce-1);
	
	-- ⤹ Do Physics ⤸ --
	if on_rail then
		if event_fire_attempts == 0 then 
			GrindBindable:Fire(on_rail, humanoid:GetState())
			print("Grinding: "..tostring(on_rail))
		end
		
		-- ⤹ Rotate If We Are Moving Backwards ⤸ --
		local offset = rail_dir == 1 and 0 or math.rad(180);
		if not config.turn_rail then
			offset = rail_origin == 1 and 0 or math.rad(180);
		end
		rail_ang = rail_ang:Lerp((rail.CFrame - rail.CFrame.p) * CFrame.Angles(0, offset, 0), 0.1 + (rail_speed/4));
		
		-- ⤹ Move The Character ⤸ --
		
		HumanoidRootPart.CFrame = CFrame.new((rail.CFrame * CFrame.new(0, 0, cur_pos) * CFrame.new(0, humanoid.HipHeight + config.hip_offset, 0)).Position) * rail_ang 	
		
		-- ⤹ Anchor The Player ⤸ --
		HumanoidRootPart.AssemblyLinearVelocity = Vector3.zero 
		
		-- ⤹ Rail Speed ⤸ --
		local add = rail.CFrame.LookVector;
		if rail_dir == -1 then
			add = -add;
		end
		
		-- ⤹ Check For A Rail ⤸ --
		local down_part = workspace:Raycast(HumanoidRootPart.Position + (add * rail_speed), (rail.CFrame - rail.CFrame.p).UpVector * -4, params);
		if down_part and down_part.Instance and down_part.Instance.Name == "Rail" then
			-- ⤹ If Not Our Current Rail ⤸ --
			if down_part.Instance ~= rail then
				rail = down_part.Instance;
				local offset = GetRailRelative(down_part.Instance);
				cur_pos = offset.Z
			end
		else
			-- ⤹ Fall Of The Rail ⤸ --
			on_rail = false;
			GrindBindable:Fire(on_rail, humanoid:GetState())
			HumanoidRootPart.AssemblyLinearVelocity = add * rail_speed * config.rbx_amplif;
			humanoid:ChangeState(Enum.HumanoidStateType.Freefall)
		end
		
		local forward_upward_part = workspace:Raycast(HumanoidRootPart.Position + (add*rail_speed), HumanoidRootPart.CFrame.LookVector, params)
		if forward_upward_part and forward_upward_part.Instance and forward_upward_part.Instance.Name == "Rail" then 
			if forward_upward_part.Instance ~= rail then
				rail = forward_upward_part.Instance;
				local offset = GetRailRelative(forward_upward_part.Instance);
				cur_pos = offset.Z
			end
		end
		-- ⤹ Variables For Slope Detection ⤸ --
		local a1a = add;
		local dotp = (a1a.unit):Dot((Vector3.new(0, -1, 0)).Unit);
		local slope = -dotp;
		local acc = 0;
		
		-- ⤹ Check? ⤸ --
		if math.abs(slope) > 0.1 then
			if slope < 0 then
				-- ⤹ Moving Down, Accelerate ⤸ --
				acc += math.abs(slope) * config.rail_accel;
			else
				-- ⤹ Moving Up, Slow Down ⤸ --
				acc -= math.abs(slope) * config.rail_decel;
			end
		end
		
		-- ⤹ Calculate Rail Speed ⤸ --
		rail_speed += (acc / config.rbx_amplif)
		if rail_speed <= 0 and slope ~= -0 then
			-- ⤹ Change Direction, Since We Are Going Too Slow ⤸ --
			if rail_dir == 1 then
				rail_dir = -1;
			else
				rail_dir = 1;
			end
			
			-- ⤹ Accelerate ⤸ --
			rail_speed = config.turn_spd;
		end
		
		-- ⤹ Friction ⤸ --
		rail_speed = math.max(0, rail_speed - config.rail_friction)
		
		-- ⤹ Speed Cap ⤸ --
		if math.abs(rail_speed) > config.speed_cap then
			rail_speed = math.sign(rail_speed) * config.speed_cap;
		end
		-- ⤹ Minimum Speed ⤸ --
		if math.abs(rail_speed) < config.min_spd and config.min_spd ~= -1 then
			rail_speed = math.sign(rail_speed) * config.min_spd;
		end
		
		-- ⤹ Move ⤸ --
		if rail_dir == 1 then
			cur_pos -= rail_speed;
		else
			cur_pos += rail_speed;
		end
		
		-- ⤹ Rail Debounce ⤸ --
		if not on_rail then
			rail_debounce = config.debounce;
		end
	elseif HumanoidRootPart.AssemblyLinearVelocity.Y <= 0.1 and rail_debounce <= 0 then
		-- ⤹ Unanchor The HumanoidRootPart ⤸ --
		HumanoidRootPart.Anchored = false 
		event_fire_attempts = 0 
		
		-- ⤹ Get Rails ⤸ --
		for _, temp_rail in pairs(workspace:GetPartBoundsInRadius(HumanoidRootPart.Position, 2.5)) do
			-- ⤹ Check If It's Actually A Rail ⤸ --
			if temp_rail.Name == "Rail" then

				local offset, player_axis = GetRailRelative(temp_rail);
				local player_axis2 = temp_rail.CFrame:VectorToObjectSpace(HumanoidRootPart.Position + HumanoidRootPart.Velocity);
				
				-- ⤹ Are We Going Backwards? ⤸ --
				local offset2 = player_axis - player_axis2;
				if offset2.Z < 0 then
					-- ⤹ Yes ⤸ --
					rail_dir = -1;
				elseif offset2.Z > 0 then
					-- ⤹ No ⤸ --
					rail_dir = 1;
				end
				
				-- ⤹ Can We Go On The Rail ⤸ --
				if math.abs(offset.Z) < (temp_rail.Size.Z/2) then
					-- ⤹ Set Rail Part & Speed ⤸ --
					rail = temp_rail;
					rail_origin = rail_dir
					rail_speed = (HumanoidRootPart.Velocity - Vector3.new(0, HumanoidRootPart.Velocity.Y, 0)).Magnitude/config.rbx_amplif;
					
					-- ⤹ Set The HumanoidRootPart Angle & Rail Position ⤸ --
					rail_ang = HumanoidRootPart.CFrame - HumanoidRootPart.CFrame.p;
					cur_pos = offset.Z;
					
					-- ⤹ We Are Now On The Rail ⤸ --
					on_rail = true;
					if event_fire_attempts == 0 then 
						event_fire_attempts = 1 
						GrindBindable:Fire(on_rail, humanoid:GetState())
					end
					
					-- ⤹ Contact Sound ⤸ --
					contact_sound:Play();
					
					-- ⤹ Enable Particles ⤸ --
					for i=1, #particles do
						particles[i].Enabled = true
					end
				end
			end
		end
	else
		-- ⤹ Unanchor The Root ⤸ --
		HumanoidRootPart.Anchored = false
		GrindBindable:Fire(on_rail, humanoid:GetState())
	end
	
	-- ⤹ Rail Sound ⤸ --
	if not on_rail then
		rail_sound:Stop();
	else
		if not rail_sound.IsPlaying then
			rail_sound:Play();
		end
	end
	
	-- ⤹ Animation ⤸ --
	if not on_rail and load_animation.IsPlaying then
		load_animation:Stop();
		
		-- ⤹ Enable Particles ⤸ --
		for i=1, #particles do
			particles[i].Enabled = false
		end
	elseif on_rail and not load_animation.IsPlaying then
		MiscFunctions.StopAllCharAnimations(animator)
		load_animation:Play();
	end
end)

And here is a clip of the bug in action. Note the FPS count and the speed of the grind.

External Media

Thank you in advance.

1 Like

You need to use it as a multiplying factor, as mentioned in the post about any frame-sensitive methods.

3 Likes
return function (Time)
	local startOS = os.clock()
	repeat game:GetService("RunService").Heartbeat:Wait() until os.clock() >= startOS + Time
end

i use this function to make precise wait, idk if it will help you

1 Like

I know that, I just don’t know where to put deltaTime in the script. I’ve put it in numerous places trying to see for an effect but usually it doesn’t do anything.

2 Likes

Looks like you might need to put it here:

3 Likes

Just tried the following

  1. Multiplying cur_pos by dt
  2. Saving the new CFrame as a variable then multiplying it by a CFrame with dt as the Z
  3. Dividing the CFrame by a CFrame with dt as the Z

None of these have worked…

3 Likes

Update: Found the solution. In lines 228-233, I had to multiply the rail speed by deltaTime. The character jerks back and forth while grinding, but hopefully I find the solution to that as well.

1 Like

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