Calculating the offset between Center of Camera and a part? (Aim Down Sights)

Currently I’m working on an Aim Down Sight system with the ViewModel that I created.
This is currently what do I get:

						char.Torso.ToolGrip.C0 = char.Torso.ToolGrip.C0 * CFrame.new(-1,0,0)
						ViewModelTorso["Left Shoulder"].C0 = ViewModelTorso["Left Shoulder"].C0 * CFrame.new(0,0,1)
						ViewModelTorso["Right Shoulder"].C0 = ViewModelTorso["Right Shoulder"].C0 * CFrame.new(0,0,-1)

For now, I pretty much just simply offset the Left Shoulder, Right Shoulder and ToolGrip Motor. ToolGrip is a custom Motor that connects the tool between tool’s handle and player’s character torso. It is visible in the ViewModel but not a descendant of the ViewModel, it’s a child of the player’s character.

There’s an AimPart inside the tool, What I want to achieve is the AimPart will be positioned in the center of the Camera, here’s the pseudo steps:

  • Calculate the offset between AimPart and the Center of Camera in CFrame
  • Let’s say the offset is (-1,0,0), then multiply it to current CFrame (Like I did above)
  • Then also multiply this offset to Left Shoulder and Right Shoulder such that they will be positioned correctly.

Currently what I did (Code above) does not put the tool in the center the screen, it didn’t even utilize the AimPart inside the tool and here’s the result:
Normal:
image
ADS (Red Part is the AimPart)
image

I have all the pseudo steps but I don’t really know how to perform this in code :woozy_face:

2 Likes

The way I would go about doing this is setting the primary part of the model to the aim part, the lerping it to the camera, then setting the primary part back to it.

You can use several camera functions to do this. First of all, you want to calculate the depth of the aim part from the camera. You can use WorldToViewportPoint for this. It returns a Vector3 and the Z component is the depth from the camera.

Next you want to determine the distance in pixels. The screen position is returned with the X and Y components (correlating to the X and Y pixel position on screen). You’ll subtract the target X Y from the center X Y (ScreenSize/2) to get the distance in pixels.

Now you’ll use ScreenPointToRay to generate a new 3D world position at the original depth. This will return a Ray. You’ll then use CFrame.new(ray.Origin + ray.Direction, ray.Origin + ray.Direction * 2) to get the ending CFrame of the ray.

Finally, you’ll want to find the 3D offset between the original position and the new position. And then you can offset the entire ViewModel by this amount. This should perfectly center the part on the screen and keep it at the exact depth.

Fun fact: These camera functions work on the server if you create new cameras

2 Likes

Right now I’m using toObjectSpace to caculate the Vector3 position in studs between the Aim Part and the character’s head.

                            -- inside run service renderstepped
				local offset = char:FindFirstChildOfClass("Tool").ADS.CFrame:toObjectSpace(char.Head.CFrame).Position
				local offsetX = offset.X
				local offsetY =	offset.Y
				local offsetZ = offset.Z
				print(offset)

               -- inside a debounce such that C0 will not multiply 60 times a second
				char.Torso.ToolGrip.C0 = char.Torso.ToolGrip.C0 * CFrame.new(offsetX,offsetY,offsetZ)
			    ViewModelTorso["Left Shoulder"].C0 = ViewModelTorso["Left Shoulder"].C0 * CFrame.new(offsetZ,offsetY,-offsetX)
				ViewModelTorso["Right Shoulder"].C0 = ViewModelTorso["Right Shoulder"].C0 *CFrame.new(offsetY,offsetZ,-offsetX)

For some reasons, the Left Shoulder and Right Shoulder is not same as ToolGrip Motor, I have to manually change the XYZ values and make some of them negative, at least now it works:

However, since it calculate the offset between the character’s head and the Aim Part, the camera pretty much does not face towards the Aim Part. So now I follow the above steps do find the Center of the Camera and replace using character head with it?

1 Like

Well what you’re trying to do is move the aim part to the center of the screen. So to center it to the screen you’d need to center the pixels on the screen. Instead of the character’s head position you would replace it with the ray’s end point. This will perfectly center the aim part on the screen.

How do I perform this step?

					local worldPoint =  char:FindFirstChildOfClass("Tool").ADS.Position
				local vector, inViewport = cam:WorldToViewportPoint(worldPoint)
				 
				local viewportPoint = Vector2.new(vector.X, vector.Y)
				local depth = vector.Z

What I would suggest doing is moving the gun independently from the arms, and calculate the arms’ joints after moving the gun to the desired CFrame.

Yes, the tool is connected to the Torso, arms and tool are independent. But how do I “move the gun to the desired CFrame” and using the Aim Part?

Sorry my bad the pixel difference isn’t necessary. You just need to use ScreenPointToRay at the screen’s center position and the depth you got previously. (The screen center in pixels is ScreenGui.AbsoluteSize/2)

AimPart would be where you’d want the gun to be relative to the camera; you’d simply move AimPart to the Camera’s CFrame.

So do I do this?

				local ray = cam:ScreenPointToRay(mouse.X, mouse.Y, 1)
				local offset = char:FindFirstChildOfClass("Tool").ADS.CFrame:toObjectSpace(CFrame.new(ray.Origin, ray.Origin + ray.Direction)).Position

I’ve already achieved the Screen Center position in Vector3, but seems that it’s still not reaching the position, do I have to do something with this?

char.Torso.ToolGrip.C0 = char.Torso.ToolGrip.C0 * CFrame.new(offset)

The white block represents the center of the screen, and the outlined part represents the AimPart, my goal is this two parts to collide each other.

Edit: Solved it!
This took me sometime to discover about the XYZ values, when I multiply C0’s value to offset it, it will position incorrectly if you put in XYZ format, probably due to the orientation of different parts, here’s my final code:

			    local ray = cam:ScreenPointToRay(mouse.X, mouse.Y, 1)
							
				local offset = char:FindFirstChildOfClass("Tool").ADS.CFrame:toObjectSpace(CFrame.new(ray.Origin, ray.Origin + ray.Direction)).Position
				local offsetX = offset.X
				local offsetY =	offset.Y
				local offsetZ = offset.Z

					   char.Torso.ToolGrip.C0 = char.Torso.ToolGrip.C0 * CFrame.new(offsetX,offsetY,offsetZ)
						ViewModelTorso["Left Shoulder"].C0 = ViewModelTorso["Left Shoulder"].C0 * CFrame.new(offsetZ,offsetY,-offsetX)
						ViewModelTorso["Right Shoulder"].C0 = ViewModelTorso["Right Shoulder"].C0 *CFrame.new(-offsetZ,offsetY,offsetX)
6 Likes

I had few questions…

I changed the formula from C0 = C0 * offset into C0 = offeset, where offset is the Vector3 value caculated by using ToObjectSpace (With the method you provided and suggested above) and I ran it inside a RunService RenderStepped.

Can you explain the difference between both formula? I find something really confusing, whenever I change the offset from the old formula (C0 = C0 * offset) into something like (C0 = C0 * 0.1,0,0), it will keep multiplying and eventually the tool will be out of the screen since it’s ran inside a RenderStepped and I keep multiplying 0.1 by every step, but how come this doesn’t happen when I multiply the offset?

When I use the new formula (C0 = offset), this happens. The C0 keep resetting. I checked there’s no code to reset the C0 back to it’s initial CFrame (0,0,0). When I implement a debounce which pause them from running every step, this stopped happening.

For unknown reasons, whenever I use the formula and I change the offset value to something that’s not offset (Such as 0.1,0,0), it will offset the arms and it went completely off.

https://gyazo.com/7a98eeb6bed480e14ddd15a1ebcf74fd

Sorry if my explanation is poor, I can provide you a repro file through DM.

The C0 property offsets Part1 from Part0. The C1 property subtracts from that offset. So the position of Part1 is equal to this: Part0.CFrame * C0 * C1:Inverse()

When doing C0 = C0 * x you constantly add that offset on top of itself meaning the offset will jump to infinity. This is because C0 is not reset.

Multiplying a CFrame acts like adding except it’s relative to the target’s direction. (-Z is forwards, Positive Y is upwards, and positive X is left)

2 Likes