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?
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.
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
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!
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)
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.