So, basically… I am making a game where the player has multiple turrets. The player can lock onto a target (instance) by clicking on an instance. When the player is locked on to something, the turrets will continue to fire until the object is destroyed or the target is changed to something else.
I have all of the above already done, however, I’m not sure how to create the auto-fire part. I thought about using loops, but found it hard to keep efficient. Each turret has its own reload time so I would have to have a loop for each turret, which caused my game to have degraded performance.
So, I am asking… how should I go about creating this auto-fire system, and what techniques do you recommend?
This is a simple way to code it. There could be ways to make it more efficient, but it would depend on your gameplay and how the game is supposed to work. I think this generally should be alright:
Instance hierarchy
Code
local function FireTurret(turret, target)
--code to fire the turret here...
turret.CurrentAmmo.Value -= 1
print(("Firing turret %s at %s (%i CurrentAmmo remaining)"):format(turret:GetFullName(), target:GetFullName(), turret.CurrentAmmo.Value))
end
local function ReloadTurret(turret)
print(("Reloading turret %s"):format(turret:GetFullName()))
--other reloading code here...
wait(turret.ReloadTime.Value)
turret.CurrentAmmo.Value = turret.MaxAmmo.Value
end
--auto fire loop
local function AutoFire(turret)
turret.IsAutoFiring.Value = true
while (turret.CurrentTarget.Value ~= nil) do
if (turret.CurrentAmmo.Value > 0) then
FireTurret(turret, turret.CurrentTarget.Value)
wait(60/turret.RPM.Value) --RPM = rounds per minute
else
ReloadTurret(turret)
end
end
turret.IsAutoFiring.Value = false
end
local function SetupTurret(turret)
turret.CurrentTarget.Changed:Connect(function(target)
if (turret.IsAutoFiring.Value == false) then
coroutine.resume(coroutine.create(AutoFire), turret)
end
end)
end
local function SetupTurrets(container)
if (not container) then return end
for i, turret in ipairs(container:GetChildren()) do
turret.CurrentAmmo.Value = turret.MaxAmmo.Value
SetupTurret(turret)
end
end
--------------------------------------
--setup/initialize the turrets
SetupTurrets(workspace:FindFirstChild("Turrets"))
You could also clone individual scripts into the turrets instead of using coroutines in a single script like my code.
In my game there is infinite ammo, and there is just a small cooldown between each shot. The turrets will stop firing when the target is changed or nil. I mainly wanted to find out how to go about creating the auto fire for each turret as each turret will have its own cooldown time.
I’ll look into the coroutine.resume(coroutine.create()) you used. Thank you.
In that case, the reloading and ammo code in my example can be ignored, but you should look at the RPM part still for how to do a “cooldown” between each shot. Each turret in my code can have a different RPM which changes how fast the turret shoots.
I can try and explain coroutines here a bit.
Coroutine Summary
What coroutines do is basically allows for you to create code that runs “concurrently” in the same script, basically allowing you to have “multiple scripts” in one script. Though it is important to understand that the code is not actually running at the same time as other code, it just appears that way on the outside.
When you create multiple scripts, it works the exact same as how coroutines work except that it is hidden from you. Each script in a game is essentially its own coroutine. Multiple scripts in Roblox don’t run at the same time, instead, they run only one script at a time (this can be changed with the new Parallel Luau feature though).
“How can they appear to run at the same time when that is not actually happening?”
Roblox uses something called a Task Scheduler to coordinate “work” (usually referred to as tasks or jobs) throughout the lifetime of a game running. The task scheduler is used in many aspects of the engine such as rendering and physics, but we are only concerned with the Lua part of task scheduling. I will be referring to “scripts” in the rest of the explanation below, but coroutines are interchangeable with it.
When a script is executed, it runs line-by-line continuously until it yields (meaning pause). This is typically done by calling a yield function. When the script yields, it will pause execution on that line and queue itself to resume at a later time in the future with the task scheduler.
At this point, the task scheduler will go ahead and check for any pending scripts that has been yielding and now need to be resumed, in which the script that is chosen will resume executing from after the line it yielded on. This process continues and essentially results in your game continuously switching between which script is executing each time one yields.
Some of the most fundamental yield functions you would be familiar with is wait() and Instance:WaitForChild(). In the case of wait, it will yield the script and resume again in the future after the specified amount of seconds have passed, and in the meantime, other scripts will run instead. You can actually code a wait function yourself in Lua by using coroutines and making a basic task scheduler.
Coroutine Example
coroutine.create is used to create a new coroutine where the code is the code from a given function. The coroutine does not run yet, it is only created and returned.
coroutine.resume is used to start a created coroutine or resume a yielded coroutine. This will return either when the coroutine finishes execution or it yields. The values returned are the values returned by the coroutine function. If the function errors, it will not propagate and it just returns the error message.
coroutine.yield is used inside a coroutine to yield it. This cannot be called outside of a coroutine. You can pass parameters to coroutine.yield and they will be returned to coroutine.resume.
coroutine.wrap is a better way to do coroutine.resume(coroutine.create()). It returns a function, that when called, will resume the coroutine. Errors that occur in the function are propagated unlike coroutine.resume.
local function InfiniteLoop()
--Print "Hello World!" every half a second, forever
while true do
print("Hello World!")
wait(0.5)
end
end
--Creates a coroutine and returns a function that will resume it
local ResumeInfiniteLoop = coroutine.wrap(InfiniteLoop)
--Resumes the coroutine, which runs the function InfiniteLoop()
ResumeInfiniteLoop()
--Normally, this code would not run because of the infinite while loop executed above.
--Coroutines allow for you to get around this.
--You will see in the output that it looks like for loop below runs at the same time as InfiniteLoop()
for i = 1, 25 do
print(i)
wait(0.25)
end
Thank you so much for all of this info, it definitely will help for the system I am trying to create, and also helps be better understand APIs that I can use for other scripts!