Confused about coroutines for turret enemies

I’m in the process of making a humanoid turret enemy that turns it’s turret barrel towards you and fires. I’ve gotten a pretty good ways along but I’ve run into a bit of a snag when it comes to certain things:

I want the turret’s aiming to operate as smoothly as it can (this means being independent of the firing cooldown and running on either a “while true do wait()” or a RunService loop) while the projectiles fire at a set rate using what’s basically FastCast. In past, all my solutions either ended up having the aiming happen concurrent with the aforementioned set rate or having the turret spew thousands of bullets within a matter of a few seconds. The current solution I have seems to do what I want it to, but I can’t tell, plus it now has the problem of not stopping its firing when the turret kills you.

My current code is here:

local CS = game:GetService("CollectionService")
local SS = game:GetService("ServerStorage")
local RS = game:GetService("ReplicatedStorage")
local run = game:GetService("RunService")

local events = RS:WaitForChild("Events")
local posEvent = events:WaitForChild("PositionsChanged")

local projectiles = SS:WaitForChild("Projectiles")
local turretBullet = projectiles:WaitForChild("Turret")

local turret = script.Parent
local humanoid = turret:WaitForChild("Humanoid")
local HRP = turret:WaitForChild("HumanoidRootPart")

local head = turret:WaitForChild("Head")
local barrel = head:WaitForChild("Barrel")
local aim = head:WaitForChild("Aim")
local COM = head:WaitForChild("CenterOfMass")

local config = turret:WaitForChild("Config")
local MAX_AGGRO = config:WaitForChild("MaxAggro")
local DAMAGE = config:WaitForChild("Damage")
local FIRE_RATE = config:WaitForChild("FireRate")

local enemies = CS:GetTagged("Enemy")

local firing = false

humanoid:ChangeState(Enum.HumanoidStateType.Running)

local targets = {}

turret.PrimaryPart:SetNetworkOwner(nil)

local function getTarget()
	local aggro = MAX_AGGRO.Value
	local chosenTarget = nil
	for player, target in pairs(targets) do
		if player.Character.Humanoid.Health > 0 then
			if (turret.PrimaryPart.Position - target).Magnitude < aggro then
				aggro = (turret.PrimaryPart.Position - target).Magnitude
				chosenTarget = player
			end
		end
	end
	return chosenTarget
end

local fire = coroutine.wrap(function()
	while true do
		firing = true
		local bullet = turretBullet:Clone()
		bullet.CFrame = barrel.WorldCFrame
		bullet.Parent = turret
		local speed = 200
		local params = RaycastParams.new()
		local iterations = 50
		params.FilterDescendantsInstances = {turret}
		local bulletConnection
		bulletConnection = run.Heartbeat:Connect(function(deltaTime)
			local position, nextPosition = bullet.Position, bullet.CFrame.LookVector * deltaTime * speed
			local result = workspace:Raycast(position,nextPosition,params)
			if result then	
				bulletConnection:Disconnect()
				bullet:Destroy()
				local human = result.Instance.Parent:FindFirstChildWhichIsA("Humanoid")
				if human and human.Health > 0 then
					human:TakeDamage(math.random(0.5 * DAMAGE.Value, 1.5 * DAMAGE.Value))
				end
			else
				bullet.Position = position + nextPosition
			end

			iterations -= 1
			if iterations < 0 then
				bulletConnection:Disconnect()
				bullet:Destroy()
			end
		end)
		task.wait(1 / FIRE_RATE.Value)
		firing = false
	end
end)

posEvent.OnServerEvent:Connect(function(player, position)
	targets[player] = position
end)

run.Heartbeat:Connect(function()
	local target = getTarget()
	if target and target.Character.Humanoid.Health > 0 and (targets[target] - HRP.Position).Magnitude <= MAX_AGGRO.Value then
		aim.CFrame = CFrame.lookAt(COM.WorldPosition, targets[target] + target.Character.PrimaryPart.Velocity/(3.9 - (targets[target] - HRP.Position).Magnitude/30))
		if not firing then
			fire()
		end
	else
		coroutine.yield(fire)
	end
	print("running", target)
end)

Let me know if there’s anything else I need to provide. I can’t imagine this being an extremely complex problem, I just don’t have a very good understanding of coroutines.

1 Like

You can either create a new coroutine for this, but I’d recommend not doing that. Firstly, I think that you should have a Vector3Value or CFrameValue or a CFrame attribute in the turret so that the client can handle the movement to prevent jittering. This can be used in a RunService.Stepped loop:

--put in loop
for _, turret in pairs(collectionService:GetTagged("Turret")) do
	local head = turret:FindFirstChild("Head")
	if not head then continue end
	local aim = head:FindFirstChild("Aim")
	if not aim then continue end
	aim.CFrame = aim.CFrame:Lerp(turret:GetAttribute("AimCFrame"), dt * 60)
end

Then, you can replace the aim.CFrame part in your code with the following code to the server script of the turret:

turret:SetAttribute("AimCFrame", CFrame.lookAt(COM.WorldPosition, targets[target] + target.Character.PrimaryPart.AssemblyLinearVelocity/(3.9 - (targets[target] - HRP.Position).Magnitude/30)))

I recommend making the AimCFrame attribute exist first or else the client script might have an error. Please note that you might have to modify the fire function to implement the change to simulate the bullet direction.

I attempted multiple variations of this based on what you suggested and even though the server would log the CFrame data, the turret would not turn, not even on the client. Even then, at this moment, I would prefer to keep everything regarding the turret on the server; this is meant to be part of a multiplayer game and I don’t feel like I’m experienced enough to juggle tasks between different players’ clients without running into a handful of issues.

The aiming of the turret doesn’t have to be silky smooth, it just needs to update faster than the firing rate of the turret, which is once a second for testing’s sake. I don’t plan on changing the way the bullets fly out of the turret because otherwise, it would be nearly impossible to dodge them.

In that case you can run the script every heartbeat and check if the last fire time was less than the current fire time minus the fire rate. This will allow you to fire the turret at your desired fire rate without creating a handful of coroutines, and additionally will allow you to update the turret’s aim CFrame.

I don’t know why I didn’t think of that, then again most of the problems I have end up having simple solutions as such. Thanks a bunch!

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