I have been making games with Godot for a couple of months now, and one of the hardest things to wrap my head around has been Viewports. In this series of posts, I’ll try to explain what I’ve learned and how you can use viewports in your own games to deliver effects like multiple windows into the same scene, viewports applied as textures to other nodes, and more. (My work has been with viewports in 2D, but most of these concepts also apply to 3D.)
First of all, what is a Viewport? The class documentation says it “Creates a sub-view into the screen.” Godot’s own Viewport tutorial says “Think of Viewports as a screen that the game is projected onto.” But what does that mean in practice?
A Viewport is a destination for the pixels that your game objects create. Once pixels are drawn in a viewport, you can display them or use them in any other way you might use a Texture.
Every scene in Godot has a default viewport, referred to as the Root Viewport. The Root Viewport is the actual root of the scene tree – the node that appears at the top of the scene tree in the Scene tab is actually a child of the Root Viewport.
Viewports display the pixels of all of their children that are not another viewport. Therefore, since all of your nodes are children of the Root Viewport, by default all of your nodes will be displayed there. This way, Godot behaves the way you intuitively expect: if you add a visible node to the current scene, it appears on the screen.
Viewports have a size, which is a rectangle that determines the number of pixels displayed by that viewport. The size of the Root Viewport is determined by the Display:Window:Size settings for your project. Simply put, the size of the Root Viewport is the size of your main game window (or screen if running in fullscreen mode).
A viewport has a transform associated with it. This transform is applied to all of the CanvasItem children of the viewport – basically any drawable object. By default, the transform has its origin at (0,0) (the top left corner of the window/screen), no rotation, and no scaling, so objects are drawn as you would expect – an object with a position of 100,100 will start drawing a hundred pixels from the top and left of the window.
That was a lot of words to explain that the Root Viewport does pretty much what you expect.
- It’s the size of your game’s window.
- If you put nodes in it, their pixels appear on the screen.
- It doesn’t modify those pixels in any way.
One common way to alter the default behavior of the Root Viewport is to create a Camera2D and attach it to one of your nodes. Now, instead of staying nailed at (0,0), the origin of your viewport will be updated to follow your node around. Your game’s main window is now a Root Viewport-sized view onto your game’s canvas, which starts out centered on the parent of the Camera2D.
Every Camera2D is associated with a Viewport. If you don’t set one explicitly with
custom_viewport(), it will be set to the nearest Viewport above the Camera2D in the scene tree (which is usually the Root Viewport).
There can be more than one camera associated with a Viewport, but only one camera at a time can be “current”. The view of the current camera is what appears in the Viewport. When you make a camera current with
make_current(), all other cameras associated with the Viewport become inactive.
There’s one last thing we need to understand before we can really start playing around with Viewports: World2Ds. Stay with me here, this gets a little weird.
A World2D has “everything pertaining to a 2D world”. It’s a complete environment or game space for a 2D project. A World2D has its own 2D physics space, canvas for drawing, and sound environment.
Okay, but why should we care? Because every Viewport has an associated World2D.
The Root Viewport’s World2D is where all of your nodes live by default. This is the world where their physics calculations are made, their pixels are drawn, and their sounds play. If your game only uses the Root Viewport, you never have to think about the World2D. Life is easy and fun.
However, when you create a new Viewport, by default, it has its own World2D.
What does this mean in practice? It means that a freshly created Viewport node has some behavior that may not be intuitive.
Nodes added as children of an unmodified new Viewport node:
- Will not appear on the screen
- Won’t interact physically with nodes in the rest of the scene
- Can’t be heard if they play sounds
Doesn’t this make a new Viewport node useless by default? Sort of. But you can make it useful in a few different ways.
- You can assign an existing World2D to it, turning it into “another view” on that world.
- You can put it in a ViewportContainer, which will give you a window into its private World2D.
- You can get the pixels of its World2D as a ViewportTexture, and display them on any node that accepts a Texture.
In my next post, I’ll walk you through a sample project that provides examples of each of these use cases.