Gun Firerate Slows Down With FPS Drops, I need solutions

Previous thread

I’m using the spawn() function in my game because if the framerate dips below 45, all of the guns in-game become bolt-action rifles pretty much, resulting in players with slower PCs to rage quit 90% of the time. I’ve opted to put all of the UserInput stuff within a spawn function, while putting numerous other functions inside of spawn functions to offset the load. This seems to be working, but I want input from people who are a bit more experienced in this than me.

How can I prevent things from being slowed down client-side?

For some reason, pretty much everything that’s time based, be it the pain screen that flashes red for a second and then fades out slowly, the guns, etc. Everything slows down with device performance. I’m not sure what to do other than possibly use spawn. :stuck_out_tongue:

Originally was a thread asking the pros and cons to using Spawn()

3 Likes

A game should aim rather on the action than quality if the device is not powerful enough, unlike a movie where you’d expect to see all the frames. Roblox renders objects based on the current time and not on the number of rendered frames, for example if you tween an UI for 1 second, it’ll finish as soon as 1 second, despite your frame rate being 60 Hz or 5 Hz, however with 5 Hz you’d skip some frames.

So if your gun can fire at 50 Hz, and your frame rate is 45 Hz, an ideal game would render on average more than 1 shot per frame.

Some games account for the network rates too, they’d keep your gun shooting on the server until you release it.

1 Like

My problem right now is the same thing that plagued TurboFusion’s gunkit.

I think it has to do possibly with my code being ran with a renderstepped, and I might change that out eventually.

My problem is if someone’s FPS is below 50, instead of a minigun firing off 50-60 bullets a second, it fires one bullet every 5 seconds. This could be a network issue, but that wouldn’t make sense to me considering the gun firing is handled on the client.

The pain screen which is just a color correction effect that slowly fades out also has this problem, so you can have a red screen for about 15 seconds, even though it’s only supposed to last 3 seconds at most.

EDIT
Probably has nothing to do with RenderStepped - the gun’s “sway” mechanic never slows down.

I’m wondering if it’s the while loop instead that controls the firing. Going to see if transitioning everything to use a RenderStepped function instead of a while loop would fix this problem.

Should be noted that this also occurs on 60 FPS for some reason, so it’s not tied to device performance it seems.

Spawn seems to have a very slight delay before it runs the function (about a 30th of a second), you could try a coroutine, seems to be lower.

Otherwise it seems to be an inherent problem wherein scripts don’t run a segment after the intended delay (guessing it’s due to being on the same thread something something)

Could also try keeping track of how many bullets have been fired in the last x time, and if say the clients’ FPS is say 30 on a 60 bullet per second gun you could rig it to fire 2 bullets on the same frame for example.

1 Like

The pain screen which is just a color correction effect that slowly fades out also has this problem, so you can have a red screen for about 15 seconds, even though it’s only supposed to last 3 seconds at most.

It sounds to me like you’re changing things a fixed amount every frame. One of the points of binding a function to RenderStepped is that it returns the time that has passed since the last frame. Example:

Wrong way of doing things

local movePerFrame = 0.01
function onRenderStepped()
	moveTheThing(movePerFrame)
end

Better way:

local movePerSecond = 0.5
function onRenderStepped( dt )
	moveTheThing(movePerSecond * dt)
end

The latter will make the thing move 0.5 per second no matter how fast or slow the players PC is.

4 Likes

The pain screen is just a for loop that gradually changes the Tint to 255, 255, 255.

TweenService

4 Likes

That would actually probably be a lot better than my current implementation. Never thought of using that. :smile:

Actions which must be done every frame should be scaled to match the length of time between frames, this is commonly referred to as the frame delta time. Ie if you want to move a block at 1 stud per second then you would do the following

function updatePosition(delta)
    part.Position = part.Position + Vector3.new(1, 0, 0) * delta
end

This block will now move at 1 stud per second along the x axis regardless of the current fps.

For more complicated actions (such as firing a gun at 3000 rpm (50 rounds per second) when the client is only rendering at 30 fps) you need an accumulating delta value. On each frame you add the frame delta to a value, then when you fire a bullet you subtract a set value. Here is a quick example which an fps around 30 and a target fire rate of 50 rounds per second (or a shot every 20 ms).

Frame 1 delta=30ms accumulatedDelta = 0ms
Frame 2 delta=31ms accumulatedDelta = 30ms (>20 so shoot!) = 10ms
Frame 3 delta=33ms accumulatedDelta = 41ms (>20 so shoot!) = 21ms (>20 so shoot!) = 1ms
Frame 4 delta=28ms accumulatedDelta = 34ms (>20 so shoot!) = 14ms

etc. As you can see this will fire multiple shots per certain frames to keep up with the target frame rate.

Now with Spawn() I wouldn’t worry about performance concerns. You can think of it as like running a new script alongside. I doubt you will see any performance hit unless you are being super wasteful. As for the slight delay after calling Spawn(), the scheduler will schedule it to run on the next Lua frame which is typically around 33ms.

8 Likes

How do you obtain the FrameDelta exactly?

game:GetService("RunService").RenderStepped(FrameDelta)

https://developer.roblox.com/api-reference/event/RunService/RenderStepped

2 Likes

So would this remove the need to use while loops (went with those since those didn’t seem to be tied to Framerate, but I was wrong) ?

I’m also assuming I would put the fire() function inside of the onRenderStepped() function.

I’m pretty certain while loops shouldn’t be tied to frame rate.

1 Like

They are for me for some reason. It could be really bad optimization on my part, but I’m not sure.

A while loop is a standard feature of Lua and is not specific to Roblox or to you. It has nothing to do with ‘frame rate’ unless you set it up so that each iteration yields until the next frame or something similar.

Regarding your original post: magnalite gave some helpful advice. The code that FracturedSoftware posted unfortunately will not work because they haven’t called the Connect method of the RenderStepped Event. What I imagine he is suggesting you do is use the parameter passed when the Event fires (time since last render frame) - this will help you follow magnalite’s advice:

7 Likes

to add to magnalite’s reply, also be aware of the difference between a variable time step as opposed to a fixed time step. RenderStepped's step param is an example of a variable time step, because the user’s frame rate can change over time (i.e. each step is not exactly equal). Depending on what you’re doing (esp. physics calculations) this can introduce some subtle, hard-to-crack bugs; I learned this the hard way when I was attempting to create a deterministic billiard sim in Roblox Lua (TL;DR variable time step is neva eva deterministic)

to solve this problem, implementations typically use something called a time accumulator. Here’s a quick and dirty example:

local RenderStepped = game:GetService("RunService").RenderStepped
local GameRunning = true

spawn(function()
	local deltaT = 1/30 -- the fixed time step
	local currentTime = tick()
	local accumulator = 0
	while GameRunning do
		local newTime = tick()
		local frameTime = newTime - currentTime
		currentTime = newTime
		accumulator = accumulator + frameTime
		while accumulator >= deltaT do
			-- sim is ticked with the deltaT arg here
			accumulator = accumulator - deltaT
		end
		RenderStepped:Wait()
		-- interp can be done here
	end
end)

edit: something I forgot to mention - if you want to do interpolation, then the state of the last tick has to be cached (otherwise you have nothing to interpolate)

also, this example is not optimized - it can be by utilizing that step parameter from RenderStepped for frameTime instead of calculating it ourselves. Additionally, the above code will run VERY HOT, so you have to be extremely careful about what you’re doing in it

ANOTHER thing to consider (I know I’m making many edits :slight_smile: ) for more consistent performance: it may be a better idea to use RunService.Heartbeat rather than RunService.RenderStepped if what’s being done in this code is game logic (rather than visual effects), due to how the rendering pipeline works. @buildthomas provides a good overview of why here (I swear this is my last edit)

8 Likes

How fast does the gun shoot?

It doesn’t really matter if it’s 1.6 seconds (Pump Shotgun) or 0 seconds (Minigun.)

It just seems that the firing rate is tied to the framerate for some reason.

You mean 1.6 seconds per bullet for the pump shotgun and 0 seconds for Minigun? How does that work? You can’t have a 0 second wait, that’ll crash your game…