Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed game process in editor #99010

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Hilderin
Copy link
Contributor

@Hilderin Hilderin commented Nov 10, 2024

Implements game embedding for Windows and Linux (X11 only) in the new Game Workspace: GitHub PR #97257

As suggested, this implementation places the running game window as a child of the editor and embeds it inside the Game Workspace. The running game remains a separate process, with all keys and inputs handled in a separate event loop to maintain performance even when embedded.

image

image

U7w7iZ8zil.mp4

New Options in the Game Workspace Toolbar

  • Embed game on Play: On/Off: Enables or disables embedding. (Default: Enabled)

    • If multiple instances are configured, only the first instance will be embedded.
  • Make Game Workspace floating on Play: On/Off: When enabled, the Game Workspace opens in a floating window when the game starts. (Default: Enabled)

    • This option is not available when "Embed game on Play" is Off.
    • This option is not available in single-window mode.
  • Keep the aspect ratio of the embedded game: On/Off: Maintains the aspect ratio of the game window in the Game Workspace while embedding is enabled. (Default: Enabled)


Important Information

  • When embedded, some display settings cannot be changed at runtime to prevent the game window from exiting the Game Workspace. These settings will generate an error if modified during runtime in embedded mode:
    • Window Mode
    • Size
    • Minimum size
    • Maximum size
    • Position
    • Resizable
    • Always on Top
    • Popup
    • Current screen

Additional Features

  • Added the Engine.is_embedded_in_editor() method in GDScript/C#, which helps prevent errors when attempting to change unsupported window settings while embedded or for adjusting behavior based on whether embedding is active.
    • The same information can also be retrieved using OS.has_feature("embedded_in_editor").

Making Your Game "Embedded-Compatible"

  • Handling Mouse Capture: Currently, no default keyboard shortcut exists to exit mouse capture mode when embedded, which can be inconvenient for games like the TPS Demo that hide the mouse cursor during gameplay. As a workaround, you can modify the game to toggle the mouse cursor on pressing the Escape key instead of returning to the menu. Here’s an example for level.gd:

    func _input(event):
        if event.is_action_pressed("quit"):
            if Engine.is_embedded_in_editor():
                if Input.get_mouse_mode() == Input.MOUSE_MODE_VISIBLE:
                    Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
                else:
                    Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
            else:
                emit_signal("quit")
  • Preventing Fullscreen Errors: In the TPS Demo, errors occur when starting the game in fullscreen mode while embedded, as the DisplayServer does not allow window mode changes in embedded mode. You can prevent these errors using a simple check:

    if !Engine.is_embedded_in_editor():
        get_window().mode = Settings.config_file.get_value("video", "display_mode")

Known Issues

  • Windows: If the user focuses on the embedded game and quickly clicks a button in the editor, the click may not register. This seems to be due to Windows taking too long to reactivate the editor window, causing the mouse-up event to be missed. Disabling Unfocused Low Power Mode while the game is embedded mitigates this issue but is not a perfect solution.

  • Linux: X11 does not support moving windows outside screen bounds. If the editor window is moved outside the screen boundaries while embedding a game, the window is resized to prevent Linux from resetting its position to the screen edge. This limitation appears to be a known issue without a programmatic workaround.

  • Scene Previews Disabled: When the Game Workspace tab is active, scene previews are disabled because they appear under the embedded game. Since previews are standard controls rather than popups, this is a temporary workaround to prevent issues, though it disables scene previews. Addressing this in a future PR is recommended.

  • Game Process Recording: Tools like OBS Studio or Game Bar cannot record the embedded game by capturing the Godot Editor process. To record the game, you must capture the entire screen or a specific section of it. This limitation is expected given the separate processes for the editor and game.

  • Single Window Mode and Popups/Tooltips: When the game is embedded and the editor runs in single-window mode, all popups and tooltips are displayed beneath the embedded game process.


Testing

  • Windows: Tested on Windows 11 (1, 2, and 3 monitors) and Windows 10 (1 monitor).
  • Linux: Tested on Ubuntu 24.04 (1 and 2 monitors) and Fedora 40 KDE Plasma 6.1.5 (1 and 2 monitors).

@SheepYhangCN
Copy link
Contributor

Added the Engine.is_embedded() method in GDScript/C#

Add embedded into has_feature might be a better solution to ensure consistency since it is similar to the feature tags like debugor editor?

@AThousandShips AThousandShips changed the title Embedding game process in editor Embed game process in editor Nov 10, 2024
scene/scene_string_names.h Outdated Show resolved Hide resolved
servers/display_server.h Outdated Show resolved Hide resolved
@KoBeWi
Copy link
Member

KoBeWi commented Nov 10, 2024

If multiple instances are configured, only the first instance will be embedded.

Is this a limitation? If not, you could embed multiple instances inside a TabContainer.

EDIT:
Also the WINDOW_FLAG_HIDDEN allows for making apps that minimize to tray I think? (we already support tray icons)

@KoBeWi

This comment was marked as resolved.

@Hilderin
Copy link
Contributor Author

Add embedded into has_feature might be a better solution to ensure consistency since it is similar to the feature tags like debugor editor?

I added "embedded" to OS.has_feature to returns true when the game is running embedded. I still kept Engine.is_embedded to be consistent with Engine.is_editor_hint. Sounds good?

@Hilderin
Copy link
Contributor Author

Hilderin commented Nov 26, 2024

@allenwp

Did you see my notes in this #99010 (comment)? I posted it just as you were writing up an update on your progress 😅 (notably the idea of showing resolution like in the original proposal)

Excellent suggestion, I did not see the suggestion to add the game resolution. I added it on top right corner:
image

Sounds good?

@jcostello
Copy link
Contributor

It feels weird the empty background outside the game window. What if the background has the same color as the game toolbar? How does it look like?

@Hilderin
Copy link
Contributor Author

@Calinou

On KDE X11, if I hold Windows and use the left and right mouse button, I can move/resize windows without needing to drag their border

I tried to disable window moving on x11, but nothing seems to do the trick. Following the x11 documentation, I tried setting _NET_WM_ALLOWED_ACTIONS with no allowed actions or some allowed actions but it's no use:

Atom wm_allowed_actions = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False);

Atom allowed_actions[] = {};
XChangeProperty(
    x11_display,
    p_window,
    wm_allowed_actions,
    XA_ATOM,
    32,
    PropModeReplace,
    (unsigned char *)allowed_actions,
    0 // Number of allowed actions
);

I'm really not an expert on x11 and Linux, maybe someone else can help to find a fix to prevent the use to move the window with the Window key?

editor/editor_run.h Outdated Show resolved Hide resolved
@KoBeWi
Copy link
Member

KoBeWi commented Nov 27, 2024

It's possible to move embedded window to another screen with Windows+Shift+Right.

editor/window_wrapper.cpp Outdated Show resolved Hide resolved
doc/classes/Engine.xml Outdated Show resolved Hide resolved
@KoBeWi
Copy link
Member

KoBeWi commented Nov 27, 2024

image
Why is embedding hidden inside the menu, while aspect ratio, which is rarely needed, is outside? I think embedding should be a button and aspect lock should be in the menu.

Also I'd remove "Next" from "on Next Play", it looks overly verbose.

@Mickeon
Copy link
Contributor

Mickeon commented Nov 27, 2024

Also I'd remove "Next" from "on Next Play", it looks overly verbose.

There needs to be another hint those toggles only apply next time the project is run, then. Alternatively, they should not be toggleable while the project is running.

@KoBeWi
Copy link
Member

KoBeWi commented Nov 27, 2024

Functionality-wise it's mostly fine now, I went over the implementation and it looks ok too (aside from the comments I left).

One major concern is that the editor is frozen while the embedded proces is starting. It's even worse when you have multiple instances enabled, because the editor waits for all of them. Embedding is enabled by default, so this will result in worse user experience (although also makes the Game tab closer to Unity I guess).
I tested only in dev build, so maybe it won't be a problem in optimized builds. Still, as I already mentioned above, we at least shouldn't make embedding hidden behind a menu option. Given all the drawbacks, it should be easy to disable.

I think this Is very cool and useful as a prototype to gather feedback, but I wouldn't release unless we can figure out a way of doing the embedding using low level "vulkan voodoo magic".
This feels a bit too fragile and full of corner cases. Not to deter from Hilderin 's efforts of course! I'm just saying I wouldn't rush this past the door

"Vulkan voodoo magic" is available only on Vulkan. We need a fallback implementation for other renderers.

@Hilderin
Copy link
Contributor Author

Hilderin commented Nov 27, 2024

Also I'd remove "Next" from "on Next Play", it looks overly verbose.

@Calinou ask me to add the "Next", we just need a consensus on that.

Still, as I already mentioned above, we at least shouldn't make embedding hidden behind a menu option. Given all the drawbacks, it should be easy to disable.

We just need a consensus because at first it was in the toolbar but I was asked to place these options in a menu.

@Hilderin
Copy link
Contributor Author

I'll try to look tomorrow or the next day for these issues:

  • One major concern is that the editor is frozen while the embedded proces is starting.
  • It's possible to move embedded window to another screen with

@Chaosus
Copy link
Member

Chaosus commented Nov 28, 2024

I've tested a 2D Platformer Demo (https://github.com/godotengine/godot-demo-projects/tree/4.2-31d1c0c/2d/platformer) and noticed that the embedded viewport is very small - is this intended?

изображение

Also in TPS Demo it is impossible to press Escape to switch modes without quit to main menu?

@Calinou
Copy link
Member

Calinou commented Nov 28, 2024

nd noticed that the embedded viewport is very small - is this intended?

Yes, as integer scaling forces the viewport's scale to be an integer value. Since the window embedding doesn't increase the window size automatically to account for the GUI controls displayed around the game, this "downgrades" the viewport's scale to a lower value.

We could have the window size increase automatically to account for its controls to avoid this issue, but it will require the screen to be large enough to work.

@Hilderin
Copy link
Contributor Author

Hilderin commented Nov 29, 2024

@KoBeWi

After testing and trying to fix these issues, bad news, I don't think it's possible to fix these, or at least, I did not find a solution.

One major concern is that the editor is frozen while the embedded proces is starting.

The problem comes from Windows, when parented, when a process stops the WndProc loop, the other process stops also. So when then game window starts, Godot freezes when loading the game, causing the editor to freeze also. The bigger the project, bigger the issue is apparent. I tried to postpone parenting but Windows really does not like it and causes issues where the child window can appear behind the editor on startup and not moving while the game is loading, etc...

It's possible to move embedded window to another screen with Windows+Shift+Right.

I don't think it's possible to disable this shortcut on Windows from an application. One way to maybe mitigate this issue similar to moving the window with Window+Shift+MouseLeftClick on Linux could be to check the game window position and relocate it every second or something from the editor. I'm not sure this is worth it.

@Hilderin
Copy link
Contributor Author

@Chaosus

Also in TPS Demo it is impossible to press Escape to switch modes without quit to main menu?

Check the PR description in the "Making Your Game "Embedded-Compatible" section. I guess the TPS demo should be upgraded to implement the modifications.

@Hilderin
Copy link
Contributor Author

Hilderin commented Nov 29, 2024

@Chaosus

and noticed that the embedded viewport is very small - is this intended?

What's your screen resolution? I guess you started the game embed in the editor and not in the floating window? In this mode, there's no way to actually resize the window by code because it's based on the size of the Game Workspace panel inside the editor. Also, you seems to a old version of this PR based on the options I see in the toolbar. I suggest you try in the "Make Game Workspace floating on Play" option and try to resize the window. In this mode, are you able to resize the game window on make the game bigger?

}

// Remove duplicates/unwanted parameters.
List<String>::Element *E = r_arguments.find("--position");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a linked list, so finding/erasing repeatedly is very costly. You can iterate the list and remove arguments as you find them.

List<String>::Element *E = r_arguments.front();
while (E) {
	N = E->next();

	if (E->get() == "something") {
		r_arguments.erase(E);
	} else if (E->get() == "something else") {
		r_arguments.erase(E);
	}

	E = N;
}

etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.