I’m currently cooking up a minimap for a game I’m making. As of right now, I’m using a combination of a Part to designate where “North” is, and a conversion of that part using WorldToScreenPoint to allow myself to convert it to a degree. However it’s proven to be unreliable, I was wondering if there’s a different way I could achieve this effect?
I don’t get what you mean by the way you’re doing it. Get the camera direction vector and take the atan2() of the X and Z part, this will give you an angle that works.
I decided to make a similar compass to see if I can get it to work. Getting it to work properly in the case of positive depth was quite easy by using WorldToScreenPoint. Yours apprently doesn’t always work even in this case. The angle should be calculated using the direction from the compass center to the screen position of the part. Did you do this or something else?
I’m not sure what would be the most logical way to calculate the direction when the depth is negative. I tried a few different approaches. The first one was just calculating the direction from the compass to the point given by :WorldToScreenPoint(), just like in the case of points with positive depth. When I realised that this projects points on the left of the camera to the right side of the screen and points below the camera to the upper side of the screen, I decided to flip the direction such that points on the left of the camera are on the left side of the screen and points and also the vertical direction changes correspondingly.
I still wasn’t happy with the results. The further behind the part was, the closer the calculated screen position was to the center of the screen. This caused the compass to point towars the center when moving far from the part. Next, I decided to use ortographic projection for the points with negative depth. In ortographic projection the coordinates are not scaled based on depth. This, however, has the problem that when the depth changes from positive to negative (when the camera moves), there’s a sudden jump from really big screen coordinates (outside the screen) to much smaller screen coordinates. This is because, when the depth approaches zero, the coordinates calculated using prespective projection approach infinity, but the coordinates calculated with orthographic projection don’t get bigger.
The final approach I tried was considering the screen coordinates of the part infinite when the depth is negative. In perspective projection, the direction of the projected point from the center of the screen is its direction from the camera in the x and y axes (left-right axis and vertical axis) of the camera. Thus, I decided that the screen position vector of the part is considered to be an infinitely long vector with this direction. The compass needle direction would then be calculated by subtracting the compass screen position (finite) from this screen position (infinite). However, when subtracting a finite vector from an infinite one, the finite one has practically no effect on the result. Thus, we can consider the compass direction to be the direction of this infinite vector. And said direction is the direction of the (x, y) position vector of the part in the coordinate system of the camera.
There may very well be better, more logical approaches that I just didn’t think of.
Here’s the code. The final approach is the one used in this code. You may need to edit the angle conversion based on where the north pointer is pointing when its Rotation property is 0. Mine is pointing to the right (positive screen x-axis direction).
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local GuiService = game:GetService("GuiService")
local compassOffsetFromScreenEdge = 25
local compassDiameter = 75
local partPos = Vector3.new(50, 20, -75)
local screenGui
local mainFrame
local compassPointer
local partPosFrame
local part
local function createCompass()
screenGui = Instance.new("ScreenGui")
screenGui.Name = "CompassScreenGui"
screenGui.ResetOnSpawn = false
mainFrame = Instance.new("Frame")
mainFrame.Name = "MainFrame"
mainFrame.AnchorPoint = Vector2.new(1, 1)
mainFrame.Position = UDim2.new(1, -compassOffsetFromScreenEdge, 1, -compassOffsetFromScreenEdge)
mainFrame.Size = UDim2.fromOffset(compassDiameter, compassDiameter)
local mainFrameUiCorner = Instance.new("UICorner")
mainFrameUiCorner.CornerRadius = UDim.new(.5, 0)
mainFrameUiCorner.Parent = mainFrame
local compassMiddle = Instance.new("Frame")
compassMiddle.Name = "CompassMiddle"
compassMiddle.BackgroundColor3 = Color3.new(1, 1, 1)
compassMiddle.Size = UDim2.fromOffset(10, 10)
compassMiddle.ZIndex = 2
compassMiddle.AnchorPoint = Vector2.new(.5, .5)
compassMiddle.Position = UDim2.fromScale(.5, .5)
local compassMiddleUICorner = Instance.new("UICorner")
compassMiddleUICorner.CornerRadius = UDim.new(.5, 0)
compassMiddleUICorner.Parent = compassMiddle
compassMiddle.Parent = mainFrame
compassPointer = Instance.new("Frame")
compassPointer.Name = "CompassPointer"
compassPointer.BackgroundColor3 = Color3.new(1, 0, 0)
compassPointer.BorderSizePixel = 0
compassPointer.Size = UDim2.new(.5, 0, 0, 5)
compassPointer.Position = UDim2.fromScale(.75, .5)
compassPointer.AnchorPoint = Vector2.new(.5, .5)
compassPointer.Parent = mainFrame
partPosFrame = Instance.new("Frame")
partPosFrame.BackgroundColor3 = Color3.new(1, 0, 0)
partPosFrame.Size = UDim2.fromOffset(5, 5)
partPosFrame.AnchorPoint = Vector2.new(.5, .5)
partPosFrame.Parent = screenGui
mainFrame.Parent = screenGui
screenGui.Parent = Players.LocalPlayer.PlayerGui
end
local function getCompassDirection()
local camera = workspace.CurrentCamera
local partPosInCameraSpace = camera.CFrame:Inverse() * part.Position
if partPosInCameraSpace.Z < 0 then
local screenPointWithDepth = camera:WorldToScreenPoint(part.Position)
local screenPoint = Vector2.new(screenPointWithDepth.X, screenPointWithDepth.Y)
partPosFrame.Visible = true
partPosFrame.Position = UDim2.fromOffset(screenPoint.X, screenPoint.Y)
local mainFrameCenterPosition = mainFrame.AbsolutePosition + Vector2.new(mainFrame.Size.X.Offset, mainFrame.Size.Y.Offset) * .5
return (screenPoint - mainFrameCenterPosition).Unit
end
partPosFrame.Visible = false
return Vector2.new(partPosInCameraSpace.X, -partPosInCameraSpace.Y).Unit
end
local function updateCompass()
local screenDirectionFromCompassCenter = getCompassDirection()
compassPointer.Position = UDim2.fromScale(.5 + screenDirectionFromCompassCenter.X * .25, .5 + screenDirectionFromCompassCenter.Y * .25)
local angleInDegrees = math.atan2(screenDirectionFromCompassCenter.Y, screenDirectionFromCompassCenter.X) * 180 / math.pi
compassPointer.Rotation = angleInDegrees
end
local function createPart()
part = Instance.new("Part")
part.Name = "CompassTestPart"
part.Anchored = true
part.CanCollide = false
part.Position = partPos
part.Parent = workspace
end
createPart()
createCompass()
RunService.RenderStepped:Connect(updateCompass)
Edit: I had named the angle variable angleInRadians
although it was the angle in degrees.
The end result is far better than what I was trying before (which was using atan2)
Old Code:
r_s.Heartbeat:Connect(function()
local vector, onscreen = camera:WorldToScreenPoint(workspace.NORTH_NODE.Position)
local screenpoint = Vector2.new(vector.X, vector.Y)
script.Parent.Rotation = math.deg(math.atan2(screenpoint.Y - script.Parent.AbsolutePosition.Y, screenpoint.X - script.Parent.AbsolutePosition.X)) + 90
end)
The old code provided some jank, so thank you for assisting with this.
End Result of new Code:
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.