Simulating depth with backgrounds is a staple of 2D games. Godot’s parallax system has been part of the core node set even before Godot went open source. With all of Godot’s advancements in the last decade, the parallax system has remained (mostly) the same. In the upcoming 4.3 release, it’s time for it to get some love and attention too!
A fresh start
Parallax2D
is a new node intended as an all-in-one replacement for ParallaxBackground
and ParallaxLayer
. It has feature-parity with the old system along with some new goodies! It’s flagged as experimental in 4.3 and is subject to change while we get some more feedback. The plan is to eventually deprecate (not remove) the old nodes, but rest assured, they won’t be deprecated until we are confident they are fully superseded by Parallax2D
. New features will be focused on Parallax2D
, but we will still be accepting bug fixes for the old nodes.
You can convert your current ParallaxBackground
and ParallaxLayer
s by clicking on your ParallaxBackground
node in the scene tree, then Convert to Parallax2D
from the context dropdown. Please give it a try!
Why the change?
There have been a lot of proposals and issues regarding Godot’s parallax system over the last few years. Bogusly, these had trouble getting movement without a significant overhaul of the two nodes that it’s comprised of: ParallaxBackground
and ParallaxLayer
. Godot has introduced so many new features and upgrades in related areas that the parallax system didn’t originally need to support (because they didn’t exist; A reasonable justification indeed!). I recently ran into one such case working on a faux-lighting system to replicate the look of lights in NES and SNES-era video games.
To achieve this lighting effect, my system uses GradientTexture2D
’s to mask an overlay or foreground objects using a screen-reading shader. For this to work, both the mask and the target need to be in the same CanvasLayer
. This is normally the case for nodes like Sprite2D
or TextureRect
, but ParallaxLayer
is restricted to being a direct child of ParallaxBackground
. Not only this (even worse), ParallaxBackground
’s base class is CanvasLayer
, making it completely incompatible with this technique. GROSS.
To understand why this is the case, we need to point out that ParallaxLayer
not only provides a parallax effect, but also an incredibly popular “infinite scrolling” bonus effect. Of course, actual infinite scrolling isn’t possible (right?), so, behind the scenes, Godot performs a bit of visual trickery (dramatization):
The textures are repeated by a value you set and their position zips back when it scrolls too far. This makes it appear like it’s repeating forever, deceiving the viewer! Godot is practically an illusionist!
Originally, ParallaxBackground
was intentionally made a CanvasLayer
(stay with me here) to take advantage of a quirk of the rendering system: each layer is processed one at a time. This provides space to repeat entire branches multiple times, achieving ParallaxLayer
’s “infinite scrolling” effect. This isn’t ideal, though. We want to allow this anywhere in the tree, not just at the root of a layer. By passing a field unique to ParallaxLayer
further down in the renderer, we can have the same effect on a sub-branch. This also reveals something else significant: without the need for ParallaxBackground
to be a CanvasLayer
, there’s not much of a need for ParallaxBackground
at all! Aw man! This is starting to sound like a breaking change…
After consulting with a few other contributors, it was clear there were two options:
- Wait until 5.0 and make these changes directly to
ParallaxLayer
and retire ParallaxBackground
. - Make a new node and provide a conversion tool.
As you can see, we went with the second option. While duplicate features could be confusing to users, 4.0 only came out a year ago, so users may be waiting a long time for these fixes if we push it out to a major release. By creating an entirely new node we have the freedom to make these changes immediately, and even more of them!
Bells and whistles
With the big change up front (inheriting Node2D
, and consolidating the nodes), it’ll be much easier to add features or fixes without breaking changes in the future. Here is a list of some of the most common issues and hotly requested features we’ve supported for Parallax2D
:
- Simplified workflow
- Ability to follow camera rotation
- Compatibility with
CanvasGroup
and BackBufferCopy
- Support for zooming out with a
Camera2D
- Autoscroll
- Improved documentation
- Performance improvements
Reorganization
The inspector properties are now reorganized and categorized. Notice that the limits now follow the same logic as Camera2D
limits. How standardized! There are notes in the previous parallax system’s documentation clarifying that mirroring
doesn’t actually mirror anything, but rather just repeats. The name has been changed to repeat_size
to better reflect what it actually does.
Consolidation
Requiring two nodes in a strict hierarchy has been a point of confusion and frustration for users in the past, with it not always being the clearest which node is responsible for what. With the CanvasLayer
trick no longer required for the “infinite repeat” effect, it frees us up for some simplification. We discussed this decision in depth and have a few reasons for it.
If the new Parallax2D
were required to be a direct child of something like ParallaxBackground
, the only purpose it’d serve is to run a for-loop over all its children to set values. A nice shortcut, but not enough to warrant a whole separate core node and enforce that relationship. Some new handy editor shortcuts were provided in the years since the parallax system was first put in place, like the ability to select multiple of the same node and update shared properties all at once. Alternatively, if a user wants to update all the nodes via code, they’d need to write a script for it anyway… not to mention it’d reintroduce the hard coupled parent/child relationship we removed, so it’s best left up to the user to choose on a case-by-case basis. I’m also not opposed to making an add-on if no one beats me to it!
CanvasLayer
to Node2D
If you’re wondering if you can still get the benefits of a CanvasLayer
, you can! The difference now is that you can choose. Just like any normal Node2D
, placing a Parallax2D
in a CanvasLayer
will prevent zoom and rotating with the camera. You’ll just want to make sure the Parallax2D
’s follow_viewport
value matches the CanvasLayer
’s follow_viewport_enabled
property.
You may notice that Parallax2D
’s follow_viewport
is enabled by default. This decision was made because ParallaxBackground
’s follow_viewport_enabled
is not enabled even though it is following the viewport. It can be confusing for a CanvasLayer
to not behave like a CanvasLayer
, so instead Parallax2D
behaves more like one would expect.
autoscroll
scrolls the layer automatically in addition to the parallax settings. Honestly, that’s it! This could be done with a custom script, but we decided to add it since it was highly requested, easy to implement, and has no performance hit if you aren’t using it.
repeat_times
This shouldn’t come up often, but the new repeat_times
property is being introduced to help with the problem of zooming out while trying to achieve an infinite repeat effect. When you zoom out and the repeat size becomes smaller than the screen, the effect is broken. In the video below, both scenes depict a collection of scrolling Parallax2D
nodes while zoomed out. The top scene has their repeat_times
set to 1, and the bottom scene is set to 3 (an extra repeat to the left and right).
Increasing the repeat_times
will allow for the number of repeats to grow and spread outward, so that you can zoom out and not reveal the magic going on behind the scenes. Eventually, it’d be nice to have a flag to adjust this automatically, but this is a good start! Bear in mind that this repeats all children of the Parallax2D
node. If you want only one of the children to repeat, it might be preferable to use the texture_repeat
property and Sprite2D
’s region to prepare your background.
What else?
Parallax2D
is now compatible with BackBufferCopy
and CanvasGroup
, and you’re also free to rotate the camera without breaking the parallax effect! Upon a few initial tests, we noticed some performance increases as well.
Caveats and future
With any effect that is based on perception, there are some tricky areas (since the entire effect is a trick!). A few of the same drawbacks to ParallaxBackground
still apply to Parallax2D
. For example, multiple cameras aren’t directly supported. This makes sense because Parallax2D
follows the same concept as ParallaxBackground
: the textures’ positions are modified directly and move at different speeds relative to the viewport. If you have multiple viewports, the textures can’t move relative to all of them and can’t be in two places at once! Just like in ParallaxBackground
, as a workaround, you can clone the Parallax2D
s and place each in a separate viewport.
There’s a known trick you can perform with ParallaxBackground
by scaling the entire CanvasLayer
up or down to create the impression of depth using the follow_viewport_scale
property. This techinque also makes it easier to preview your effect in the editor. This is still available with Parallax2D
if you place it in a CanvasLayer
, but (just like using it with ParallaxBackground
) it has some flaws. It scales both size and speed, which may not be desired, and might not be suitable for some pixel art styles that avoid scaling textures. The better option is to provide an officially-supported way to preview the parallax effect in the editor, and a few contributors have already shown progress on providing this in the form of an add-on or editor tool. Can you believe that? 4.3 isn’t even out yet! The sheer power of this community.
Additionally, even though Parallax2D
is a Node2D
, you should still take caution when moving or scaling any parent nodes. It can be a delicate effect, so it’s recommended to be cautious in complex setups.
Thanks!
Thanks to those who helped make this feature, especially: MewPurPur for their lightning-fast SVG skills, Mickeon and clayjohn for championing this feature from the get-go, and KoBeWi, AThousandShips, and adamscott for jumping in with detailed reviews and constant questions to make sure it covered all corner cases and was just right… not to mention a dozen others that helped with feedback and testing. I’m Mark DiBarry (a.k.a. Mr. Dink in the Godot community). I’ve been contributing to the Godot engine for the last few years, but this is my first “big feature” contribution. I hope you like it!
Do you see room for improvement or felt the spark of an idea while reading this? We’re always looking for more contributors and would love it if you’d join in. Don’t worry about the size of your first submission; Many of us started with rectifying spelling errors in the docs. We wouldn’t be where we are without the willingness of our community to jump in and get their hands dirty. What are you waiting for?