Help with Rotating Turret script

Objective
I’m trying to create a turret system that can rotate on an X and Y axis to track targets.

Problem
Unfortunately, though it seems like the Y axis works fine, the X axis seems to be slightly off the mark (See video below).

[Video]

[Code]

local RunService = game:GetService("RunService")

--// Structure
local Turret = script.Parent.Parent
local System = Turret.System
local YawHinge = Turret.Mount_Assembly.MountBase.YawHinge
local PitchHinge = Turret.Gun_Turret.GunBase.PitchHinge
local Target_Part = game.Workspace["AAGT_Target-A"]

local direction = Vector3.xAxis
RunService.Stepped:Connect(function(et, dt)
	direction = Turret.Gun_Turret.GunBase.CFrame:PointToObjectSpace(Target_Part.Position)
	
	local FlatDirection = direction * Vector3.new(1, 0, 1)
	YawHinge.TargetAngle = math.deg(Vector3.xAxis:Angle(FlatDirection, Vector3.yAxis)) -- Calculates the angle between the z axis and the flat direction
	
	local axis = Vector3.yAxis:Cross(FlatDirection)
	PitchHinge.TargetAngle = -math.deg(FlatDirection:Angle(direction, axis))
end)

I used B Ricey’s video on Youtube as a reference (https://youtu.be/BBoJiE4IkSg), though instead of using a reference part, I used an existing part that’s centered and facing forward. The barrel is directly in front of this part. Could anyone provide insight on what might be wrong?

Thanks!

2 Likes

Do you want to accomplish this by any means or do you want to stick with a mathematical approach?

2 Likes

I stuck with the mathematical approach for the sake of following along the video and learning how to do things a certain way, but at this point I’d rather the model just work and learn whatever I can.

1 Like

How about this turret? I think it behaves similar to the YouTube video you provided, but it lacks some features like the ‘idle animation’ mentioned in the video, however, that’s pretty easy to implement using the same CFrame math.


Here’s the code:

local ball = script.Parent.Ball
local target = workspace.Target
local platform = script.Parent.Base.Platform

--local startCFrame = ball.CFrame
--local radius = 12

--local i = 0
while task.wait() do
	--i += 0.05
	
	local origin = ball.Position
	local direction = target.CFrame.Position
	local goal = direction -- IGNORE_Y_AXIS -> Vector3.new(direction.X, ball.Position.Y, direction.Z)
	
	ball.CFrame = ball.CFrame:Lerp(CFrame.lookAt(origin, goal), 0.05)
	
	--local targetCframeGoal = platform.CFrame * CFrame.new(math.sin(i) * radius, 0, math.cos(i) * radius)
	
	--target.CFrame = target.CFrame:Lerp(targetCframeGoal, 0.05) 
	
end
2 Likes

Hello I’ve had to deal with the exact same problem as you do in a scrapped tank system, here’s how I solved it:

function Tank:SetBarrelTargetAngle(theta: number)
	local _theta = math.clamp(theta, -15, 30)
	self.model.BarrelConstraint.TargetAngle = _theta
end

function Tank:SetTurretTargetAngle(theta: number)
	self.model.TurretConstraint.TargetAngle = theta
end

function Tank:TurretLookAt(lookAt: Vector3)
	local model = self.model
	local body = model:FindFirstChild("Body") :: Part
	local turret = model:FindFirstChild("Turret") :: Part
	local barrel = model.Barrel :: Part

	local barrelPos = barrel.Position
	local barrelLookAt = CFrame.lookAt(barrelPos, lookAt)
	local barrelRotationVector = Vector3.new(barrelLookAt:ToEulerAnglesXYZ())
	local xtheta = -math.deg(barrelRotationVector.X)

	local projectedVector = body.CFrame:PointToObjectSpace(lookAt) * Vector3.new(1, 0, 1)
	local ytheta = math.deg(math.atan2(projectedVector.Z, projectedVector.X) - math.pi / 2)

	self:SetTurretTargetAngle(ytheta)
	self:SetBarrelTargetAngle(xtheta)
end

The Y axis was rotating correctly but the X axis was always off by 90-270 degrees of rotation. I solved it by making the horizontal rotation into a 2D (X, Z) projection instead.

Now this won’t work out of the box but you can surely migrate it.
This system used hinge constraints I’m pretty sure, and it was made to rotate towards where the mouse cursor was pointing. Hope this helps!

1 Like

I blame my lack of progress / accurate implementation on my rudimentary understanding of CFrames and Angles. Here’s what I have now:

The White one is the original, no changes were made.

The Black one is the experimental version I randomly just threw into the mix, though it’s very rugged.

local RunService = game:GetService("RunService")

--// Structure
local Turret = script.Parent.Parent
local System = Turret.System
local YawHinge = Turret.Mount_Assembly.MountBase.YawHinge
local PitchHinge = Turret.Gun_Turret.GunBase.PitchHinge

local Target_Part = game.Workspace["AAGT_Target-A"]


local direction = Vector3.xAxis
RunService.Stepped:Connect(function(et, dt)
	
	Turret.Gun_Turret.GunBase.CFrame = CFrame.lookAt(Turret.Gun_Turret.GunBase.Position, Target_Part.Position)
end)

The Red one is my attempt to replicate your method with my setup.

local RunService = game:GetService("RunService")

--// Structure
local Turret = script.Parent.Parent
local System = Turret.System
local YawHinge = Turret.Mount_Assembly.MountBase.YawHinge
local PitchHinge = Turret.Gun_Turret.GunBase.PitchHinge

local Target_Part = game.Workspace["AAGT_Target-A"]


while task.wait() do
	local direction = Target_Part.CFrame.Position
	
	--// X Section	
	local origin = Turret.Gun_Turret.GunBase.Position
	local goal = direction
	Turret.Gun_Turret.GunBase.CFrame = Turret.Gun_Turret.GunBase.CFrame:Lerp(CFrame.lookAt(origin, goal), .05)
	
	--// Y Section
	local axis = Vector3.yAxis:Cross(direction)
	PitchHinge.TargetAngle = -math.deg(direction:Angle(direction, axis))
end

The Green one is my attempt to replicate @CE0_OfTrolling’s setup.

local RunService = game:GetService("RunService")

--// Structure
local Turret = script.Parent.Parent
local System = Turret.System
local YawHinge = Turret.Mount_Assembly.MountBase.YawHinge
local PitchHinge = Turret.Gun_Turret.GunBase.PitchHinge

local Target_Part = game.Workspace["AAGT_Target-A"]


RunService.Stepped:Connect(function(et, dt)
	local body = Turret.Gun_Turret.GunBase
	
	--// X Section
	local XYDirection = CFrame.lookAt(Turret.Gun_Turret.GunBase.Position, Target_Part.Position) --// X Control	
	YawHinge.TargetAngle = math.deg(Vector3.new(XYDirection:ToEulerAnglesXYZ()).X)
	
	--// Y Section
	local direction = Turret.Gun_Turret.GunBase.CFrame:PointToObjectSpace(Target_Part.Position) * Vector3.new(1, 0, 1)
	PitchHinge.TargetAngle = math.deg(math.atan2(direction.Z, direction.X) + math.pi / 2)
end)

This is something I’m willing to dedicate more time into learning, since this project would essentially open a ton of new projects (vehicle-mounted weapons like CEO mentioned, ship weapons, and other kinds of ground-based emplacements)

1 Like

I love how mine is completely broken. It’s likely due to the different nature of what you’re making I suppose. Anyways here’s a video of it in action:

I’m not sure why it doesn’t work with your setup but if you found a working method good for you I guess.

2 Likes

I promise it wasn’t your fault, I just need to keep messing around with these scripts and learn more. The black turret’s flaw is that it doesn’t gracefully turn, it just forces the gun body to face the desired direction and forcefully makes the mount assembly follow suit, rather than turning the mount assembly first (Yaw) and then the gun body itself (Pitch).

Nice tank by the way, I’m hoping to make something similar down the line!

I’ll continue to mess around with the scripts and can provide the model itself if you wanted to take a look. I had intended on releasing it to the public anyways once it was completed.

I ended up using a reference point, as noted in B Ricey’s video at 10:10. I neglected to use it earlier, thinking my GunBody would be enough, since it’s centered and its position doesn’t change regardless of movement. However when I switch the GunBody within the script to the Reference point, which is anchored and located at the exact position of the GunBody, the script works perfectly.

I don’t understand why this is the case, but at the very least it’s working. If someone could provide insight as to why that’s the case, I can mark this thread as resolved.

Thanks for all the help so far, by the way. I think my naivety is what makes it difficult to implement the solutions you’ve all provided, it’s not that your solutions don’t work.