Hour 21. Viewports and Canvas
What You’ll Learn in This Hour:
Viewport and its use cases
Managing your rendering through Canvas Layers
Mixing 2D and 3D scenes
Adding a split-screen feature to a game
In this hour, we start playing with viewports. So far, we’ve talked a lot about how to assemble different nodes types into a Scene Tree that
represent our world. However, given that we are not building a weather forecast simulation, well want to render things by displaying images on
the user’s screen. This is what the viewport is about: opening a window into our world to the user.
While this seems a very generic role, this also means that viewports allow us to implement a wide range of features:
Create advanced GUI
Mix multiple scenes into one
Take screenshots of your game
Mix 2D and 3D
Add advanced effects like rearview mirror
Allow split-screen
In short, if you’re planning to create more than simple demos, you will most likely need viewports sooner or later, so let’s choose sooner and
start learning about them.
Viewports
If you’ve already read Hour 13 by now, you should have a rough idea what a viewport is and what Godot does with it.
The viewport is seen as a bridge between our world scene and the low-level parts of Godot like the visual server responsible for rendering a
scene as a frame.
Most of the time, the viewport is a shy beast: so far, we’ve hardly encountered it, and all our projects rendered fine. This is because the very
root node of our scene (the one we get by calling get_node(‘/root’) inGDScript) is itself a
Viewport automatically created byGodot when
the game starts (Figure 21.1).
FIGURE 21.1
Once our game starts, the root node is always a viewport that’s automatically added.
Given that the viewport is going to render each frame of our world, we have to tell it where to place itself and what to face before doing the
rendering. This is done by adding a
Camera (or
Camera2D if you’re doing 2D) child node. Note that a viewport can have multiple
cameras, but only one at a time is configured as current.
Obviously, if a viewport doesn’t have a camera set, it will stick with the default configuration (remember the blue rectangle that is present by
default in the 2D editor). This is fine for simple 2D games like the one we did in Hour 5, but quickly falls short when we need more advanced
features like scrolling. If you do a 3D game, however, a
Camera is mandatory, or your screen will be black.
So far, if talking about the camera made sense, viewport seems more like a technical detail of how Godot handles rendering. This is because
our use cases were fairly simple, so let’s imagine something a bit more complicated.
For example, we are building a 3D action game, and at some point, the player enters a room with a television screen on. A decade ago, we
had no choice but to display fixed images on the TV screen due to power limitations. However, things have changed (and Godot has arrived),
so a much more modern approach is to create a scene somewhere, render it, and display the result on the TV screen (hence, etymologically
speaking, it’s really a television).
Translated into Godot, this means we will create a new
Viewport node, assign it a child scene so it’s played on the TV, and a
Camera node that acts as a real television camera. Finally, back in our main scene, we will connect the television screen
MeshInstance
material’s texture to the
Viewport’s texture (see Listing 21.1).
LISTING21.1 Connecting to a Viewport’s Texture
Click here to view code image
func host_game(port):
var viewport = get_node(‘Viewport’)
# For a 2D Sprite
var sprite = get_node(‘Sprite’)
sprite.texture = viewport.get_texture()
111111111
22222222# For a 3D MeshInstance
var mesh = get_node(‘Mesh’)
mesh.material_override.albedo_texture = viewport.get_texture()
Of course, in our main scene we also have a
Camera representing the player view and configuring the root
Viewport (the one
created automatically byGodot) that produces the frame displayed on the user’s screen.
One more thing that’s worth mentioning is that the
Viewport node has anOwn World property that can be set to either use or not use the
main scene.
To illustrate this, let’s continue with our example: instead of a television displaying a show, there’s a security monitor connected to a camera (a
real one this time) somewhere in the level. To do that, the security camera becomes another view on the world (just like the player has its own).
The same way the player has a
Viewport (which happened to be the default root one, but it doesn’t matter) and a
Camera to render
the world, we will create a
Viewport with only a
Camera child node for the security camera.
On the contrary, to render a show completely unrelated to our main scene, enabling the Own World property prevents us from mixing the
scenes together and rendering bad code, especially considering global configuration to a scene like
WorldEnvironment’s skybox (Figure
21.2).
FIGURE 21.2
Different Scene Trees for the TV show (left) and the security camera. Note that.TelevisionViewport’s Own World property is enabled, unlike the
SecurityCameraViewport.
Another great feature of viewport is that it allows us to mix 2D and 3D together! This is really useful every time you want to add a 2D minimap
in a 3D game, for example.
You should get the idea now: first we create a
Viewport node child of our main scene containing all the nodes for our subscene.
Then we create a surface (a
Sprite for 2D, a
MeshInstance in 3D) in our main scene and connect its texture (or its material’s texture
for a
MeshInstance) to the
Viewport’s texture.
Note that 2D and 3D are rendering separately, so you don’t have to use the
Viewport’s Own World property to avoid mixing your 2D and
3D scenes (well, unless your 3D scene inside your 2D main scene uses a viewport on 2D scenes . . . you’re not doing that, right?).
One last cool thing about viewports: they are not limited to graphics. You can enable 2D or 3D positional audio (using the
Camera to
configure the position of your listener) and have your viewport play sound accordingly. You can also choose whether or not the inputs sent by
the player are passed to a
Viewport, or you can send arbitrary inputs to it.
NOTE
Watch Out With Input Singleton
When processing inputs, most of the time, we use the Input singleton (for example, doing Input.is_key_pressed(KEY_UP). However,
as a singleton, this is something that is global to the entire game and thus ignores the viewport’s “Disable Input” property!
The bottom line is, to use viewport’s “Disable Input,” you should only process your input with a gui_input(event) method (Figure 21.3).
FIGURE 21.3
Viewport properties to configure in order to share (or not) with the parent Viewport.
TRY IT YOURSELF
Take Screenshots from Within a Game
1. Take any of the games we already created and start modifying the input handling to trigger a _screenshot() function when the
enter key is pressed.
2. In the _screenshot(), we get back the main viewport node, select its texture, and extract its data as a new
Image.
3. The texture outputs an upside-down image, so we have to flip its on its y axis.
4. Finally, we can simply save the
Image as a file. See Listing 21.2.
LISTING21.2 Screenshot Function
Click here to view code image
func _screenshot():
var img = get_viewport().get_texture().get_data()
img.flip_y()
img.save_png(‘screenshot.png’)
111111111
22222222It’s that easy!
NOTE
Vflip
Due to the wayGodot does its rendering, by default, the Viewport’s texture outputs an upside-down image. To solve this, you can enable the
Vflip property in the Viewport or simply call the flip_y() method on the resulting image.
Canvas Layers
Now that we are head-deep into rendering, it’s good to mention another cool tool provided byGodot to make our life easier: the
CanvasLayer.
When making a 2D game, the order of drawing elements is really important, given that it determines whether your character stands under or
over a tree, for instance.
A simple solution for this trouble is to use the Z property to sort our nodes’ drawing order. This works great at first, but when our scene starts
growing in complexity, it becomes increasingly harder to make sure no subscenes have a node with the wrong Z value.
On top of that, this trick doesn’t work at all when dealing with a 3D game where we want to have a GUI.
This is where the
CanvasLayer steps in. As its name suggests, it wraps all of its child nodes into a single rendering layer. ThenGodot
does the rendering of our scene layer by layer, starting with the
CanvasLayer with the lower value of its Layer property.
NOTE
Default CanvasLayer
Every node that is not child of a
CanvasLayer is considered byGodot to be part of the default layer, which has a value of 0. So, when
adding a
CanvasLayer, remember to set its Layer property to a negative value if it’s in the background or a positive one for a foreground
layer.
A very common use case of
CanvasLayer is with 2D platforms to create the separation between fixed background, parallax scrolling
background, middle-ground (where the player interacts with the world), and foreground for the GUI. In fact, Godot even provides a special layer
node called
ParallaxBackground to simplify the implementation
o
f parallax scrolling.
TRY IT YOURSELF
Play with Parallax Scrolling
1. Create a simple 2D scene with a fixed background and a player-controlled ship. To make things easier, you can use the assets
from Hour 5’s game.
2. Add a child
Camera2D node to the ship and disable its Drag Margin H&V properties to make it follow the ship’s
movements exactly.
3. Now add
ParallaxBackground to the scene. Make sure it displays between the background and the player (a good idea
would be to use some
CanvasLayer for the background and player, and remember that
ParallaxBackground is
already a
CanvasLayer).
4. Configure the
ParallaxBackground’s base scale property (0 means no parallax scrolling on this axis). See Figure 21.4.
FIGURE 21.4
Configuring ParallaxBackground.
5. Add one or more
ParallaxLayer child nodes to the
ParallaxBackground, each one containing multiple Sprites of
asteroids. You can customize the Scale property to specify how fast each one will move on each axis. See Figure 21.5.
FIGURE 21.5
The Scale property specifies how fast each one will move on each axis.
6. Now play the scene and observe how the different
ParallaxLayer nodes move compared to the player. See Figure 21.6.
111111111
22222222FIGURE 21.6
Mesh and light.
NOTE
Keep the Number of Layers Low
Keep in mind that adding
CanvasLayer has a cost given there’s less freedom for the Godot renderer to process our nodes in batches. A
good rule of thumb would be to only use a couple of
CanvasLayer in your main scene (typically world rendering, in-game GUI, and a
paused menu for a 3D game) and keep the Z axis ordering in the subscenes.
Split-Screen
One really cool feature in
Viewport is the split-screen: we can divide our screen into two (or more) parts, each one usually controlled by a
different player.
From what we’ve learned so far, we can implement this by dividing our screen into multiple
Sprite nodes, each connected to a
Viewport with a camera node.
If we think about it, this is not a really elegant solution: it would be much better to have a
Control node instead of this 2D
Sprite to
have useful features like anchor or margin. Besides, we have to do the texture connection with a bit of code, which adds to the complexity.
The solution to these concerns, of course, lies in a new type of node provided byGodot: the
ViewportContainer node. Think of it as a
Control node that connects directly with its first child
Viewport node.
TRY IT YOURSELF
Add Split-Screen to a Scene
1. Create a simple 3D scene. Typically, it’s a mesh (don’t forget to add a material to it) and a light. See Figure 21.6.
2. Create another scene with a regular
Node as its root and our simple 3D scene as children.
3. Now add multiple ViewportContainers, configure their size to divide the screen between them, and enable their Stretch
properties.
4. For each ViewportContainer, add a Viewport and Camera. Move the cameras to show different angles of the scene. See Figure
21.7.
FIGURE 21.7
Cameras show various angles.
5. Finally, play your scene. As expected, we have multiple views of the scene. See Figure 21.8.
FIGURE 21.8
Multiple views of the scene.
Summary
In this hour, we saw how useful
Viewport is when dealing with advanced subjects like nested scenes, multiple views on the same scene,
or mixing 2D and 3D scenes together. On top of that, we saw how useful
CanvasLayer is for ordering the drawing of a complex scene
and how to combine
ParallaxBackground and
ParallaxLayer nodes to create parallax scrolling efficiently. Finally, we learned how
to add the very common split-screen feature to a game.
Q&A
Q. My 3D scene is not rendered by my viewport!
A. Make sure you didn’t forget to add some illumination to your scene. Also, check how you do your connection between the
Viewport
texture and the surface using it. Finally, make sure you configured the Size property of the
Viewport.
Q. My viewport produces an upside-down image.
A. You forgot to check the Vflip property of your
Viewport. Also, if it is connected to a
MeshInstance, it’s possible its
111111111
22222222Transformation property is the cause.
Q. My Parallax background is not moving.
A. Parallax moves relative to the camera, so make sure it is moving (and not just your character). Check if the
ParallaxBackground’s
Base Scale or
ParallaxLayer’s Scale properties are not set to 0 on one of their axes.
Workshop
See if you can answer the following questions to test your knowledge.
Quiz
1. What are the differences between the
Viewport and the
Camera nodes?
2. How do you configure the
Viewport to render the frames displayed on the user’s screen?
3. In which layer are the nodes rendered that don’t belong to a
CanvasLayer?
4. Why is it useless to give a wrap to a
ParallaxBackground node a
CanvasLayer parent node?
Answers
1. The
Camera node configures the
Viewport; the
Viewport does the actual rendering of the scene.
2. It’s always the root
Viewport (aka /root node), automatically created byGodot.
3. They will render in the default 0 layer. Keep in mind that layers are rendered from lower to greater value.
4.
ParallaxBackground is already a
CanvasLayer by itself.
Exercises
1. Try to add a split-screen feature to the game we created in Hour 17 to provide a side and top view.
2. Merge multiple games into a single one by using the
ViewportContainer and configuring each
Viewport to manage its own
world.
3. Now go inception-style by controlling a game from within another game:
Create a 3D scene with an arcade game machine (basically a big rectangle
MeshInstance with another plane and
MeshInstance representing the screen) and a player (another rectangle
MeshInstance) with a camera.
Attach a
Viewport playing the game from Hour 5 to the arcade machine screen.
If the game-within-the-game originally uses the Input singleton, convert the input processing to use a _gui_input() method
(typically by creating you own custom Input class this is updated each frame by _gui_input()).
Now inject input to the game-within-the-game
Viewport from script. The simplest way to do this is to provide some
Button nodes. Another more advanced way is allowing the player
MeshInstance to move (or control its view and send a
RayCast there) and detecting its collision with other
MeshInstance representing the arcade machine’s buttons.
111111111
22222222