Replicating 2D Map?

Hi, I’m trying to replicate this map;
I want to turn a 3D map into a 2D map as shown below.
I’m attempted doing this using ScrollingFrame but had no luck - Below is an example created by @EpicDogMan10.

EDIT:

There’s a lack of content around high-lighting on this sort of subject - If you read further, you’ll see a more in-depth explanation of this process by @daftcube .

3 Likes

Oh yea your gonna need to ray trace this one. Now im not talking like rtx on im just saying add a pixel for where it hits if you were to cast a ray from a virtual camera point. I’ve never coded this in boblox but I have made a ray tracer on the GPU. (Note this one produces like 1000’s of planets in a sense compared to you only needing to do like 100).

Can you just use a Frames | Documentation - Roblox Creator Hub?

Otherwise just map object locations to GUI locations.

What part are you having trouble with?

1 Like

Introduction: This is going to be a complex UI

Making a map like this will involve a complex UI. You were on the right track when you were using a ScrollingFrame. However, I think you might have run into problems when trying to do the mathematics to transform positions in the world to positions that could be used to position icons on the UI.

My post aims to teach how to conduct the math for these transformations. Of course, creating a complete map like will require more code to create each icon, and, if you want it to show player ship positions, code to update the positions of player icons every frame. However, this should get your math sorted.

Position Transformations

For calculating the positions of the icons on the screen, there is absolutely no need to do advanced raytracing or anything like that. Raytracing would be OVERKILL for something like this. Raytracing involves casting a ray for every pixel on the screen. We don’t need to raytrace because we already know the position of every object we want to place on the map just from its position in the world.

If I’m understanding this correctly, all you want to do is make a reduced representation of a 3d game world on a 2D plane. In other words, we need to map the X and Z coordinates of objects within the game world to X and Y coordinates on a map GUI. All that we need to do this is some pretty trivial transformation math.

Say I have a scrolling UI frame of a set canvas size. To render this map, I will need to create a frame/ui element for each object of interest, be it a planet, moon, or ship. Then, I need to have some way to transform the objects in the 3d world to UDim2 coordinates that represent an offset from the canvas’s origin.

We can use a Linear Mapping operation to map a value from one range to another. I talked about this in another response, but another application will really show its versatility.

--[[
    Source from RosettaCode:
    @param a1 (number) The lower bound of the range to map from
    @param a2 (number) The upper bound of the range to map from
    @param b1 (number) The lower bound of the range to map to
    @param b2 (number) The upper bound of the range to map to
    @param s (number) The value in the range [a1,a2] to be mapped to [b1,b2]
    @returns (number) The value 's' mapped from [a1,a2] to [b1,b2]
--]]
function map_range( a1, a2, b1, b2, s )
    return b1 + (s-a1)*(b2-b1)/(a2-a1)
end

We can use this function to create a more specialized function that maps the position of objects in the bounds of the game world to a position in the bounds of a UI. But before we can do that, you need to define what the bounds of the game world are. Objects outside this range will not render correctly (unless you write code to handle that, but that will be left as an exercise for the reader), so be sure to pick a minimum and maximum position that bounds the entire game world. The following code sets such constants to a 2000x2000x2000 box.

-- This constant will define the minimum position of the game world.
local MIN_POSITION = Vector3.new(-1000,-1000,-1000)
-- This constant will define the maximum position of the game world.
local MAX_POSITION = Vector3.new(1000,1000,1000)

Now that we have our bounds, we can write a function that transforms a position in the game world to a position on the screen. We can’t render in 3 dimensions on a standard flat UI, so we will just ignore every object’s Y coordinate, opting to use X and Z instead.

function TransformWorldPositionToUIPosition(position : Vector3, canvasSizeX : number, canvasSizeY : number)
    local xCoordinateOnUI = map_range(MIN_POSITION.x, MAX_POSITION.x, 0, canvasSizeX, position.x)
    local yCoordinateOnUI = map_range(MIN_POSITION.z, MAX_POSITION.z, 0, canvasSizeY, position.y)

    return UDim2.new(0, xCoordinateOnUI, 0, yCoordinateOnUI) 
end

This function should give us the position of where an object of interest in the 3d world should be on the 2D map.

Zooming - More Transformation Math

I’m about out of time and need to get back to studying, but if you want to know how to further implement zoom transformation math like what was displayed in your example gif, I’m happy to give a follow-up post.

Happy programming!

4 Likes

I probably should have clarified daft that I wasnt saying to write a shader program I was just saying to move frames dependent on if the shader picked up anything

1 Like

I understand, although I still think something like that would be both much more complex and eat many more cycles than are required to solve this problem.

Roblox’s UI isn’t optimized for rapid updates every frame. While declarative UI frameworks like Roact allow programmers to work as if they are rendering UI elements on a per-frame basis, such frameworks have taken great care to optimize UI updates and are still restricted by the underlying performance of Roblox UI instances.

While shaders are intriguing things to make in Roblox, they shouldn’t be considered as a serious solution for a majority of use-cases as Lua’s software-constrained scripting environment simply doesn’t provide performance comparable to environments that are much closer to the hardware – especially when simpler avenues to solving the problem exist…

I agree with you. I just suggested it as we could use the vectors defined in the ray tracer as the basis of our transformation matrices like what you were suggesting with zooming in. However if you could do the exact same with that magical function (still dont understand how it works lol) then yea I think that’d be better then

1 Like

Hey! The concept behind this looks promising, however when bringing it into reality I’m met with the following error:
Players.OiiTaru.PlayerGui.ScreenGui.Ctrl:8: invalid argument #2 (UDim expected, got number)

local ScreenGui = {}
ScreenGui.Gui = script.Parent
ScreenGui.Main = ScreenGui.Gui["Main"]
ScreenGui.Map = ScreenGui.Main["Map"]
ScreenGui.Content = ScreenGui.Map["Content"]

function map_range( a1, a2, b1, b2, s )
	return b1 + (s-a1)*(b2-b1)/(a2-a1)
end

local MIN_POSITION = Vector3.new(-1000,-1000,-1000)
local MAX_POSITION = Vector3.new(1000,1000,1000)

function TransformWorldPositionToUIPosition(position : Vector3, canvasSizeX : number, canvasSizeY : number)
	local xCoordinateOnUI = map_range(MIN_POSITION.x, MAX_POSITION.x, 0, canvasSizeX, position.x)
	local yCoordinateOnUI = map_range(MIN_POSITION.z, MAX_POSITION.z, 0, canvasSizeY, position.y)

	return UDim2.new(0, xCoordinateOnUI, 0, yCoordinateOnUI) 
end

local WarpPoint = workspace:FindFirstChild("WarpPoint")
if WarpPoint then
	for _,Child in pairs (WarpPoint:GetChildren()) do
		if Child:IsA("BasePart") then
			
			local Marker = script.Marker:Clone()
			Marker.Parent = ScreenGui.Content
			Marker.Position = TransformWorldPositionToUIPosition(Child.Position, ScreenGui.Content.CanvasSize.X, ScreenGui.Content.CanvasSize.Y)
			
		end
	end
end

EDIT:
Believe I patched that part however this is the end result:


The markers seem to appear in the top left of the screen.

local ScreenGui = {}
ScreenGui.Gui = script.Parent
ScreenGui.Main = ScreenGui.Gui["Main"]
ScreenGui.Map = ScreenGui.Main["Map"]
ScreenGui.Content = ScreenGui.Map["Content"]

function map_range( a1, a2, b1, b2, s )
	return b1 + (s-a1)*(b2-b1)/(a2-a1)
end

local MIN_POSITION = Vector3.new(-1000,-1000,-1000)
local MAX_POSITION = Vector3.new(1000,1000,1000)

function TransformWorldPositionToUIPosition(position, canvasSizeX , canvasSizeY)
	local xCoordinateOnUI = map_range(MIN_POSITION.x, MAX_POSITION.x, 0, canvasSizeX, position.x)
	local yCoordinateOnUI = map_range(MIN_POSITION.z, MAX_POSITION.z, 0, canvasSizeY, position.y)
	
	return UDim2.new(0, xCoordinateOnUI, 0, yCoordinateOnUI) 
end

local WarpPoint = workspace:FindFirstChild("WarpPoint")
if WarpPoint then
	for _,Child in pairs (WarpPoint:GetChildren()) do
		if Child:IsA("BasePart") then
			
			local Marker = script.Marker:Clone()
			Marker.Parent = ScreenGui.Content
			spawn(function()
				while true do wait()
					Marker.Position = TransformWorldPositionToUIPosition(Child.Position, ScreenGui.Content.CanvasSize.X.Scale, ScreenGui.Content.CanvasSize.Y.Scale)
				end
			end)
		end
	end
end

This is on the right track.

I do apologize, my code was written rather hastily because I was studying for finals and just happened to stumble upon this thread.

Consider the following line:

TransformWorldPositionToUIPosition(Child.Position, ScreenGui.Content.CanvasSize.X.Scale, ScreenGui.Content.CanvasSize.Y.Scale)

The function I wrote expects the second range in terms of pixels rather than screen scale. So, you want to use the absolute window size instead.

To kind of expand why the scale won’t work with the code, our code proportionally maps a value in one range to another. For example, if we had a value 2 in the range [0, 10], and we wanted to “map” that value onto the range [0, 100], the value we would get is 20. Scale is a value in terms of the range [0, 1], so basically what you’re saying with your current code is that we want to map an x and y coordinate from the range [-MIN_POSITION, MAX_POSITION] to [0, 1].

If you have any more questions, let me know and I’ll do my best to answer them.

1 Like

This is brilliant! Thank you so much;
Sorry for delayed response - I’ve been hounded at work the past few hours.

I was wondering if we could touch upon the follow:

  • Rescaling / Zoom feature.
  • Center-ing the GUI so that the “Middle” of the GUI (0.5, 0.5) can be the Players / Ships position?

EDIT:-

Despite the blue orb being at (0, 0, 0) - It’s represented on the map differently.

1 Like