Figured it out, I needed to make a plane in front of the camera then get the intersection of the ray to the plane.
local function MoveChar()
local ray = Cam:ScreenPointToRay(PosUI2d.X, PosUI2d.Y, 0)
-- making a plane in front of the camera
local DIST = DIST_FROM_CAM
local camPos = Cam.CFrame.Position
local camLook = Cam.CFrame.LookVector
local planePos = camPos + camLook * DIST
local planeNorm = camLook
-- getting the intersection of the ray and the plane
local numer = (planePos - ray.Origin):Dot(planeNorm)
local denom = ray.Direction:Dot(planeNorm)
if math.abs(denom) < 1e-6 then
return -- the ray is nearly parallel to le plane
end
local t = numer / denom
local Pos3D = ray.Origin + ray.Direction * t
-- DEBUG
local x = Instance.new("Part")
x.Position = Pos3D
x.Size = Vector3.new(1,1,1)
x.Color = Color3.new(1,0,0)
x.Anchored = true
x.Parent = workspace.Tmp
-- update CFrame (if needed)
Att.CFrame = CFrame.new(Pos3D)
end