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 6, 2025
1 parent 1586c56 commit 33b3d02
Show file tree
Hide file tree
Showing 79 changed files with 2,256 additions and 345 deletions.
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
16 changes: 8 additions & 8 deletions editor/editor_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -936,17 +936,17 @@ 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");

int default_play_window_pip_mode = 0;
String game_embed_mode_hints = "Disabled:-1,Use Per-Project Configuration:0,Embed Game:1,Make Game Workspace Floating:2";
#ifdef ANDROID_ENABLED
default_play_window_pip_mode = 2;
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
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")
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);

// 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 @@ -31,6 +31,7 @@
#ifndef GAME_VIEW_PLUGIN_H
#define GAME_VIEW_PLUGIN_H

#include "editor/editor_main_screen.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/plugins/editor_debugger_plugin.h"
#include "editor/plugins/editor_plugin.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
118 changes: 118 additions & 0 deletions platform/android/game_menu_utils_jni.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**************************************************************************/
/* game_menu_utils_jni.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#include "game_menu_utils_jni.h"

#include "editor/editor_node.h"
#include "editor/plugins/game_view_plugin.h"

extern "C" {

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSuspend(JNIEnv *env, jclass clazz, jboolean enabled) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->set_suspend(enabled);
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_nextFrame(JNIEnv *env, jclass clazz) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->next_frame();
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setNodeType(JNIEnv *env, jclass clazz, jint type) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->set_node_type(type);
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelectMode(JNIEnv *env, jclass clazz, jint mode) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->set_select_mode(mode);
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setSelectionVisible(JNIEnv *env, jclass clazz, jboolean visible) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->set_selection_visible(visible);
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraOverride(JNIEnv *env, jclass clazz, jboolean enabled) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->set_camera_override(enabled);
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_setCameraManipulateMode(JNIEnv *env, jclass clazz, jint mode) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->set_camera_manipulate_mode(static_cast<EditorDebuggerNode::CameraOverride>(mode));
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamera2DPosition(JNIEnv *env, jclass clazz) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->reset_camera_2d_position();
}
#endif
}

JNIEXPORT void JNICALL Java_org_godotengine_godot_utils_GameMenuUtils_resetCamera3DPosition(JNIEnv *env, jclass clazz) {
#ifdef TOOLS_ENABLED
GameViewPlugin *game_view_plugin = Object::cast_to<GameViewPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_plugin_by_name("Game"));
if (game_view_plugin != nullptr && game_view_plugin->get_debugger().is_valid()) {
game_view_plugin->get_debugger()->reset_camera_3d_position();
}
#endif
}
}
Loading

0 comments on commit 33b3d02

Please sign in to comment.