{"id":18275495,"url":"https://github.com/geegaz/multiple-windows-tutorial","last_synced_at":"2025-04-05T22:04:11.019Z","repository":{"id":178118349,"uuid":"611167400","full_name":"geegaz/Multiple-Windows-tutorial","owner":"geegaz","description":"Godot 4 tutorial for making a character that moves outside of the game window","archived":false,"fork":false,"pushed_at":"2024-09-05T11:32:48.000Z","size":384,"stargazers_count":377,"open_issues_count":1,"forks_count":24,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-03-29T21:02:50.079Z","etag":null,"topics":["gdscript","godot","godot-engine"],"latest_commit_sha":null,"homepage":"","language":"GDScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/geegaz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-08T09:02:31.000Z","updated_at":"2025-03-28T19:42:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"264f6593-f023-4c04-9710-919945d91fdd","html_url":"https://github.com/geegaz/Multiple-Windows-tutorial","commit_stats":null,"previous_names":["geegaz/multiple-windows-tutorial"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geegaz%2FMultiple-Windows-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geegaz%2FMultiple-Windows-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geegaz%2FMultiple-Windows-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/geegaz%2FMultiple-Windows-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/geegaz","download_url":"https://codeload.github.com/geegaz/Multiple-Windows-tutorial/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247406085,"owners_count":20933803,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gdscript","godot","godot-engine"],"created_at":"2024-11-05T12:13:06.144Z","updated_at":"2025-04-05T22:04:10.975Z","avatar_url":"https://github.com/geegaz.png","language":"GDScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Multiple Windows tutorial for Godot 4.3\n\n- [Contents of this Repository](#contents-of-this-repository)\n- [Introduction](#introduction)\n- [Part 1 - Using Godot 4's Window](#part-1---using-godot-4s-window)\n- [Part 2 - Making windows share the same world](#part-2---making-windows-share-the-same-world)\n\t- [Camera following a window's position](#camera-following-a-windows-position)\n\t- [Camera in a separate window](#camera-in-a-separate-window)\n\t- [Sharing the same world](#sharing-the-same-world)\n- [Part 3 - Making a character that moves across windows](#part-3---making-a-character-that-moves-across-windows)\n\t- [Making the main window (almost) invisible](#making-the-main-window-almost-invisible)\n\t- [Hiding the world in the main window](#hiding-the-world-in-the-main-window)\n\t- [Moving the window with the character](#moving-the-window-with-the-character)\n- [Limitations](#limitations)\n- [Conclusions](#conclusion)\n\n## Contents of this Repository\n\nThis project is a demo of how Godot 4's multiple windows feature allows for interesting 4th-wall-breaking mechanics. It was created with Godot 4.0 and updated to Godot 4.3 by [@wadantasjr](https://github.com/wadantasjr).\n\n### Controls:\n- **Arrows**: moves the character\n- **Space**: when focused on the character, spawns another window\n- **Escape**: when focused on the character, quits the demo\n\n### Scenes:\n- **demo** is the scene created by following the tutorial step-by-step. Scripts are integrated to the scene and are not perfect, but functional\n- **main** is the default scene. Uses an improved version of the tutorial scripts\n- **view_window** is a window with a camera already setup \n- **level** contains the actual level, with a tilemap and a few background layers\n- **character** contains a working character controller without a camera\n\n### Scripts:\n- **Main.gd** contains the logic of the main window. Close to the tutorial with a few improvements\n- **ViewWindow.gd** contains the logic of every additional window\n- **Character.gd** is the character controller's script\n- **MovementProvider.gd** has the base logic for sending inputs to the character controller\n- **MovementProviderPlayer.gd** extends `MovementProvider` for player inputs\n\n*This project uses sprites from my game **A Key(s) Path**, which is open-source on [GitHub](https://github.com/geegaz/A-Key-s-Path) and available on [Itch](https://geegaz.itch.io/out-of-controls).\u003cbr\u003e\nThe project also uses Portponky's excellent plugin **Better Terrain** for Godot 4.3, check it out on [Github](https://github.com/Portponky/better-terrain) !*\n\n---\n\n## Introduction\nSo, a while ago I posted [a tweet about using multiple windows](https://twitter.com/geegaz_/status/1632419279843803138) with the Godot 4.0 release. Here's what it looked like:\n\n![A character jumping between different windows](media/jumping_through_windows.gif)\n\nSo I'm making this tutorial since many of you seemed to enjoy it ! Here is the basic idea: \n\n\u003e The character is a separate unresizeable, borderless and transparent window that's exactly the size of the character's sprite. Additional windows that are instanciated have their own camera, and copy the main window's world_2d to see the same world. The character manipulates the main window's position to seem like they're moving around on the screen, and the additional windows move their camera in the world to match the window's position on the screen.\n\nThe tutorial will be divided in 3 parts:\n1. **How to use the new Window node**\n2. **How to make multiple windows share the same world**\n3. **How to make a character that can move outside windows**\n\nNow with all that in mind, let's start !\n\n\n\n## Part 1 - Using Godot 4's Window\n\nGodot 4 introduces a new kind of node, **Window**, which replaces Godot 3's Popup node. This new node is much more powerful, as it can be either embeded in the main window as a regular Control but can also be an entirely separate window ! \n\nWindow also inherits from Viewport, which means Windows are actually viewports and can use all the properties and methods from Viewport nodes. In fact, that's actually what we're going to use to display the same world in multiple windows.\n\nHere is what the main parameters for the Window look like in the inspector.\n\n![Some of the Window's node properties. Visit the Godot wiki that's linked in the next paragraph for the complete list of properties (and better screen-reader support)](https://user-images.githubusercontent.com/39198556/224128483-5ade9306-d860-4258-8988-cfec89797a25.png)\n\nIf you've already tweaked the projects setting a lot in Godot, some of the flags might seem familiar. A lot of them are in common with the project's Window settings, along with some ones that only make sense for additional window. Of course, this is not the complete list of the Window's properties - you can find all of them [on the Godot wiki](https://docs.godotengine.org/en/stable/classes/class_window.html) !\n\nHere are the flags we're going to use, and what they do:\n- **transient**:     this window will close at the same time as its parent window, and can't enter fullscreen\n- **unresizeable**:  this window can't be resized nor maximized\n- **borderless**:    no border or title bar on this window\n- **always_on_top**: this window stays on top of others even when focusing another one\n- **transparent**:   *allows* this window to be transparent - other settings need to be set to make it actually transparent\n\nBut when you create a Window node and test your project, you might notice it appears by default as a Control node, embeded in the main window. \nTo fix that, go in the **Project Settings** \u003e **Window** \u003e switch to **Advanced Settings** \u003e disable **Embed Subwindows**\n\n![Godot's Project Settings, in the Window section. Advanced Settings are enabled and the option Embed Subwindows has been unchecked](https://user-images.githubusercontent.com/39198556/224145376-35ee13ea-7b91-459a-9c1b-e105123789d4.png)\n\n\n\n## Part 2 - Making windows share the same world\n\nNow, let's do something fun with a window !\n\n### Camera following a window's position\n\n`Main.gd`\n```gdscript\nextends Node\n\n@onready var _Camera: Camera2D = $Camera2D\n\nfunc _process(delta: float) -\u003e void:\n\t_Camera.position = get_window().position\n```\n![Hierarchy view of a scene in Godot 4. The root is a basic node with a script attached and a Camera2D as a child](https://user-images.githubusercontent.com/39198556/224136796-4b099cbd-c98e-466f-92e0-fc98978ba87a.png)\n\nAttach this script to a node in your scene, and add a Camera2D to this node. The camera will follow the window's position on the screen, although with a slight delay. This is due to the window's position updating before the `_process()` method is called in the engine, making the camera flicker and lag behind.\n\n### Camera in a separate window\n\n`ViewWindow.gd`\n```gdscript\nextends Window\n\n@onready var _Camera: Camera2D = $Camera2D\n\nvar last_position: = Vector2i.ZERO\nvar velocity: = Vector2i.ZERO\n\nfunc _ready() -\u003e void:\n\t# Set the anchor mode to \"Fixed top-left\"\n\t# Easier to work with since it corresponds to the window coordinates\n\t_Camera.anchor_mode = Camera2D.ANCHOR_MODE_FIXED_TOP_LEFT\n\t\n\ttransient = true # Make the window considered as a child of the main window\n\tclose_requested.connect(queue_free) # Actually close the window when clicking the close button\n\nfunc _process(delta: float) -\u003e void:\n\tvelocity = position - last_position\n\tlast_position = position\n\t_Camera.position = get_camera_pos_from_window()\n\nfunc get_camera_pos_from_window()-\u003eVector2i:\n\treturn position + velocity\n```\n![Hierarchy view of a scene in Godot 4. The root is a basic node, it has a Sprite2D and a Window as children. The Window has a script attached and a Camera 2D as a child](https://user-images.githubusercontent.com/39198556/224141011-5de5b114-3745-4a5f-987b-31eac9c9bd3b.png)\n\nHere, there's a little more going on: this time we're using a separate window, which means we can access its position directly. I also added the window's velocity from the last frame to the camera's position to mitigate the view lagging behind. There's still flickering, but the view follows more closely the actual position of the window.\n\n\n### Sharing the same world\n\nBut now you might start to notice the issue with what we're trying to do. I put a sprite on the main node of the scene, but it doesn't appear in the additional window even when moving the window to the top-left of the screen where the sprite should be. Now that's the regular way windows would work - but not ours ! Our goal is to have the world the character is moving in shown simultaneously on several windows.\n\nFor this, we'll need an additional script on the main node, at the root of the scene:\n\n`Main.gd`\n```gdscript\nextends Node\n\n@onready var _MainWindow: Window = get_window()\n@onready var _SubWindow: Window = $Window\n\nfunc _ready():\n  _SubWindow.world_2d = _MainWindow.world_2d\n```\n![The same scene as before, but now the root has a script attached](https://user-images.githubusercontent.com/39198556/224147961-37fe1e2e-e799-47b5-a9d2-a2c692028dbe.png)\n\n...And that's pretty much it !\n\nNow when you move the subwindow to the top-left corner of your screen, you should see a part of the sprite ! At this point, I'd recommend you make the Window and Camera2D a separate scene, to be able to reuse it more easily or instanciate it at runtime.\n\nAnd now, the most important (and longest) part:\n\n\n\n## Part 3 - Making a character that moves across windows\n\nUsing the knowledge of the previous parts, you may have an idea now of how we're going to do this. Here, the character is actually the main window of the game !\n\nFor the character controller, I adapted the controller from A Key(s) Path to this project. It can move horizontally using the **left and right** keys, and jump at a variable height using the **up** key. If you're interested by the controller code, take a look at [`Character.gd`](scripts/Character.gd), [`MovementProvider.gd`](scripts/movement_providers/MovementProvider.gd) and [`MovementProviderPlayer.gd`](scripts/movement_providers/MovementProviderPlayer.gd), but we're only going to focus on manipulating the window.\n\n### Making the main window (almost) invisible\nUsing the code from the previous parts, here is the code needed to make the main window almost invisible. This is the main trick to make the character seem like part of another window:\n\n`Main.gd`\n```gdscript\n# declare the variables...\n\nfunc _ready():\n\t# Enable per-pixel transparency, required for transparent windows but has a performance cost\n\t# Can also break on some systems\n\tProjectSettings.set_setting(\"display/window/per_pixel_transparency/allowed\", true)\n\t# Set the window settings - most of them can be set in the project settings\n\t_MainWindow.borderless = true\t\t# Hide the edges of the window\n\t_MainWindow.unresizable = true\t\t# Prevent resizing the window\n\t_MainWindow.always_on_top = true\t# Force the window always be on top of the screen\n\t_MainWindow.gui_embed_subwindows = false # Make subwindows actual system windows \u003c- VERY IMPORTANT\n\t_MainWindow.transparent = true\t\t# Allow the window to be transparent\n\t# Settings that cannot be set in project settings\n\t_MainWindow.transparent_bg = true\t# Make the window's background transparent\n\t\n\t# set the subwindow's world...\n\n```\n\nSo, what is happening there? \n\nFirst, we activate **per-pixel transparency** in the project settings, which is the actual setting that allows windows to show the background of the desktop when they're transparent. \n\nNext, we set a bunch of flags to make the window **borderless, unresizeable and always on top**, which will make it completely unmoveable using the mouse. We also disable the window embeding subwindows, since we want other windows to display outside of it.\n\nFinally, we **allow the window to be transparent** and set the **background of the window to be transparent**. While this might sound strange (setting a similar property twice ?), `transparent` is only a flag of the window to allow transparency while `transparent_bg` is inherited from Viewport and makes the background of the window output transparency instead of the usual clear color. Mixed with per-pixel transparency, this combination of 3 settings makes transparent windows possible !\n\n*Most of these settings can (and should) be set in the Project Settings directly, when using Advanced Settings. But I set them here for the sake of flexibility and showing what the value of each of them should be !*\n\n### Fitting the window to the character\n\nNow, I'm going to assume you have a character controller on hand that you can use and an environment you can place it into.\n\nAfter placing the camera as a child of the character, here a small snippet to set the main window's size to the size of the character, to avoid blocking the mouse on a large portion of the screen and drawing too much empty space:\n\n`Main.gd`\n```gdscript\n# declare the variables...\n\n@export var player_size: Vector2i = Vector2i(32, 32) # Should be the size of your character sprite, or slightly bigger\n\nfunc _ready():\n\t# setup the main window...\n\n\t# The window's size may need to be smaller than the default minimum size\n\t# so we have to change the minimum size BEFORE setting the window's size\n\t_MainWindow.min_size = player_size\n\t_MainWindow.size = _MainWindow.min_size\n\n\t# set the subwindow's world...\n```\n![Hierarchy view of a scene in Godot 4. The root is still a basic node with a script attached. It still has a Window as a child, but now also a Node2D \"Level\" and a CharacterBody \"Character\" as children. The Window has a script attached and a Camera 2D as a child](https://user-images.githubusercontent.com/39198556/224170623-14c584b4-accc-4221-9b5f-1279f9876d63.png)\n\n### Hiding the world in the main window\n\nWhile the window might be the right size you can still see the world behind the player, breaking the illusion. Using visibility layers, we can hide the world in the main window and hide the player in other windows.\n\nFirst, set the player's sprite visibility layer to a different one than the world's\n\n![A Node2D's inspector window, in the Visibility section. The first visibility layer is disabled while the second is enabled](https://user-images.githubusercontent.com/39198556/224175459-9ae41213-acf7-467c-b2bb-cdba82ed1f0a.png)\n\nThen, we can add these lines to the `Main.gd`:\n\n`Main.gd`\n```gdscript\n# declare the variables...\n\n@export_range(0, 19) var player_visibility_layer: int = 1\n@export_range(0, 19) var world_visibility_layer: int = 0\n\nfunc _ready():\n\t# setup the main window...\n\t# fit the main window to the character...\n\n\t# To only see the character in the main window, we need to \n\t# move its sprite on a separate visibility layer from the world\n\t# and set the main window to cull (not show) the world's visibility layer\n\t_MainWindow.set_canvas_cull_mask_bit(player_visibility_layer, true)\n\t_MainWindow.set_canvas_cull_mask_bit(world_visibility_layer, false)\n\n\t# Contrarily to the main window, hide the player and show the world\n\t_SubWindow.set_canvas_cull_mask_bit(player_visibility_layer, false)\n\t_SubWindow.set_canvas_cull_mask_bit(world_visibility_layer, true)\n\n\t# set the subwindow's world...\n```\n\n### Moving the window with the character\n\nAt this point, the only thing remaining is making our window move ! This is quite easy, although you have to take into account that the character's camera is usually using a `drag center` anchor which affects the calculations of the window's final position.\n\nHere's the code for that (you'll just need to assign the main camera in the inspector):\n\n`Main.gd`\n```gdscript\n# declare the variables...\n\n@export_node_path(\"Camera2D\") var main_camera: NodePath\n@onready var _MainCamera: Camera2D = get_node(main_camera)\n\n# all the previous code...\n\nfunc _process(delta):\n\t# Update the main window's position\n\t_MainWindow.position = get_window_pos_from_camera()\n\nfunc get_window_pos_from_camera()-\u003eVector2i:\n\treturn Vector2i(_MainCamera.global_position + _MainCamera.offset) - player_size / 2\n```\n\nNow you should have a character that moves around on your screen, and which position matches the world you see in the additional window. \n\nThe only problem is that the character and the world might appear very small on the screen... to fix that, you might want to zoom your camera**s** and use that zoom as a multiplier in all the places that change the window or the camera position:\n\n`Main.gd`\n```gdscript\nfunc _ready():\n\t# when setting the main window's size...\n\t_MainWindow.min_size = player_size * Vector2i(_MainCamera.zoom)\n\t_MainWindow.size = _MainWindow.min_size\n\n# when calculating the new window position...\nfunc get_window_pos_from_camera()-\u003eVector2i:\n\treturn (Vector2i(_MainCamera.global_position + _MainCamera.offset) - player_size / 2) * Vector2i(_MainCamera.zoom)\n```\n`ViewWindow.gd`\n```gdscript\n# when calculating the new camera position...\nfunc get_camera_pos_from_window()-\u003eVector2i:\n\treturn (position + velocity) / Vector2i(_Camera.zoom)\n```\n\nAnd here it is !\n\n![The character halfway through the side of a window](https://user-images.githubusercontent.com/39198556/224185789-d11ac66a-ab2e-4ba7-bc96-02bda3c1ba3d.png)\n\n\n\n\n## Limitations\n\nWhile this prototype is fun, you might notice a few flaws very quickly:\n- since windows are their own viewports, control nodes and CanvasLayers will not appear in other windows, which means for example that a ParallaxBackground would need to be added to every new window in order to be visible in them\n- the view of the world in View Windows flickers when you move the window. This is due to the engine running the `_process()` method a frame later after the window was moved. While I did a basic fix to mitigate the view lagging behind, it had no effect on the flickering\n- the more additional windows you open, the worse the performances. Every window is an additional viewport, so even if they cover a small surface of the screen their impact is still significant, especially with per-pixel transparency enabled. This might not be apparent with the current demo and on high-end PCs, but still needs to be taken into consideration\n- more of a design flaw, but the character can walk even on the invisible terrain. Coding a system so that only the visible shapes have collisions would've been too much work for this proof-of-concept\n\n\n\n## Conclusion\n\nCongratulations for following to the end, this is how I made the gif you saw at the start !\n\nOf course, there are ways to do it differently, maybe more efficiently or in a more stable way. There was an example from BrightShotGames on twitter that [made a similar thing on Unity](https://twitter.com/shot_bright/status/1633218766992490496) using a fullscreen transparent window. But currently, Godot is the only engine that supports multiple windows to this extent.\n\nAnyway, hope you'll have fun with it !\n\n[**- Geegaz**](https://geegaz.itch.io)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeegaz%2Fmultiple-windows-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeegaz%2Fmultiple-windows-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeegaz%2Fmultiple-windows-tutorial/lists"}