Okay, so here’s an illustration of situation from a top-down perspective:
You can imagine the black bold arc segment is like the compass bar in the GUI. The black dots along the circle show how the marker’s position along the compass bar depends on the angle between the camera’s look direction (the black arrow) and the vector from the players position (blue circle in the center) to the marker point (ends of the blue arrows).
At the same time you might want to display cardinal directions (N, S, W, E). For those you don’t need to calculate the angle based on the position, they just have a fixed angle.
We’ll need to calculate the angle of a vector2 in 2d. For that we have atan2
. One thing to know is that atan2
takes a y
value first and then an x
value. This shows atan2
in a Cartesian coordinate system:
As you can see it’s the angle between the x axis and the point. That’s fine, except we kind of expect positive angles to be a turn to the right (in the GUI compass bar), because positive X is to the right in GUI space. No problem, just negate the output of atan2
to get the desired results.
It’s convenient to work with the markers’ world points relative to the camera, so first we convert any given point to the cameras object space. Then get the angle the camera would have to turn to face the point:
function rotationToFaceTowardsPoint(camera: Camera, point: Vector3): number
local relativePoint = camera.CFrame:PointToObjectSpacePoint)
local angle = -math.atan2(relativePoint.X, -relativePoint.Z)
return angle
end
The NSWE direction stuff works like this:
local NORTH = Vector3.xAxis
local SOUTH = -NORTH
local EAST = Vector3.zAxis
local WEST = -EAST
function rotationToFaceInDirection(camera: Camera, direction: Vector3): number
local relativeDirection = camera:VectorToObjectSpace(direction)
local angle = -math.atan(relativeDirection.X, -relativeDirection.Z)
end
Great, we can compute the angle the camera would need to turn to face any point or direction. Now to place the markers on the GUI compass bar. Like in the first figure, you’ll need to choose some “field of view” for the compass bar. I chose 90, but it could be any angle really and you should play around with it to see what feels nice.
Say the center of the bar is 0 degrees, the left edge is -fovAngle/2
and the right edge is +fovAngle/2
. Since GUI positions work from 0 = left edge to 1 = right edge, we need to convert:
function rotationToGuiPosition(rotation, fovAngle)
return rotation / fovAngle + 0.5
end
You can check that it works like this:
for a = -45, 45, 15 do
print(a, math.floor(rotationToGuiPosition(a, 90)*100)/100)
end
yep, the left of the field of view (-45) is the left of the GUI element (0), and same for the right (+45, 1).
Then you just have to create GUI elements inside the compass bar that somehow know what point or direction to track, and update them every frame by setting their position accordingly. You can set ClipsDescendants to true on the compass bar to have markers disappear from view, or clamp the position in the range [0, 1] to have them stay at the edge of the bar. Or even get fancy and calculate some appropriate transparency for each compass marker.