How to position an Object in a ViewportFrame relative to Mouse position?

I’ve tried looking on the forums to try and find a solution to this, but they’re never directly for this issue.


I need that gray part to be in the middle of the mouse basically.

Im like 90% there but I cant figure out why its like offset from the mouse and i’m honestly just stumped haha. Also tried multiplying the distance by the ray but then it just overexaggerates mouse movement.

	local MousePos = UIS:GetMouseLocation()
	
	local InsetPos = MousePos - Viewport.AbsolutePosition - GuiService:GetGuiInset()
	local X = InsetPos.X / Viewport.AbsoluteSize.X
	local Y = InsetPos.Y / Viewport.AbsoluteSize.Y
	
	local ray = Camera:ViewportPointToRay(X,Y)
	local Position = ray.Origin + ray.Direction
	
	Viewport.MopObject.CFrame = CFrame.new(Position.X,0,Position.Z)

The part will always be along 0 in the Y axis, it should only move X and Z.

Is there some sort of way to do this accurately???

1 Like

Here is something to get you started:

local mouse = game:GetService("Players").LocalPlayer:GetMouse()

local cam = Instance.new("Camera")
cam.CFrame = workspace.Cam.CFrame
cam.FieldOfViewMode = Enum.FieldOfViewMode.Diagonal
cam.FieldOfView = 50
script.Parent.ViewportFrame.CurrentCamera = cam

--//calculate cam fov and aspect ratio
local fov = math.rad(cam.FieldOfView)
local aspectRatio = script.Parent.AbsoluteSize.X/script.Parent.AbsoluteSize.Y

--//construct a plane to move the object on, with predefined width and length
local distance = 10

--//calculate dimensions of plane
local yDistance = 2*distance*math.tan(fov / 2)
local xDistance = yDistance*aspectRatio
local plane = Vector2.new(xDistance,yDistance)

--//calculate origin of plane (where origin is top left)
local originPosition = cam.CFrame.Position+(cam.CFrame.LookVector*distance)+Vector3.new(yDistance/2,0,-xDistance/2)

--since I am using a fullscreen implementation, i have to cancel out roblox screen insets
local inset = game:GetService("GuiService"):GetGuiInset()

game:GetService("RunService").RenderStepped:Connect(function()
	local posScale = Vector2.new((mouse.X+inset.X)/script.Parent.AbsoluteSize.X,(mouse.Y+inset.Y)/script.Parent.AbsoluteSize.Y)
	
	local toAdd = (plane*posScale)
	
	script.Parent.ViewportFrame.Part.Position = originPosition+Vector3.new(-toAdd.Y,0,toAdd.X)
end)

This is a full screen version, and will need tweaking to work in different dimensions, but should give you a good idea.

Think two planes. There is the plane that you see (the 2d screen), and the plane that the object will move over, which exists in 3d space with a fixed y. Simply put, this works by calculating where your mouse is relative to the origin of the screen, and applying that % to the part, relative to the origin of the 3d plane.

The most complicated part is calculating the 3d plane, in other words, where in 3d space, is the top left of the viewport. To do this, we first raycast away from the camera however far the plane is, in the direction of the camera. This will center us on the 3d plane. Then, we calculate the size of the plane with trig. Then its just subtracting half the size from the center of the plane, to get the origin (top left).

Also be aware, I manually wrote in the distance variable. This is just how far the camera is from the center of the desired 3d plane.

1 Like

Camera.ViewportPointToRay only really works for the current workspace camera. You just need a few extra steps to calculate the position yourself:

local MousePos = UIS:GetMouseLocation()

local InsetPos = MousePos - Viewport.AbsolutePosition - GuiService:GetGuiInset()
local X = InsetPos.X / Viewport.AbsoluteSize.X
local Y = InsetPos.Y / Viewport.AbsoluteSize.Y
X = 2*X - 1 -- convert X from [0, 1] to [-1, 1]
Y = 2*Y - 1 -- convert Y from [0, 1] to [-1, 1]

-- you should define these variables outside of your RunService loop,
-- but I'm putting them here so that you can see them
local height = Camera.CFrame.Position.Y
local angle = math.rad(Camera.FieldOfView / 2)
local aspectRatio = Viewport.AbsoluteSize.X / Viewport.AbsoluteSize.Y
local maxY = height * math.tan(angle) -- this is how far the part should be positioned along the Z axis to be at the top edge of the ViewportFrame
local maxX = maxY * aspectRatio -- this is how far the part should be positioned along the X axis to be at the right edge of the ViewportFrame

-- if X is equal to 1, that means the part is 100% to the right (or 100% of maxX)
-- if X is equal to -0.4, that means the part is 40% to the left
local newX = maxX * X
local newY = maxY * Y

local Position = Vector3.new(newX, 0, newY)
Viewport.MopObject.CFrame = CFrame.new(Position.X,0,Position.Z)
	


Note that this only works for your use case where the camera is looking downwards and the part is being positioned specifically on the xz-plane.

2 Likes

this is basically what i did but a lot cleaner. i would go with this implementation.

1 Like

Absolutely perfect, thank you!

I knew using rays wasn’t necessary for this at all lol I was definitely overcomplicating it, thank you for commenting it too.

Also thank you @mc3334!