How to make Player face gui position?

I’m trying to make the player always face the direction of a GUI frame, located in a billboard, located inside the player, this is because I cannot use the real mouse, it is locked in the center.

I’ve tried to use this previous topic to simulate Mouse.Hit.Position with a gui to aid in my script, along with using my existing mouse direction script i used before switching over to a custom gui mouse, but i’m not having much luck, when trying to get the value to print repeatedly like I would with Mouse.Hit.Position, it would return

https://devforum.roblox.com/t/any-way-to-make-the-gui-act-like-mousehitp/2823065

I’ve tried doing more research into the topic and how the script works, but this doesn’t seem to be a common thing people have tried to do.

Here is the current script:

local RunService = game:GetService("RunService")

local camera = game.Workspace.CurrentCamera
local Player = game.Players.LocalPlayer
local Character = Player.Character
local HRP = Character.HumanoidRootPart

local Mouse = Player:GetMouse()

HRP:WaitForChild("PseudoMouseLayout")

local PML = HRP.PseudoMouseLayout

PML:WaitForChild("PseudoCursor")

local PseudoCursor = PML.PseudoCursor

local function Get3DPosition()
	local absolutePosition = PseudoCursor.AbsolutePosition

	local unitRay = camera:ScreenPointToRay(absolutePosition.X, absolutePosition.Y)
	local raycastResult = workspace:Raycast(unitRay.Origin, unitRay.Direction * 100)
	
	return raycastResult.Position
	
end


repeat
print(Get3DPosition())
--print(Mouse.hit.Position)
task.wait(.2)
until
2 == 4

I’ve omitted the character pointing in the direction part of the script as i’m mainly focused on how to get the position values to be constantly available

4 Likes

Well, the error you are seeing is because you don’t catch the case where raycastResult does not exist (within Get3DPosition() function). This happens when the ray doesn’t hit something, for whatever reason.

To make the function return nil if the ray hits nothing, you can easily use:

return raycastResult and raycastResult.Position

However, I also have to say that the ray not hitting anything has something to do with the math you are doing for raycasting. I will look harder let you know if I can spot the error.

EDIT:

If PseudoCursor is truly a BillboardGui, then it must have a part or attachment adornee in the world. So you should not need to Raycast to find the position of the cursor in the world, am I wrong?

If this is correct, I can tell you how to align the character right away

Hello again. I made a little GUI with a draggable crosshair that implements this, so you can look at it to hopefully determine where you’re going wrong at:

Creator Store - FakeCursorGui

Notice how when the cursor hit is farther than 100 studs, or can’t reach anything, the result is nil.

EDIT: To be absolutely clear, Player GUI elements should be a member of the PlayerGui object unless you have an explicit need to have it otherwise. For your case, you should stick to using PlayerGui and not putting billboards in the HumanoidRootPart

ah! it seems to be that the issue was because I changed my camera height and wasn’t accounting for it in the script, it reads fine now that I changed the height from 100 to 500. My newest issue now though is that it isn’t playing well with the cursor follow script

If PseudoCursor is truly a BillboardGui, then it must have a part or attachment adornee in the world. So you should not need to Raycast to find the position of the cursor in the world, am I wrong?

PseudoCursor is a frame under a Billboard gui, which is stored within a player’s HumanoidRootPart which is cloned from server storage, this is so the cursor will move with the player’s position.

As for my new issue, here is the results after changing the height, pretty much what i’d want:

But when I try to incorporate the Mouse follow script, this is when problems occur.

Here is how the code looks now:

local RunService = game:GetService("RunService")

local camera = game.Workspace.CurrentCamera
local Player = game.Players.LocalPlayer
local Character = Player.Character
local HRP = Character.HumanoidRootPart

local Mouse = Player:GetMouse()

HRP:WaitForChild("PseudoMouseLayout")

local PML = HRP.PseudoMouseLayout

PML:WaitForChild("PseudoCursor")

local PseudoCursor = PML.PseudoCursor

local function Get3DPosition()
	local absolutePosition = PseudoCursor.AbsolutePosition

	local unitRay = camera:ScreenPointToRay(absolutePosition.X, absolutePosition.Y)
	local raycastResult = workspace:Raycast(unitRay.Origin, unitRay.Direction * 500)
	
	return raycastResult.Position
	
end

-- this code is only activated when i want to verify that the above function is working
--[[repeat
print(Get3DPosition())
--print(Mouse.hit.Position)
task.wait(.2)
until
2 == 4]]

RunService.RenderStepped:Connect(function()
	local PMousePos = Get3DPosition()
	local HRPPos = HRP.Position

	PMousePos = Vector3.new(PMousePos.X, HRPPos.Y, PMousePos.Z)

	HRP.CFrame = CFrame.new(HRPPos, PMousePos)
end)

Try incorporating my original suggestion, like this:

local RunService = game:GetService("RunService")

local camera = game.Workspace.CurrentCamera
local Player = game.Players.LocalPlayer
local Character = Player.Character
local HRP = Character.HumanoidRootPart

local Mouse = Player:GetMouse()

HRP:WaitForChild("PseudoMouseLayout")

local PML = HRP.PseudoMouseLayout

PML:WaitForChild("PseudoCursor")

local PseudoCursor = PML.PseudoCursor

local function Get3DPosition()
	local absolutePosition = PseudoCursor.AbsolutePosition

	local unitRay = camera:ScreenPointToRay(absolutePosition.X, absolutePosition.Y)
	local raycastResult = workspace:Raycast(unitRay.Origin, unitRay.Direction * 500)
	
    -- catch case where the ray does not hit anything
    -- now there will be no error when it happens
	return raycastResult and raycastResult.Position
	
end

-- this code is only activated when i want to verify that the above function is working
--[[repeat
print(Get3DPosition())
--print(Mouse.hit.Position)
task.wait(.2)
until
2 == 4]]

RunService.RenderStepped:Connect(function()

	local PMousePos = Get3DPosition()

    -- Cursor is not above anything
    if not PMousePos then return end


	local HRPPos = HRP.Position

	PMousePos = Vector3.new(PMousePos.X, HRPPos.Y, PMousePos.Z)

	HRP.CFrame = CFrame.new(HRPPos, PMousePos)
end)

I changed

return raycastResult.Position

to

return raycastResult and raycastResult.Position

And I added the line

if not PMousePos then return end


No more errors appear in output! although this code somehow broke my camera script, possibly because it uses a camera part, which I don’t know if you have to change the above code for, so I reverted back to a part-less system.

While the character now faces the general direction of the cursor, it noticeably seems to be off by abit

edit: just for extra checking, the anchor point isn’t in the center of the cursor, although when I changed it to be so, the offput would be even worse in some cases

make sure the anchor offset is set to {0.5, 0.5}, other than that there is no discernable error to me. The code for actually rotating toward the target is without error, for certain (and your code for finding the cursor position in world should be good, too)

image
anchor offset is indeed .5, .5, i’m starting to think maybe it’s a math problem instead

I can verify that your math is absolutely correct. The issue could be with how that CFrame.new(Vector3, Vector3) constructor operates.

In the CFrame documentation, it states that that constructor can give unstable values for high pitch values.

change the RenderStep code after this section of code:

if not PMousePos then return end

to this:

local towardCrosshairXZ = (PMousePos - HRP.Position) * Vector3.new(1, 0, 1)

towardCrosshairXZ = towardCrosshairXZ.Unit

	
local lookVectorXZ = HRP.CFrame.LookVector * Vector3.new(1, 0, 1)


HRP.CFrame *= CFrame.fromRotationBetweenVectors(lookVectorXZ, towardCrosshairXZ)

and see if it removes the instability

Unless I didn’t follow the instructions, the issue persists, this is how the code looks now

local RunService = game:GetService("RunService")

local camera = game.Workspace.CurrentCamera
local Player = game.Players.LocalPlayer
local Character = Player.Character
local HRP = Character.HumanoidRootPart

local Mouse = Player:GetMouse()

HRP:WaitForChild("PseudoMouseLayout")

local PML = HRP.PseudoMouseLayout

PML:WaitForChild("PseudoCursor")

local PseudoCursor = PML.PseudoCursor

local function Get3DPosition()
	local absolutePosition = PseudoCursor.AbsolutePosition

	local unitRay = camera:ScreenPointToRay(absolutePosition.X, absolutePosition.Y)
	local raycastResult = workspace:Raycast(unitRay.Origin, unitRay.Direction * 500)

	-- catch case where the ray does not hit anything
	-- now there will be no error when it happens
	return raycastResult and raycastResult.Position

end

-- this code is only activated when i want to verify that the above function is working
--[[repeat
print(Get3DPosition())
--print(Mouse.hit.Position)
task.wait(.2)
until
2 == 4]]

RunService.RenderStepped:Connect(function()

	local PMousePos = Get3DPosition()

	-- Cursor is not above anything
	if not PMousePos then return end


	local towardCrosshairXZ = (PMousePos - HRP.Position) * Vector3.new(1, 0, 1)

	towardCrosshairXZ = towardCrosshairXZ.Unit


	local lookVectorXZ = HRP.CFrame.LookVector * Vector3.new(1, 0, 1)


	HRP.CFrame *= CFrame.fromRotationBetweenVectors(lookVectorXZ, towardCrosshairXZ)

end)

I’m wondering if possibly the issue might be with the script behind the pseudo cursor itself

Hm. I am really starting to run out of ideas here :sob: I know that the script I can see works, because I just copy-and-pasted it and It works in a fresh game with a draggable UI element with the right anchor offset.

You are probably right that it is something else. The only idea that comes to mind is to try using ViewportPointToRay instead of ScreenPointToRay in the Get3DPosition() function