Adapting Mobile Games for a Notch in Godot

I dislike the notch on new phones and so far I have avoided phones with a notch. When I bought my Pixel 3, I specifically bought the smaller phone because it did not have a notch.

Regardless of what you may or may not think about notches, they seem to be here to stay. Even the new Macbook Pro has a notch in it’s display. So if they are here to stay, we might as well learn how to handle them in our games.

What happens to your game if you do not properly account for a notch? Well, the notch cuts out a small little section of your screen, but anything that is “behind” the notch will still be there. This includes game objects, like the player, terrain or enemies; but, it also includes GUI elements like health bars and score counters.

Taking a screenshot doesn’t show the notch, it renders normally. The only way to demonstrate the notch cutting off the GUI is to take a picture of the actual phone.

Knowing that everything cut off by the notch is still there, we just need to push any GUI elements down. There must be some sort of way to do it in code right? When we look at the Godot API and Documentation, we can find a function that sounds like it will do what we want called get_window_safe_area() that is part of the OS class.

The description of this function says “Returns unobscured area of the window where interactive controls should be rendered.” This sounds exactly like what we want. The function returns this area as a Rect2. A Rect2 has a Vector2 as the starting point (top left), a width, and a height value.

The easiest thing to do would then to put all of our GUI elements inside of a control node (itself inside of a Canvas Layer), and use this function to get the top left corner of the safe area, then apply the width and height to the control node. Ideally this will return a rectangle that takes up the full width and height of the screen up to the notch.

func _ready():
var GUI_Control = $canvaslayer/control;
var safe_area = OS.get_window_safe_area();
var pos_x = safe_area.position.x;
var pos_y = safe_area.position.y;
var safe_width = safe_area.end.x;
var safe_height = safe_area.end.y;
var safe_position = Vector2(pos_x, pos_y);
var safe_size = Vector2(safe_width, safe_height);
GUI_Control.set_position(safe_position, false);
GUI_Control.set_size(safe_size, false);

Unfortunately, this doesn’t work 100% of the time. This method does not take into account any scaling that the engine does. For example, Captain Skyrunner was developed with a resolution of 360 by 640 with the stretch mode set to expand. The iPhone 11 has a resolution of 1792 x 828. When I ran this code the scaling being applied pushed my GUI elements off screen.

Oh no, you can’t see or press the buttons anymore.

Now, when I was at this stage myself, I got completely stuck and was very frustrated with this bug. Because I am not the best programmer in the world, I went to the Godot Github page and asked for some help. Thankfully the user kleonc was kind enough to help me out and came up with a solution that resolved the issue.

func _ready():
var window_to_root = Transform2D.IDENTITY.scaled(get_tree().root.size / OS.window_size)
var safe_area_root = window_to_root.xform(OS.get_window_safe_area())
var control = $CanvasLayer/Control
	
assert(control.get_viewport() == get_tree().root, "Assumption: control is not in a nested Viewport")
var parent_to_root = control.get_viewport_transform() * control.get_global_transform() * control.get_transform().affine_inverse()
var root_to_parent = parent_to_root.affine_inverse()

var safe_area_relative_to_parent = root_to_parent.xform(safe_area_root)
control.rect_position = safe_area_relative_to_parent.position
control.rect_size = safe_area_relative_to_parent.size

This code is what is currently being used in Captain Skyrunner to adjust for a notch at run time. It seems to work on both Android and iOS. I would like to once again thank Kleonc for providing me with this solution. Hopefully this helps somebody.

Until Next Time
– Steven

2 thoughts on “Adapting Mobile Games for a Notch in Godot”

    1. OS.get_window_safe_area() returns a rectangle with the absolute safe position on the device’s resolution.

      So if your project is 360 * 640, and the resolution of a device is 1080 * 1920, then you just using the safe area will result in a screenshot like mine.

      The rest of the code is to get the correct scaled safe area for your project size.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.