i don’t know if you have your question fully answered yet, but yes “it” is possible to do that with a viewportframe. The easiest way (i can think of) would be to use a function that can convert a mouse position into a 3d position and then from there it should be fairly straight forward as to how you can rotate your character toward your mouse (there are plenty of resources on it). As for the conversion it’s actually quite simple, the main idea is that we need to first find the width and height of the viewing frustum’s far clipping plane, then we use our mouse’s coordinates relative to the viewportframe accordingly to find the mouse’s position as if it were transformed into world space as opposed to screen space. Such a conversion could look like:
local ScreenHeight = Cam_Size.Y
local ScreenWidth = Cam_Size.X
local AspectRatio = (ScreenWidth/ScreenHeight)
local Tangent = math.tan((math.rad(FieldOfView) * .5)) ---- tan(rad(fov/2))
local fx = ((2 * Depth) * (MousePos.x /(ScreenWidth-1)) -(Depth*1)) --- convert mouse pos.X between -Depth and Depth
local fy = ((2 * Depth) * (MousePos.y/( ScreenHeight-1)) -(Depth*1))
local X = (AspectRatio * Tangent * fx ) --- "AspectRatio * Tangent" is our width
local Y = (-Tangent * fy) --- we can use -Tangent as our "height"
local Z = -Depth --- Z is just negative depth
----X, Y, Z is our position however we need to make it relative to our camera!
local CF = camera.CFrame * CFrame.new(Vector3.new(NX, NY, NZ))
Complete Function
local GuiService = game:GetService("GuiService")
local function LocalPos(Pos,Viewport)
return Pos - Viewport.AbsolutePosition
end
local function ScreenToWorldSpace(Pos, viewport, camera, Depth, Gui_Inset)
Pos = LocalPos(Pos - GuiService:GetGuiInset() , viewport)
local Cam_Size = Vector2.new(viewport.AbsoluteSize.X , viewport.AbsoluteSize.Y - 36)
local Height = Cam_Size.Y
local Width = Cam_Size.X
local AspectRatio = (Cam_Size.X/Cam_Size.Y)
local Cam_Pos = camera.CFrame.Position
local Scale = (Depth or 3)
local fov =math.rad(camera.FieldOfView)
local Tangent = math.tan((fov * .5))
local fx = ((2 * Scale) * (Pos.x /(Width-1)) -(Scale*1))
local fy = ((2 * Scale) * (Pos.y/(Height-1)) -(Scale*1))
local NX = ((AspectRatio * Tangent * fx ))
local NY = (-Tangent * fy)
local NZ = -Scale
local Translatedcf = (camera.CFrame) * CFrame.new(Vector3.new(NX, NY, NZ))
return Translatedcf.Position
end
With this information all we need to do is make the “character” look at CF
, there are multiple ways to do this, but for simplicity, i just used CFrame.new(Pos, LookAtPos)
(you can replace it with a custom look at function, if you are worried about it being deprecated) and used CFrame:ToOrientation()
so that i can limit the rotation of the character’s Head, to get an okay outcome of:
Which was made by doing essentially doing:
local Character --- character
local Root -- HumanoidRootPart of character
local Head = Character.Head
local Max_X, Min_X = 2000, -10
local Max_Y, Min_Y = 2000, -2000
local function LimitRotation(CF)
local rx, ry, rz = CF:ToOrientation() --- using ToOrientation to make limiting rotation easier
rx = math.clamp(rx, math.rad(Min_X), math.rad(Max_X)) ---- clamp rx angle
ry = math.clamp(ry, math.rad(Min_Y), math.rad(Max_Y))---- clamp ry angle
return rx, ry, rz
end
RunService.RenderStepped:Connect(function()
local pos = ScreenToWorldSpace(UIS:GetMouseLocation(), vpf, Camera,3) --- function from above, changed depth to 3 so that everything would rotate toward the returned position just a little more
local rx, ry, rz = LimitRotation(CFrame.new(Head.Position, pos))
local Rotation = Root.CFrame - Root.Position -- remove position from the CFrame, so it is just rotation
Head.CFrame = (CFrame.new(Head.Position) * Rotation) * CFrame.fromOrientation( rx , ry - math.rad(180) , rz)-- make sure that the Head CFrame is also being rotated according to the actual Character rotation
local HumanoidCF = CFrame.new(pos) * CFrame.new(0, 0, 10) -- move pos back, which lessens the "strength" of the rotation
Character:SetPrimaryPartCFrame(CFrame.new(Root.Position, HumanoidCF.Position))--- rotate entire character
end)
Full Code
local UIS = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
local vpf = script.Parent.ViewportFrame
local Camera = Instance.new("Camera")
local Character = vpf.Character --- r15 character
Character.Parent = vpf
local Root = Character:WaitForChild("HumanoidRootPart")
Camera.Parent = vpf
vpf.CurrentCamera = Camera
Camera.CFrame = Root.CFrame * CFrame.new(0, 0, 8)
local function LocalPos(Pos, Viewport)
return Pos - Viewport.AbsolutePosition
end
local function ScreenToWorldSpace(Pos, viewport, camera, Depth, Gui_Inset)
Pos = LocalPos(Pos - GuiService:GetGuiInset() , viewport)
local Cam_Size = Vector2.new(viewport.AbsoluteSize.X , viewport.AbsoluteSize.Y - 36)
local Height = Cam_Size.Y
local Width = Cam_Size.X
local AspectRatio = (Cam_Size.X / Cam_Size.Y)
local Cam_Pos = camera.CFrame.Position
local Scale = (Depth or 1)
local fov = math.rad(camera.FieldOfView)
local Tangent = math.tan((fov * .5))
local fx = ((2 * Scale) * (Pos.x / (Width - 1)) - (Scale * 1))
local fy = ((2 * Scale) * (Pos.y / (Height - 1)) - (Scale * 1))
local NX = ((AspectRatio * Tangent * fx ))
local NY = (-Tangent * fy)
local NZ = -Scale
local Translatedcf = (camera.CFrame) * CFrame.new(Vector3.new(NX, NY, NZ))
return Translatedcf.Position
end
local Head = Character.Head
local Max_X, Min_X = 2000, -10
local Max_Y, Min_Y = 2000, -2000
local function LimitRotation(CF)
local rx, ry, rz = CF:ToOrientation()
rx = math.clamp(rx, math.rad(Min_X), math.rad(Max_X))
ry = math.clamp(ry, math.rad(Min_Y), math.rad(Max_Y))
return rx, ry, rz
end
RunService.RenderStepped:Connect(function()
local pos = ScreenToWorldSpace(UIS:GetMouseLocation(), vpf, Camera, 3)
local rx, ry, rz = LimitRotation(CFrame.new(Head.Position, pos))
local Rotation = Root.CFrame - Root.Position
Head.CFrame = (CFrame.new(Head.Position) * Rotation) * CFrame.fromOrientation( rx , ry - math.rad(180) , rz) -
local HumanoidCF = CFrame.new(pos) * CFrame.new(0, 0, 10)
Character:SetPrimaryPartCFrame(CFrame.new(Root.Position, HumanoidCF.Position))
end)
Here is an accompanying place file: ViewportTest.rbxl (31.0 KB)
Chances are if i didn’t explain the Screen to World space conversion very well, i would recommend checking out this tutorial that talks more in depth about how conversion between spaces (world & screen) work.