Help with tank turret vertical hinge movement

Hello, I am making a tank system for a game I am working on. Currently, I am trying to figure out how to make the tank turret follow the mouse. I was able to make it follow the mouse horizontally with the help of my brother and some weird trigonometry stuff, but neither I or him could figure out how to make it work vertically. I am using servos for both horizontal and vertical movement.

Calculation code:

local function doHorizontalMathForRotate(MousePos,Base)
	local baseToTargetVector = Base.Position - MousePos
	local xAngle = math.deg(math.atan2(baseToTargetVector.Z,baseToTargetVector.X))-90  + Base.Orientation.Y
	return xAngle
end

local function doVerticalMathForRotate(MousePos,Base)
	
	--local object_horizontal_offset = Base.CFrame:PointToObjectSpace(MousePos)
	--local object_yaw_angle = math.atan2(-object_horizontal_offset.X, -object_horizontal_offset.Z)
	--local yaw_force = yaw_controller(0, object_yaw_angle)
	
	--local distance = math.sqrt((Base.Position.X-MousePos.X)^2+(Base.Position.Z-MousePos.Z)^2)
	--local yAngle=math.deg(math.tan(Base.Position.Y+MousePos.Y/distance))-2
	--print(yaw_force)
	return 0
end

-The commented code was taken from a different developer forum post, which ended up not working as expected.
-The variable named “Base” refers to the bottom part of the tank.
-The variable named “MousePos” refers to mouse.hit.p
-This script is ran on the server, and the math calculation functions are called every 20th of a second. I know this is not the best for performance, but I currently have it set up this way because I am trying to fix a different unrelated bug.

I do not know any trig, as I am taking geometry this year, so please try to simplify things for me as much as possible.

The path to the tube of the turret is tank.TankTube, and the path to the part above the base is tank.TankTop

2 Likes

Use the mouse instance.

https://developer.roblox.com/en-us/api-reference/class/Mouse

I am. As I said in the post, MousePos refers to mouse.hit.p.

I am trying to figure out the math required to set the vertical servo’s(the one in the tank turret) current angle to a number corresponding with the position of the mouse.

1 Like

Since the “vertical” servo (I’ll call it the pitch servo) can’t change the yaw (“horizontal” servo), it should pretty much ignore that completely and only focus on what it can change. This lets us simplify the problem to a from-the-side view relative to the turret:

image

Here A is the center of the turret (the point that the servo rotates around) and B is the aim point. α is the target angle of the servo we want to find. The arrow is the “up” vector of the base of the tank (not the turret!), which isn’t always (0, 1, 0) in world space because the tank may be parked on an incline or even flipped upside down, and aiming should probably still work in those cases.

This 2D side view lets us draw in a right angle triangle, which is great because we can use trig relations to find the servo angle.

We can simplify it even further by thinking of the aim point in the frame of reference of the turret, or in other words in its object-space coordinates. This makes sense because the servo angle isn’t relative to the world, but to the rest of the tank! It makes it simpler by putting A at the center of the coordinate space we’re using and removing the whole “up direction of the tank” thing:

image

We can perform that transformation in code like this:

local tankSpaceAimPoint = tankBaseTurretAxisCFrame:ToObjectSpace(worldSpaceAimPoint)

If the relative “forwards” direction of the turret is the Z axis of tankBaseTurretAxisCFrame, then that’s equivalent to the horizontal axis in the diagrams. That means the adjecent side is simply tankSpaceAimPoint.Z, and the opposite side is tankSpaceAimPoint.Y.

From here we can simply use good old SohCahToa because we know the opposite and adjecent sides (hypotenuse is easy to find, too).

Buuuut there’s a better way, atan2. The “normal” atan would give us the correct angle in the 1st and 3rd quadrant, but not the 2nd and 4th. atan2 is the same as atan except it deals with this for us:

local pitchAngle = math.atan2(tankSpaceAimPoint.Y, tankSpaceAimPoint.Z)

You should pretty much be able to just set TargetAngle to that result, but things might be flipped around depending on how exactly you built your tank. Feel free to ask if that’s an issue. Hope this helps!

2 Likes

Hello, thank you for helping me. I attempted to use your math and code, but it isn’t working the way it is intended to.

here is my code:

local function doVerticalMathForRotate(MousePos,Base)
	local tankSpaceAimPoint = CFrame.new(tank.TankTop.TopTube.WorldPosition):ToObjectSpace(MousePos)
	local pitchAngle = math.atan2(tankSpaceAimPoint.Y, tankSpaceAimPoint.Z)
	print(pitchAngle)
	return pitchAngle
end

TopTube is an attachment. I changed MousePos to Mouse.Hit

1 Like

Hi, sorry for the late reply :sweat_smile:

CFrame.new(tank.TankTop.TopTube.WorldPosition)

This makes a new CFrame that has the same position as the TopTube, but no rotation. Could you try this instead?

tank.TankTop.TopTube.CFrame:ToObjectSpace(MouseCFrame)
1 Like

I did it, but it appears to have the same issue as seen in the video above.

Sorry for my late reply, too

local function doVerticalMathForRotate(MouseCFrame,Base)
	local tankSpaceAimPoint = tank.TankTop.TopTube.CFrame:ToObjectSpace(MouseCFrame)
	local pitchAngle = math.atan2(tankSpaceAimPoint.Y, tankSpaceAimPoint.Z)
	print(pitchAngle)
	return pitchAngle
end

Can you upload the tank as a place file or model file so I can take a look at it in Studio?

2 Likes

tankfile.rbxl (66.2 KB)
For some reason I didn’t get the notification of this reply. Sure, though.

Are you still planning on doing it? Sorry for being 2 months late

Sure, I’ll take a look

Putting that print statement in was really helpful BTW ^.^ I’d definitely bet too lazy to try finding the relevant code on my own

Here’s how I debugged it, because that’s more useful than just giving the answer:

Pretty sure the math for getting the angle is right, so the two most likely things to be wrong are:

  • The mouse CFrame
  • The math for converting that to tank-space

Ruled out the former:
image

Let’s try printing the tank-space mouse CFrame:

local function printV3(v3: Vector3, decimals: number)
	local decimals = math.ceil(decimals or 2)
	local numberFormat = ([[%%.%df]]):format(decimals)
	local vectorFormat = ([[(%s, %s, %s)]]):format(numberFormat, numberFormat, numberFormat)
	print(
		vectorFormat:format(v3.X, v3.Y, v3.Z)
	)
end

local function doVerticalMathForRotate(MouseCFrame, Base)
	local tankSpaceAimPoint = tank.TankTop.TopTube.CFrame:ToObjectSpace(MouseCFrame)
	game.Workspace.Indicator.CFrame = MouseCFrame
	printV3(tankSpaceAimPoint, 0)
	local pitchAngle = math.atan2(tankSpaceAimPoint.Y, tankSpaceAimPoint.Z)
	print(("%.1f"):format(pitchAngle))
	return pitchAngle
end

Giving this result:

That can’t be right not right, the tank-space mouse CFrame is definitely not 26 studs to the right of the tank, relative to the tank (i.e. where the laser hits). So that confirms the second possibility.

Looking closely at

local tankSpaceAimPoint = tank.TankTop.TopTube.CFrame:ToObjectSpace(MouseCFrame)

… I noticed that you’re using the CFrame of an Attachment instead of the WorldCFrame. Unfortunate way to make it, the local-space CFrame should have been called “ObjectCFrame” and world-space CFrame should just be “CFrame”. But once you know it’s not hard to deal with. See if it’s fixed:

local tankSpaceAimPoint = tank.TankTop.TopTube.WorldCFrame:ToObjectSpace(MouseCFrame)

Hmm, the mouse CFrame is correct now but the angle is still wrong. The number printing below the Vector3 is the angle, in degrees. It should be slightly above 0, but it’s almost 180. Probably a sign is flipped? Looking closely at the angle calculation:

local pitchAngle = math.atan2(tankSpaceAimPoint.Y, tankSpaceAimPoint.Z)

More or less right, but atan2 is a bit weird in terms of which axis is which, and what signs they should have. Let’s double check. First coordinate is Y coordinate, second is X coordinate (in terms of 2d trig):

image

In this case, Y is how far the aim point is above the tank, so that’s just the Y coord of the tank-space mouse hit position. X is how far in front the aim point is. Because Roblox is weird, the positive Z axis actually represents the backwards direction, not the forward direction. To to get from Z-coordinate to “how much in front”, you need to flip the sign:

local pitchAngle = math.atan2(tankSpaceAimPoint.Y, -tankSpaceAimPoint.Z)

Did it work?

Nope, but the angle seems a lot better. Remembering that some things are annoyingly in degrees and not radians, and noticing that the servo is at a waaay too low angle, try converting to degrees because it
probably doesn’t happen anywhere else in the code:

local function doVerticalMathForRotate(MouseCFrame, Base)
	local tankSpaceAimPoint = tank.TankTop.TopTube.WorldCFrame:ToObjectSpace(MouseCFrame)
	game.Workspace.Indicator.CFrame = MouseCFrame
	local pitchAngle = math.atan2(tankSpaceAimPoint.Y, -tankSpaceAimPoint.Z)
	return math.deg(pitchAngle)
end

:sparkles: et voilà :sparkles:

image

10 Likes

Thanks robama, it worked! And also, thank you for providing the math instead of just sending back a file.

2 Likes