Right way to do a 2D map for 3D game?

#Godot 4.5

I’m trying to render a 2D map for my 3D game as an independent resource, and then show the full thing when I bring up a map screen overlay… but also show a sub section of it as a mini-map in the corner of the screen during normal gameplay. Seems like this ought to be easy, but so far I haven’t figured out a way to do this. Mainly, I just need to figure out which nodes are appropriate for doing this (and maybe how to arrange the in the scene tree).

I created a demo map as a sprite child of a Node2D, then made that a child of a Subviewport. This simulates my map being rendered to a Subviewport. So I have my demo map. Now I want to show it in two different ways (full map and mini-map). I can show the whole thing in a texture rect, but I can’t seem to find a way to display a subsection of it except with a camera2D but that affects both the full and mini-map. I tried an atlas texture, but that’s not dynamic (well, unless I recreate it each frame; maybe that’s what I should do)?

How else might I do this?

For my game, have a 2D image of the map under the playable world, visible only a select render layer
then, above the player is a camera that only sees said layer, its in a subviewport and uses some code to always stay above the player
its output is rendered to a TextureRect in the corner of the screen

My map will be dynamically created and updated with moving icons and things though, so it can’t just be an image. Unless I render into an image or texture I guess. That could work.

I guess there’s a million ways I could do this. Maybe it’s best if I just render the mini-map independently in its own subviewport. I could still have a single render function that just takes a size and call it with one size for the mini-map and one for the full map.

What I don’t get is that how do you do multiplayer? I would have thought that you have the 2D or 3D world and you could just instantiate multiple cameras into it. What if you wanted split screen multiplayer. How would you do that? In some sense, displaying my map in two different ways it like that.

each object that is present in the 3D world could also have a “mapview” counterpart, which is only visible to the minimap camera, that way even moving objects would be updated in real time

as for multiplayer, i am not quite sure, never dealt with it myself but i am pretty sure that each player could have their own minmap camera and minimap

I’ve got a minimap/radar view in Stahldrache; it’s a 3D world with a 2D tilemap for the ground. The levels are generated procedurally.

What I do is:

  • generate an image by sampling the terrain on a grid (the tilemap makes this fairly trivial)
  • the image is displayed in a 2D subviewport with its own camera; this lets me rotate/scale it independently as needed
  • dynamic stuff (radar blips, waypoints…) on the map are done with 2D sprites in the minimap viewport

It’s worked fairly well for me.

1 Like

I think if I want to proceed I just need to go with the most obvious working method… I’ll just render the mini-map as a separate pass through the game grid matrix based on player location. It won’t be much of a performance hit. Meanwhile, I need to read the editor docs to understand things a bit better I guess.

If it’s just a static sprite, you can use Control elements for your UI and you’ll have a lot more control over it in the long run than you would rendering it as 2D inside a Viewport.

Ok, I figured out exactly how do it right. My question wasn’t really how to make/draw a map, but how to draw it once and display it in two places with different zooms and things. It’s basically the same problem as making a 2 player split screen game. Here’s the node layout and script I used:

UI (CanvasLayer)
  MapScreen (ColorRect)
    SVC (SubviewportContainer)
      SV (Subviewport)
        MapCam (Camera2D)
        FullMap (Node2D)
          [Whatever nodes you use to draw your world/map]
  MiniMap (ColorRect)
    SVC (SubviewportContainer)
      SV (Subviewport)
        MiniMapCamp (Camera2D)

The color rects are just to provide a small border

The SVC’s are used to size the view (I set anchors to 0.1 and 0.9 to leave a border)

To make the SV’s fill the SVC’s:
-On the SVC’s enable Stretch
-On the SV’s enable Size 2D Override Stretch

Finally, you just need a tiny script (unless there’s a way to set this in the editor I don’t know of). I attached this to my UI node:

extends CanvasLayer

@onready var map_vp: SubViewport = $MapScreen/SVC/SV
@onready var mini_map_vp: SubViewport = $MiniMap/SVC/SV

func _ready() -> void:
	mini_map_vp.world_2d = map_vp.world_2d

Basically, it’s just two subviewports sharing the same world_2d which you can position and resize however you like (such as half and half for multi-player split screen, or in my case, within my map screen node/scene and in the corner of my primary game view). Then once they’re in position on the screen, you change the two camera offsets and zooms to control precisely what is visible in the two viewports.

I can see potential future problems for my own use case. If I want to enable controls on the map (like tool tips for mouse highlighting) that might end up in the mini-map. So I may end up needing to do an independent rendering for the mini-map, but it feels nice to get it done the way I wanted for now.

I should add that anyone trying to follow in my footsteps later… positioning control nodes (which most of these are) is a trick in and of itself if you’re not familiar with how anchors and things work. I didn’t cover that here.

2 Likes