Trouble with aiming a turret to the mouse on one axis while staying parallel to a surface

Hello, in a game I am working on, you can place turret-like guns onto a character (a pillbaby lol), and then they should be able to pivot on one axis to look at the player’s mouse position, whilst also staying parallel to the character that it is welded to.

The main issue I am having is getting the turret to rotate all 360 degrees correctly, as I have tried limiting the rotation to strictly the “main” axis (the Z axis) while using CFrame.lookAt, but that produces a result where sometimes the Y axis rotation also plays a factor in the rotation, resulting in just using the Z produce incorrect results where it will not always follow your cursor. I know this is happening as I made the lookat freely rotate the turret as a test, and turning it behind the character caused it to also turn on the Y axis.

Furthermore, I need to make the turret stick to your body while maintaining its current rotation, but I do have some idea as to how I could do this, such as raycasting to get the normal or basing it off of the Torso’s rotation, since they will be correlated in some way; however, help with this as well would also be appreciated, and I can’t implement this yet until the first problem is sorted out.

Here is the current code; I have tried limiting the lookat property of the CFrame.lookAt to use the gun’s Z position, but as I said earlier, this produces some Y rotation that, without it, results in an incomplete rotation of the turret to lock onto the cursor. I have omitted parts that include rotating the turret to be aligned with the body if placed on the other side of the baby and just a couple of lines that turn on and off WeldConstraints when pivoting the gun, as these are not relevant.

local pos = gun:GetPivot().Position
local pointpos = mouse.Hit.Position
		
local lookat = CFrame.lookAt(pos, pointpos)

local x, y, z = lookat:ToOrientation()
local px, py, pz = gun:GetPivot():ToOrientation()

lookat = CFrame.new(lookat.Position) * CFrame.Angles(px, y, z) --setting y to py results in what i described above

gun:PivotTo(lookat)

I suspect that I will have to somehow combine both the Z and Y strictly from the lookat while still snapping it to the side of the baby. I have sent a screenshot of how one may place the turret on the character, with a rough drawing of the axis of rotation I am looking for.


Any help would be appreciated, thanks!

  1. Don’t use Mouse.Hit as that is deprecated
  2. but if it works it works :stuck_out_tongue:
local pos = gun:GetPivot().Position -- or the position you want it to be on the body
local pointpos : Vector3 = mouse.Hit.Position

pointpos = Vector3.new(pointpos.X,pos.Y,pointpos.Z)

local lookat = CFrame.lookAt(pos, pointpos)

gun:PivotTo(lookat)

This code make it so it can only do like parallel to the ground…
I have re-read, I realize this isn’t blender, Z is forward not up haha
change line 4 to

pointpos = Vector3.new(pointpos.X,pointpos.Y,pos.Z)

This is definitely on the right track, but rotating it behind the character causes it to turn around like this. Also, I will probably switch to UserInputService after I get this working now that you say that, it’s just mouse.Hit.Position is so much easier and straightforward to use lol.

I am not sure if I understand correctly, I don’t see how user input service will help in this case. The “intended” way to get mouse.hit.position is to use get the direction then raycast in that direction.
do you want to add 10 lines for basically no reason? if it works it works lol

about the picture, so the maximum it can go should be straight up or straight down?
in this case

local gunCF : CFrame = gun:GetPivot()
-- the math: (https://www.mathsisfun.com/algebra/vectors-dot-product.html)
local lookVector : Vector3 = gunCF.RightVector -- or LookVector
-- ^ I am guessing X is forward based on your statement about Z being axis of rotation
local relativeVector : Vector3 = pointpos - pos
if lookVector:Dot(relativeVector) < 0 then
    -- it is behind the player
    pointpos = Vector3.new(pos.X,pointpos.Y,pos.Z) -- straight above pos or smth
end

about the picture, so the maximum it can go should be straight up or straight down?

If you were thinking that I meant I wanted the rotation to be clamped, then no, I want a whole 360 degrees of rotation on that axis, including if you point behind you, but I probably was a little vague with that wording. The bottom is pointing outwards in the image.

oh oops I didn’t notice that

in that case you can specify the direction up is when using lookat
CFrame.lookAt(pos,pointpos,Vector3.yAxis)

I tried using Vector3.yAxis but it didn’t change anything, so I then tried using Vector3.xAxis, but this rotated the problem by 90 degrees, with the back pointing outwards whenever you point downward. Likewise, setting it to -Vector3.xAxis points the back outwards when pointing up, and -Vector3.yAxis does the same thing when pointing forward. Basically, this just shifts the problem to another spot on the rotation.

Very… intresting…
Since it appear your game would be 2.5d (the player can only move sideways?) I suggest making the gun actually point toward the audience slightly by a not noticible amount (assuming game is 2.5d)
pointpos = Vector3.new(pos.X + 0.01,pos.Y,pointpos.Z) -- or -0.01

Well, the game actually isn’t 2.5d, I just took the images at roughly the same angle, so pointing it out a little would not work. Sorry for the confusion. The problem that I’ve had is that the bottom of the turret that should be against your character is turning to face out.

In that case we then have to do a bit more math for relative CFrames
after the line that does pointpos = ...
the position and gun is now on the same plane, we need only add a positive or negative of

local gunCF : CFrame = gun:GetPivot()
pointPos += gunCF.RightVector * 0.01 -- or negative 0.01

which will result them not being on the same plane if that make sense
probably a better way to do this in general…
but then I realize this also might not work since now I think about it, being backwards is technically also a valid solution since it pointed at the cursor…
have we tried Vector3.new(0,0,1) for the up of the lookat?

Yeah, I unfortunately get the same result, but I see what you tried to do. I’ll try that for the lookat upvector

Update: It causes the gun to turn like this, which is not what I want, as I want the base to stick to the character.

I guess I have thought of another solution, since for reasons I do not comprehend (because I am bad at math) this only happen when behind, which is useful, since this correlate with the dot product being negative, so we just rotate it back when dot product is negative

local gunCF : CFrame = gun:GetPivot()
local lookVector : Vector3 = gunCF.RightVector -- or LookVector
local relativeVector : Vector3 = pointpos - pos
if lookVector:Dot(relativeVector) < 0 then
    -- it is behind the player
    lookat *= CFrame.Angle(0,0,math.rad(180)) -- or whichever axis that was...
end

I tried changing the “lookVector” variable to be RightVector, LookVector, and even UpVector, but all didn’t do anything except for UpVector, which just caused erratic movement. Printing the dot product that you showed, none actually go below zero at any point during the rotation except for the UpVector, which is at random times and is probably irrelevant.

Well is there a way to get the Head’s LookVector to check which way front is?
I suspect this behavior is because gun is at the previous rotation so it is always forward…

The gun has a main part in it that serves as the PrimaryPart and thus pivot for the model. The front surface of that part is facing towards the torso when the back isn’t pointing out the wrong way, so I believe the lookvector would be going towards the torso from the gun.

So in the script, use the torso’s lookvector to check the dotproduct if that is possible, since the torso does not rotate up and down.

Well, the goal of the gun is to also always have it be parallel to the surface that it is originally placed on, so I don’t think this would work as the two faces would always be at the same positions, but I could be wrong. I don’t see how the torso’s lookvector would be very helpful in this case because the player can just point their mouse cursor in any direction.

wait actually you could just do this by getting the gun’s Upvector and the surface’s Upvector, if they are pointing the same way (dot product being positive) then the gun isn’t upside down

local upvector = gun:GetPivot().UpVector
local torsovector = player.Character.Torso.CFrame.UpVector
		
if upvector:Dot(torsovector) > 0 then
	lookat *= CFrame.Angles(0, 0, math.pi)
end

This is the code I just added, but the CFrame.Angles that I am multiplying lookat by isn’t working great, as the pi usage for Z in the code shown causes the turret to flip erratically 180 degrees constantly. I also tried changing the X and Y to be multiplied by pi, but Y just caused the rotation to be inverted to where you actually pointed and X was also erratic.

realized that i may have meant to make it < 0, but I also tried that and got similar results

I meant the surface normal and not the up.
In this case it would be the torso’s rightvector no? since that is the up of the surface it is attached to