Skip to content

Commit

Permalink
Add support for embedding game process in the Android Editor
Browse files Browse the repository at this point in the history
- Implement Android editor specific `EmbeddedGodotGame` to support embedding the game window in the Android editor
  • Loading branch information
m4gr3d committed Feb 7, 2025
1 parent 1586c56 commit caa8a1b
Show file tree
Hide file tree
Showing 68 changed files with 2,329 additions and 358 deletions.
15 changes: 0 additions & 15 deletions doc/classes/EditorSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1129,24 +1129,9 @@
<member name="run/platforms/linuxbsd/prefer_wayland" type="bool" setter="" getter="">
If [code]true[/code], on Linux/BSD, the editor will check for Wayland first instead of X11 (if available).
</member>
<member name="run/window_placement/android_window" type="int" setter="" getter="">
Specifies how the Play window is launched relative to the Android editor.
- [b]Auto (based on screen size)[/b] (default) will automatically choose how to launch the Play window based on the device and screen metrics. Defaults to [b]Same as Editor[/b] on phones and [b]Side-by-side with Editor[/b] on tablets.
- [b]Same as Editor[/b] will launch the Play window in the same window as the Editor.
- [b]Side-by-side with Editor[/b] will launch the Play window side-by-side with the Editor window.
- [b]Launch in PiP mode[/b] will launch the Play window directly in picture-in-picture (PiP) mode if PiP mode is supported and enabled. When maximized, the Play window will occupy the same window as the Editor.
[b]Note:[/b] Only available in the Android editor.
</member>
<member name="run/window_placement/game_embed_mode" type="int" setter="" getter="">
Overrides game embedding setting for all newly opened projects. If enabled, game embedding settings are not saved.
</member>
<member name="run/window_placement/play_window_pip_mode" type="int" setter="" getter="">
Specifies the picture-in-picture (PiP) mode for the Play window.
- [b]Disabled:[/b] PiP is disabled for the Play window.
- [b]Enabled:[/b] If the device supports it, PiP is always enabled for the Play window. The Play window will contain a button to enter PiP mode.
- [b]Enabled when Play window is same as Editor[/b] (default for Android editor): If the device supports it, PiP is enabled when the Play window is the same as the Editor. The Play window will contain a button to enter PiP mode.
[b]Note:[/b] Only available in the Android editor.
</member>
<member name="run/window_placement/rect" type="int" setter="" getter="">
The window mode to use to display the project when starting the project from the editor.
[b]Note:[/b] Game embedding is not available for "Force Maximized" or "Force Fullscreen."
Expand Down
7 changes: 7 additions & 0 deletions editor/editor_main_screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ EditorPlugin *EditorMainScreen::get_selected_plugin() const {
return selected_plugin;
}

EditorPlugin *EditorMainScreen::get_plugin_by_name(const String &p_plugin_name) const {
ERR_FAIL_COND_V(!main_editor_plugins.has(p_plugin_name), nullptr);
return main_editor_plugins[p_plugin_name];
}

VBoxContainer *EditorMainScreen::get_control() const {
return main_screen_vbox;
}
Expand All @@ -254,6 +259,7 @@ void EditorMainScreen::add_main_plugin(EditorPlugin *p_editor) {
buttons.push_back(tb);
button_hb->add_child(tb);
editor_table.push_back(p_editor);
main_editor_plugins.insert(p_editor->get_plugin_name(), p_editor);
}

void EditorMainScreen::remove_main_plugin(EditorPlugin *p_editor) {
Expand All @@ -280,6 +286,7 @@ void EditorMainScreen::remove_main_plugin(EditorPlugin *p_editor) {
}

editor_table.erase(p_editor);
main_editor_plugins.erase(p_editor->get_plugin_name());
}

EditorMainScreen::EditorMainScreen() {
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_main_screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class EditorMainScreen : public PanelContainer {
HBoxContainer *button_hb = nullptr;
Vector<Button *> buttons;
Vector<EditorPlugin *> editor_table;
HashMap<String, EditorPlugin *> main_editor_plugins;

int _get_current_main_editor() const;

Expand All @@ -80,6 +81,7 @@ class EditorMainScreen : public PanelContainer {
int get_selected_index() const;
int get_plugin_index(EditorPlugin *p_editor) const;
EditorPlugin *get_selected_plugin() const;
EditorPlugin *get_plugin_by_name(const String &p_plugin_name) const;

VBoxContainer *get_control() const;

Expand Down
18 changes: 11 additions & 7 deletions editor/editor_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -936,17 +936,21 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("run/window_placement/rect_custom_position", Vector2());
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/screen", -5, screen_hints)
#endif
// Should match the ANDROID_WINDOW_* constants in 'platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt'
String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2,Launch in PiP mode:3";
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints)

EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/game_embed_mode", 0, "Use Per-Project Configuration:0,Embed Game:1,Make Game Workspace Floating:2,Disabled:3");
String game_embed_mode_hints = "Disabled:-1,Use Per-Project Configuration:0,Embed Game:1,Make Game Workspace Floating:2";
#ifdef ANDROID_ENABLED
if (OS::get_singleton()->has_feature("xr_editor")) {
game_embed_mode_hints = "Disabled:-1";
} else {
game_embed_mode_hints = "Disabled:-1,Auto (based on screen size):0,Enabled:1";
}
#endif
int default_game_embed_mode = OS::get_singleton()->has_feature("xr_editor") ? -1 : 0;
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/game_embed_mode", default_game_embed_mode, game_embed_mode_hints);

int default_play_window_pip_mode = 0;
#ifdef ANDROID_ENABLED
default_play_window_pip_mode = 2;
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/enable_game_menu_bar", 0, "Enabled For All Games:0,Enabled For Embedded Games Only:1");
#endif
EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/play_window_pip_mode", default_play_window_pip_mode, "Disabled:0,Enabled:1,Enabled when Play window is same as Editor:2")

// Auto save
_initial_set("run/auto_save/save_before_running", true, true);
Expand Down
54 changes: 36 additions & 18 deletions editor/plugins/game_view_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,10 @@ void GameView::_notification(int p_what) {
// Embedding available.
int game_mode = EDITOR_GET("run/window_placement/game_embed_mode");
switch (game_mode) {
case -1: { // Disabled.
embed_on_play = false;
make_floating_on_play = false;
} break;
case 1: { // Embed.
embed_on_play = true;
make_floating_on_play = false;
Expand All @@ -620,10 +624,6 @@ void GameView::_notification(int p_what) {
embed_on_play = true;
make_floating_on_play = true;
} break;
case 3: { // Disabled.
embed_on_play = false;
make_floating_on_play = false;
} break;
default: {
embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true);
make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true);
Expand Down Expand Up @@ -1019,6 +1019,18 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {

///////

void GameViewPlugin::selected_notify() {
if (_is_window_wrapper_enabled()) {
#ifdef ANDROID_ENABLED
notify_main_screen_changed(get_plugin_name());
#else
window_wrapper->grab_window_focus();
#endif
_focus_another_editor();
}
}

#ifndef ANDROID_ENABLED
void GameViewPlugin::make_visible(bool p_visible) {
if (p_visible) {
window_wrapper->show();
Expand All @@ -1027,13 +1039,6 @@ void GameViewPlugin::make_visible(bool p_visible) {
}
}

void GameViewPlugin::selected_notify() {
if (window_wrapper->get_window_enabled()) {
window_wrapper->grab_window_focus();
_focus_another_editor();
}
}

void GameViewPlugin::set_window_layout(Ref<ConfigFile> p_layout) {
game_view->set_window_layout(p_layout);
}
Expand All @@ -1050,6 +1055,11 @@ Dictionary GameViewPlugin::get_state() const {
return game_view->get_state();
}

void GameViewPlugin::_window_visibility_changed(bool p_visible) {
_focus_another_editor();
}
#endif

void GameViewPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
Expand All @@ -1074,13 +1084,11 @@ void GameViewPlugin::_feature_profile_changed() {
debugger->set_is_feature_enabled(is_feature_enabled);
}

#ifndef ANDROID_ENABLED
if (game_view) {
game_view->set_is_feature_enabled(is_feature_enabled);
}
}

void GameViewPlugin::_window_visibility_changed(bool p_visible) {
_focus_another_editor();
#endif
}

void GameViewPlugin::_save_last_editor(const String &p_editor) {
Expand All @@ -1090,7 +1098,7 @@ void GameViewPlugin::_save_last_editor(const String &p_editor) {
}

void GameViewPlugin::_focus_another_editor() {
if (window_wrapper->get_window_enabled()) {
if (_is_window_wrapper_enabled()) {
if (last_editor.is_empty()) {
EditorNode::get_singleton()->get_editor_main_screen()->select(EditorMainScreen::EDITOR_2D);
} else {
Expand All @@ -1099,13 +1107,22 @@ void GameViewPlugin::_focus_another_editor() {
}
}

bool GameViewPlugin::_is_window_wrapper_enabled() const {
#ifdef ANDROID_ENABLED
return true;
#else
return window_wrapper->get_window_enabled();
#endif
}

GameViewPlugin::GameViewPlugin() {
debugger.instantiate();

#ifndef ANDROID_ENABLED
window_wrapper = memnew(WindowWrapper);
window_wrapper->set_window_title(vformat(TTR("%s - Godot Engine"), TTR("Game Workspace")));
window_wrapper->set_margins_enabled(true);

debugger.instantiate();

game_view = memnew(GameView(debugger, window_wrapper));
game_view->set_v_size_flags(Control::SIZE_EXPAND_FILL);

Expand All @@ -1115,6 +1132,7 @@ GameViewPlugin::GameViewPlugin() {
window_wrapper->set_v_size_flags(Control::SIZE_EXPAND_FILL);
window_wrapper->hide();
window_wrapper->connect("window_visibility_changed", callable_mp(this, &GameViewPlugin::_window_visibility_changed));
#endif

EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameViewPlugin::_feature_profile_changed));
}
Expand Down
13 changes: 12 additions & 1 deletion editor/plugins/game_view_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#define GAME_VIEW_PLUGIN_H

#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_main_screen.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_plugin.h"
#include "scene/debugger/scene_debugger.h"
Expand Down Expand Up @@ -208,17 +209,22 @@ class GameView : public VBoxContainer {
class GameViewPlugin : public EditorPlugin {
GDCLASS(GameViewPlugin, EditorPlugin);

#ifndef ANDROID_ENABLED
GameView *game_view = nullptr;
WindowWrapper *window_wrapper = nullptr;
#endif

Ref<GameViewDebugger> debugger;

String last_editor;

void _feature_profile_changed();
#ifndef ANDROID_ENABLED
void _window_visibility_changed(bool p_visible);
#endif
void _save_last_editor(const String &p_editor);
void _focus_another_editor();
bool _is_window_wrapper_enabled() const;

protected:
void _notification(int p_what);
Expand All @@ -228,14 +234,19 @@ class GameViewPlugin : public EditorPlugin {
bool has_main_screen() const override { return true; }
virtual void edit(Object *p_object) override {}
virtual bool handles(Object *p_object) const override { return false; }
virtual void make_visible(bool p_visible) override;
virtual void selected_notify() override;

Ref<GameViewDebugger> get_debugger() const { return debugger; }

#ifndef ANDROID_ENABLED
virtual void make_visible(bool p_visible) override;

virtual void set_window_layout(Ref<ConfigFile> p_layout) override;
virtual void get_window_layout(Ref<ConfigFile> p_layout) override;

virtual void set_state(const Dictionary &p_state) override;
virtual Dictionary get_state() const override;
#endif

GameViewPlugin();
~GameViewPlugin();
Expand Down
1 change: 1 addition & 0 deletions platform/android/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ android_files = [
"rendering_context_driver_vulkan_android.cpp",
"variant/callable_jni.cpp",
"dialog_utils_jni.cpp",
"game_menu_utils_jni.cpp",
]

env_android = env.Clone()
Expand Down
Loading

0 comments on commit caa8a1b

Please sign in to comment.