Trying to use tick() to get a decent automatic weapon

I’m trying to do full auto weapon, however can’t get the wait time accurate. If FireRate is set at like 10000 (10,000 bullets / second), I’m getting no where close to that number.

while MouseDown do

    local CurrentTick = tick()
	local WaitTime = 60 / EquippedWeapon.Config:GetAttribute("FireRate")
	while tick() - CurrentTick < WaitTime do
		RunService.Heartbeat:Wait()
	end
end

I’ve also tried wrapping it in a Heartbeat function, but then it fires way too many, and I can’t get the right wait time.

2 Likes

You’ll have to queue multiple rounds every frame. This is a good practice even with lower round per minute weapons as low FPS can affect weapon fire rate.

I’m unsure what you mean by this?

Can you try using os.clock? Its far accurate than tick and tick will be deprecated anyway and has this potential issue when used on client:

1 Like

Fire multiple bullets each frame. Keep track of when the last bullet was fired and loop till TimeLast+(1/10000*60)*i>TimeCurrent.

local timeLast = os.clock()
while Heartbeat:Wait() do
	local timeCurr = os.clock()
	while timeCurr-timeLast>=1/10000*60 do
		--fire
		timeLast+=1/10000*60
	end
end

If you only fire one bullet per heartbeat then the maximum rate of fire you can achieve is limited by the rate at which heartbeat fires. Instead you should fire multiple bullets instead.

local FIRE_RATE = ...

function startFiring()
	local startTime = os.clock()
	local fired = 0
	return RunService.Heartbeat:Connect(function (step)
		local elapsedTime = os.clock() - startTime
		local totalBullets = elapsedTime * FIRE_RATE
		for i = 1, totalBullets - fired do
			fire()
		end
		fired = totalBullets
	end)
end

This method returns an RBXScriptConnection which you should disconnect when you want to stop firing.

2 Likes

Looks promising, I’ll have to try it out when I get home.

My only other concern is how could I keep this secure on the server too?? I can’t remember exactly what I’m doing, but I basically save their tick() when they shoot, if they shoot again I check their last tick() and make sure it’s been enough time (from the FireRate) for them to shoot again

You need to keep track of how many bullets the client has available and how many they have fired. Then just make sure over some period of time (such as a second) they don’t exceed the rate of fire (and if they do, it’s not by much).

1 Like

Unsure if my implementation is a little off, but it seems to do like a burst, stop, burst, stop, etc.

Fire rate is set at just 1. If I do like 10, its way too many bullets then.

local startTime = os.clock()
local fired = 0
		
Heartbeat = RunService.Heartbeat:Connect(function(step)
	if Player:GetAttribute("Sprinting") then return end -- Sprinting
	
	if not MouseDown then return end
		
	local elapsedTime = os.clock() - startTime
	local totalBullets = elapsedTime * EquippedWeapon.Config:GetAttribute("FireRate")
	
	for i = 1, totalBullets - fired do
		if Player:GetAttribute("Sprinting") then break end -- Sprinting
		
		if not MouseDown then break end
		
		-- Raycast from the mouse to get position
		local Mouse = UserInputService:GetMouseLocation() or Player:GetMouse()
		local UnitRay = Camera:ScreenPointToRay(Mouse.X, Mouse.Y)
		
		local MouseRaycast = workspace:Raycast(
			UnitRay.Origin,
			UnitRay.Direction * 1000,
			CastParams
		)
		
		local MousePosition = MouseRaycast.Position
	
		local FirePoint = Weapon.FirePoint.WorldPosition
		
		Bullet.Fire(
			Player,				-- Player who fired
			Character.Weapon,	-- Weapon model
			true,				-- Fired
			MousePosition, 		-- To
			FirePoint			-- From
		)
		
		Recoil() -- Create recoil effect
		
		fired = totalBullets
	end
end)

What I’m after is a constant flow of bullets, using the one loop type, but editable in their fire rate, so i should be able to have an automatic gun that fires 1000 rounds a minute, and another (with the same loop) firing say 100 rounds a minute

FIre rate set at 10 at the start of the video. Seems to be a lot more bullets than 10 a minute :sweat_smile: (obviously 10 a minute isn’t really auto, but when I set it to like 100 or even 1000 my studio crashed)

When I’m firing, I’m holding the house down the whole time. So when I set it to 1, and 0.5, my mouse is being beld down, but there’s a large gap between rounds.

btw I would recommend not using while loops, they just lag out a LOT. Also Heartbeat is limited, if it were 10,000 you would need like 10,000 fps I’m pretty sure. Anyways, it’s impossible to get that high basically unless you group bullets for instance shoot 1000 every 0.1 seconds, you won’t even see the difference though as 0.1 is basically to short, try to do 0.01 and 100 bullets though, it’ll be even less visible

I’m not trying to do crazy numbers like that though. I’m after 1,000 a minute, not a second :sweat_smile:

LOL, ok then, yeah I guess a while Holding do is prob the best, just do

while MouseHolding do
   FireBullet()
    wait(60/FireRate)
end

This is because you never change the values of startTime and fired. These need to be reset every time the gun starts firing.

Keep in mind you don’t need to listen to heartbeat all the time, only while the user is holding the fire button. So you can move most of your logic for firing/stop firing out of the event handler entirely.

A while loop is not sufficient. I’ve done a while loop with a wait() and even then, the bullets are not that fast

I’m unsure what you mean by never changing the values of startTime and fired?? I don’t know what they should be set to? With my code, they are being reset every time I start firing, even so, I can’t get it any different to the video in my previous response :confused:

The heartbeat is wrapped inside the inputBegan function, and I disconnect it on InputEnded

--// Input began
local function InputBegan(input, GPE)
	if GPE then return end
	
	if Humanoid.Health <= 0 then return end -- Dead
	
	if input.UserInputType ~= Enum.UserInputType.MouseButton1 and input.UserInputType ~= Enum.UserInputType.Touch then return end
	
	if Player:GetAttribute("Sprinting") then return end -- Sprinting
	
	local Weapon = ViewModel:FindFirstChild("Weapon")
	if not Weapon then return end
	
	local EquippedWeapon = Weapons:FindFirstChild(PlayerData.Equipped.Value)
	if not EquippedWeapon then return end
	
	if not CanFire then return end -- Can't fire yet
	
	MouseDown = true
	
	local Auto = EquippedWeapon.Config:GetAttribute("Auto")
	if Auto then -- Automatic fire
		
		CanFire = false
		
		local startTime = os.clock()
		local fired = 0
		
		Heartbeat = RunService.Heartbeat:Connect(function(step)
			if Player:GetAttribute("Sprinting") then return end -- Sprinting
			
			if not MouseDown then return end
			
			local elapsedTime = os.clock() - startTime
			local totalBullets = elapsedTime * EquippedWeapon.Config:GetAttribute("FireRate")
			
			for i = 1, totalBullets - fired do
				if Player:GetAttribute("Sprinting") then break end -- Sprinting
				
				if not MouseDown then break end
				
				-- Raycast from the mouse to get position
				local Mouse = UserInputService:GetMouseLocation() or Player:GetMouse()
				local UnitRay = Camera:ScreenPointToRay(Mouse.X, Mouse.Y)
				
				local MouseRaycast = workspace:Raycast(
					UnitRay.Origin,
					UnitRay.Direction * 1000,
					CastParams
				)
				
				local MousePosition = MouseRaycast.Position
				
				local FirePoint = Weapon.FirePoint.WorldPosition
				
				Bullet.Fire(
					Player,				-- Player who fired
					Character.Weapon,	-- Weapon model
					true,				-- Fired
					MousePosition, 		-- To
					FirePoint			-- From
				)
				
				Recoil() -- Create recoil effect
				
				fired = totalBullets
			end
		end)
	end
end

This should work better: v

Local dt = 0
While mouseDown do
	fire() 
	while dt < fire rate do
		dt += heartbeat:Wait() 
	end
	dt = 0
end

This should work a bit better but is still frame rate influenced, what you should use instead is a time accumulator which is in fact frame rate independent.