3D Positioning to 2D Positioning

Howdy developers!

Today I come to you seeking assistance. I’ll try to keep it short. I’ve been trying to make a ‘Heartbeat sensor’ for my game, however I’m having trouble figuring out how to convert the 3D positions of players into 2D positions on this screen. I’ve already been able to determine that the enemy players are close enough to you using .magnitude and Dot with lookVector to determine they are ‘in front’ of you and not behind you.

My goal is to have only players in front of you show up, and I’ve successfully nullified the Y axis in the .magnitude part. By that I mean I want to have an enemy player to show up on the Heartbeat Sensor as 5 studs away from you for example, even if they’re 5 studs away from you but 100 feet below or above you.

Anyway, I have no idea how to convert these 3D positions into 2D positions on this screen. How would you go about this? I appreciate any and all replies!

Here’s an example of my ideal final result. Lower player is ‘you’, and the green dot on the screen is you as well.
image

6 Likes

This sounds great! However, I can’t help, but I just came here to give you feedback! The monitor looks nice! Gives us a good detail of how it’s going to work! I hope you will get some help! :slight_smile:

4 Likes

Get the object space of the object relative to the player, transform that by the monitor’s parameters, remove all those that aren’t in the FOV of the scanner i.e. if they’re behind you

1 Like

How would that be done code-wise?

1 Like

You can use Camera:WorldToScreenPoint() (ignores GUI inset) or Camera:WorldToViewport() (accounts for GUI inset).

1 Like

This won’t work unless you instantiate a new camera from above with a down vector focus

1 Like

Here’s something I just quickly threw together, edit it for your implementation:

local Screen = game.Workspace.Part.SurfaceGui
local Point  = Screen.Frame --> Needs to have a AnchorPoint of 0.5, 0.5
local A, B   = game.Workspace.A, game.Workspace.B

game:GetService("RunService").Heartbeat:connect(function ()
	local abs  = Screen.AbsoluteSize * 0.5
	local a, b = A.CFrame, B.CFrame
	local rel  = a:toObjectSpace(b)
	
	Point.Position = UDim2.new(
		0.5 + rel.x/abs.x,
		0,
		0.5 + rel.z/abs.y,
		0
	)
end)

Obviously you would need to offset the Y axis for your implementation as you have the centre point at the bottom of the SurfaceGui.

1 Like

Hey, thanks for the code! I’ve been messing around with what you gave me but my results seem to be positions like UDim2.new(2.503, 0, -0.533, 0), which don’t appear on the screen. I set the frame AnchorPoint to 0.5,0.5 but the dot never showed up. Setting it to 0,0 at least gave me the result shown below, although the range is much less than 250.

    local abs = gui.Bg.AbsoluteSize * 0.5
	for _,v in pairs(game.Players:GetChildren()) do
		local vChar = v.Character
		if vChar and vChar ~= char then --checking if both characters exist
			local vCharCF = vChar.PrimaryPart.CFrame
			vCharCF = vCharCF + Vector3.new(0, (charCF.p.Y - vCharCF.p.Y), 0) --setting enemy player Y to same as client
			local vec = (charCF.p - vCharCF.p)
			local dot = -(vec:Dot(charCF.lookVector) / vec.magnitude)
			if vec.magnitude < 250 and dot > 0 then --checking enemy is closer than 250 studs and is in 'front' of you
				local newP = gui.Player:Clone()
				local rel = charCF:toObjectSpace(vCharCF)
				newP.Position = UDim2.new(0.5 + rel.x / abs.x, 0, 0.5 + rel.z / abs.y, 0) --Isocortex's code
				newP.Visible = true
				newP.Parent = gui.Bg.Frame
			end
		end
	end

2 Likes

You need to account for the distance you’re scanning for:

local SCAN_DISTANCE = 250
newP.Position = UDim2.new(
	0.5 + rel.x / (abs.x * ((SCAN_DISTANCE*2)/abs.x),
	0, 
	0.5 + rel.z / (abs.y * ((SCAN_DISTANCE*2)/abs.y), 
	0
)

Again, I’m still using the anchor point at 0.5, 0.5 - I’m assuming yours doesn’t appear as your using Scale to resize the Frame object?

The reason your dot disappears on your video though is surely because of the LookVector operation. Don’t forget to account for the Y offset as mentioned in my previous post though, you need to lower the centre point to match your texture

heartbeat_scriptinghelpers.rbxl (20.5 KB)

1 Like

Ah, thanks for that! The demo helps a lot. I’m having trouble though getting the center point of the sensor to be at the bottom center rather than the center of the screen. How would you change the numbers to enact this?

local Screen = game.Workspace.Part.SurfaceGui
local Point  = Screen.Frame --> Needs to have a AnchorPoint of 0.5, 0.5
local A, B   = game.Workspace.A, game.Workspace.B

local SCAN_DISTANCE = 250
local HEIGHT        = 0.75

game:GetService("RunService").Heartbeat:connect(function ()
	local abs  = Screen.AbsoluteSize * 0.5
	local a, b = A.CFrame, B.CFrame
	local rel  = a:toObjectSpace(b)
	
	local vec = a.p - b.p
	local dot = -(vec:Dot(a.lookVector) / vec.magnitude)
	if vec.magnitude < 250 and dot > 0 then
		Point.BackgroundColor3 = Color3.new(1, 1, 1)
		Point.Position = UDim2.new(
			0.5 + rel.x/(abs.x * ((SCAN_DISTANCE*2)/abs.x)),
			0,
			HEIGHT + rel.z/(abs.y * ((SCAN_DISTANCE*2)/abs.y)),
			0
		)
	else
		Point.BackgroundColor3 = Color3.new(1, 0, 0)
	end
end)

Although this assumes that you have anchorpoint of 0.5,0.5 - will be 1 - HEIGHT if you have 0, 0.

Hope that’s finally provided you with the solution :slight_smile:

This is perfect! Thank you for your help with this:D It should work in the game now.