diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index bd4762bd1443..cefd4ed52c64 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1673,6 +1673,13 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1);
GLOBAL_DEF("input_devices/pointing/android/override_volume_buttons", false);
GLOBAL_DEF_BASIC("input_devices/pointing/android/disable_scroll_deadzone", false);
+ GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_controller", false);
+ GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_left_thumbstick", true);
+ GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_right_thumbstick", true);
+ GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_a", true);
+ GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_b", true);
+ GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_x", true);
+ GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_y", true);
// These properties will not show up in the dialog. If you want to exclude whole groups, use add_hidden_prefix().
GLOBAL_DEF_INTERNAL("application/config/features", PackedStringArray());
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 1ee99764d216..88f5e3e19682 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -168,6 +168,7 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_emulating_mouse_from_touch"), &Input::is_emulating_mouse_from_touch);
ClassDB::bind_method(D_METHOD("set_emulate_touch_from_mouse", "enable"), &Input::set_emulate_touch_from_mouse);
ClassDB::bind_method(D_METHOD("is_emulating_touch_from_mouse"), &Input::is_emulating_touch_from_mouse);
+ ClassDB::bind_method(D_METHOD("get_virtual_controller"), &Input::get_virtual_controller);
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_mode"), "set_mouse_mode", "get_mouse_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_accumulated_input"), "set_use_accumulated_input", "is_using_accumulated_input");
@@ -1054,6 +1055,10 @@ bool Input::is_emulating_touch_from_mouse() const {
return emulate_touch_from_mouse;
}
+VirtualController *Input::get_virtual_controller() {
+ return OS::get_singleton()->get_virtual_controller();
+}
+
// Calling this whenever the game window is focused helps unsticking the "touch mouse"
// if the OS or its abstraction class hasn't properly reported that touch pointers raised
void Input::ensure_touch_mouse_raised() {
diff --git a/core/input/input.h b/core/input/input.h
index 10d357a1f92d..c72aed339931 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -31,6 +31,7 @@
#pragma once
#include "core/input/input_event.h"
+#include "core/input/virtual_controller.h"
#include "core/object/object.h"
#include "core/os/keyboard.h"
#include "core/os/thread_safe.h"
@@ -387,6 +388,8 @@ class Input : public Object {
void set_use_accumulated_input(bool p_enable);
bool is_using_accumulated_input();
+ VirtualController *get_virtual_controller();
+
void release_pressed_events();
void set_event_dispatch_function(EventDispatchFunc p_function);
diff --git a/core/input/virtual_controller.cpp b/core/input/virtual_controller.cpp
new file mode 100644
index 000000000000..a7f702a2c335
--- /dev/null
+++ b/core/input/virtual_controller.cpp
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* virtual_controller.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 "virtual_controller.h"
+
+void VirtualController::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("enable"), &VirtualController::enable);
+ ClassDB::bind_method(D_METHOD("disable"), &VirtualController::disable);
+ ClassDB::bind_method(D_METHOD("is_enabled"), &VirtualController::is_enabled);
+ ClassDB::bind_method(D_METHOD("set_enabled_left_thumbstick", "enable"), &VirtualController::set_enabled_left_thumbstick);
+ ClassDB::bind_method(D_METHOD("is_enabled_left_thumbstick"), &VirtualController::is_enabled_left_thumbstick);
+ ClassDB::bind_method(D_METHOD("set_enabled_right_thumbstick", "enable"), &VirtualController::set_enabled_right_thumbstick);
+ ClassDB::bind_method(D_METHOD("is_enabled_right_thumbstick"), &VirtualController::is_enabled_right_thumbstick);
+ ClassDB::bind_method(D_METHOD("set_enabled_button_a", "enable"), &VirtualController::set_enabled_button_a);
+ ClassDB::bind_method(D_METHOD("is_enabled_button_a"), &VirtualController::is_enabled_button_a);
+ ClassDB::bind_method(D_METHOD("set_enabled_button_b", "enable"), &VirtualController::set_enabled_button_b);
+ ClassDB::bind_method(D_METHOD("is_enabled_button_b"), &VirtualController::is_enabled_button_b);
+ ClassDB::bind_method(D_METHOD("set_enabled_button_x", "enable"), &VirtualController::set_enabled_button_x);
+ ClassDB::bind_method(D_METHOD("is_enabled_button_x"), &VirtualController::is_enabled_button_x);
+ ClassDB::bind_method(D_METHOD("set_enabled_button_y", "enable"), &VirtualController::set_enabled_button_y);
+ ClassDB::bind_method(D_METHOD("is_enabled_button_y"), &VirtualController::is_enabled_button_y);
+}
diff --git a/core/input/virtual_controller.h b/core/input/virtual_controller.h
new file mode 100644
index 000000000000..0188e7fb7128
--- /dev/null
+++ b/core/input/virtual_controller.h
@@ -0,0 +1,57 @@
+/**************************************************************************/
+/* virtual_controller.h */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#pragma once
+
+#include "core/object/class_db.h"
+
+class VirtualController : public Object {
+ GDCLASS(VirtualController, Object);
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual void enable() = 0;
+ virtual void disable() = 0;
+ virtual bool is_enabled() = 0;
+ virtual void set_enabled_left_thumbstick(bool p_enabled) = 0;
+ virtual bool is_enabled_left_thumbstick() = 0;
+ virtual void set_enabled_right_thumbstick(bool p_enabled) = 0;
+ virtual bool is_enabled_right_thumbstick() = 0;
+ virtual void set_enabled_button_a(bool p_enabled) = 0;
+ virtual bool is_enabled_button_a() = 0;
+ virtual void set_enabled_button_b(bool p_enabled) = 0;
+ virtual bool is_enabled_button_b() = 0;
+ virtual void set_enabled_button_x(bool p_enabled) = 0;
+ virtual bool is_enabled_button_x() = 0;
+ virtual void set_enabled_button_y(bool p_enabled) = 0;
+ virtual bool is_enabled_button_y() = 0;
+};
diff --git a/core/os/os.cpp b/core/os/os.cpp
index 29e49f8f65fe..25aeead0dce8 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -779,6 +779,16 @@ void OS::benchmark_dump() {
#endif
}
+VirtualController *OS::get_virtual_controller() const {
+ return nullptr;
+}
+
+void OS::controller_connected() const {
+}
+
+void OS::controller_disconnected() const {
+}
+
OS::OS() {
singleton = this;
diff --git a/core/os/os.h b/core/os/os.h
index b70516436e99..fbb8457b4528 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -31,6 +31,7 @@
#pragma once
#include "core/config/engine.h"
+#include "core/input/virtual_controller.h"
#include "core/io/logger.h"
#include "core/io/remote_filesystem_client.h"
#include "core/os/time_enums.h"
@@ -371,6 +372,12 @@ class OS {
virtual bool _test_create_rendering_device(const String &p_display_driver) const { return true; }
#endif
+ virtual VirtualController *get_virtual_controller() const;
+
+ virtual void controller_connected() const;
+
+ virtual void controller_disconnected() const;
+
OS();
virtual ~OS();
};
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index 19446e175580..d41e63042037 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -42,6 +42,7 @@
#include "core/input/input.h"
#include "core/input/input_map.h"
#include "core/input/shortcut.h"
+#include "core/input/virtual_controller.h"
#include "core/io/config_file.h"
#include "core/io/dir_access.h"
#include "core/io/dtls_server.h"
@@ -314,6 +315,8 @@ void register_core_types() {
GDREGISTER_NATIVE_STRUCT(AudioFrame, "float left;float right");
GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time");
+ GDREGISTER_ABSTRACT_CLASS(VirtualController);
+
worker_thread_pool = memnew(WorkerThreadPool);
OS::get_singleton()->benchmark_end_measure("Core", "Register Types");
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index ca54a44f489a..f6a4be1ab4a2 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -202,6 +202,13 @@
By default, the deadzone is automatically calculated from the average of the action deadzones. However, you can override the deadzone to be whatever you want (on the range of 0 to 1).
+
+
+
+ Return the platform-specific virtual controller or [code]null[/code] if it is not available. See also [VirtualController].
+ [b]Note:[/b] Currently implemented only on iOS 15.0+.
+
+
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index d44d4edb13fa..1621b78fc84e 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1608,6 +1608,28 @@
If [code]true[/code], the magnetometer sensor is enabled and [method Input.get_magnetometer] returns valid data.
+
+ If [code]true[/code], the button "A" shows in the virtual controller.
+
+
+ If [code]true[/code], the button "B" shows in the virtual controller.
+
+
+ If [code]true[/code], the button "X" shows in the virtual controller.
+
+
+ If [code]true[/code], the button "Y" shows in the virtual controller.
+
+
+ If [code]true[/code], if there are no physical controllers connected, the game shows the virtual controller.
+ [b]Note:[/b] Currently implemented only on iOS 15.0+.
+
+
+ If [code]true[/code], the left thumbstick shows in the virtual controller.
+
+
+ If [code]true[/code], the right thumbstick shows in the virtual controller.
+
The locale to fall back to if a translation isn't available in a given language. If left empty, [code]en[/code] (English) will be used.
[b]Note:[/b] Not to be confused with [TextServerFallback].
diff --git a/doc/classes/VirtualController.xml b/doc/classes/VirtualController.xml
new file mode 100644
index 000000000000..b2d510a0a6ba
--- /dev/null
+++ b/doc/classes/VirtualController.xml
@@ -0,0 +1,110 @@
+
+
+
+ A software emulation of a real controller that you configure specifically for your game.
+
+
+ Use a virtual controller to display software controls that you can customize over your game. You create a virtual controller from a configuration where you choose the input elements to display. When you connect the controller to the device, users interact with it similarly to a real controller.
+ [b]Note:[/b] Currently implemented only on iOS 15.0+.
+
+
+
+
+
+
+
+ Disconnects the virtual controller from the device and removes it from the screen.
+
+
+
+
+
+ If there are no physical controllers connected, connects the virtual controller to the device and displays it on the screen.
+
+
+
+
+
+ Returns [code]true[/code] if the virtual controller is enabled.
+
+
+
+
+
+ Returns [code]true[/code] if the button "A" is enabled.
+
+
+
+
+
+ Returns [code]true[/code] if the button "B" is enabled.
+
+
+
+
+
+ Returns [code]true[/code] if the button "X" is enabled.
+
+
+
+
+
+ Returns [code]true[/code] if the button "Y" is enabled.
+
+
+
+
+
+ Returns [code]true[/code] if the left thumbstick is enabled.
+
+
+
+
+
+ Returns [code]true[/code] if the right thumbstick is enabled.
+
+
+
+
+
+
+ Changes the visibility of a button "A" element. Default is [code]true[/code].
+
+
+
+
+
+
+ Changes the visibility of a button "B" element. Default is [code]true[/code].
+
+
+
+
+
+
+ Changes the visibility of a button "A" element. Default is [code]true[/code].
+
+
+
+
+
+
+ Changes the visibility of a button "Y" element. Default is [code]true[/code].
+
+
+
+
+
+
+ Changes the visibility of a left thumbstick element. Default is [code]true[/code].
+
+
+
+
+
+
+ Changes the visibility of a right thumbstick element. Default is [code]true[/code].
+
+
+
+
diff --git a/drivers/apple/joypad_apple.mm b/drivers/apple/joypad_apple.mm
index 38dfc5aca8cd..cae366e274d1 100644
--- a/drivers/apple/joypad_apple.mm
+++ b/drivers/apple/joypad_apple.mm
@@ -34,6 +34,7 @@
#import
#include "core/config/project_settings.h"
+#include "core/os/os.h"
#include "main/main.h"
class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleMotor {
@@ -432,6 +433,7 @@ void play_strong_pattern(CHHapticPattern *p_pattern) {
return;
}
add_joypad(controller);
+ OS::get_singleton()->controller_connected();
}];
disconnect_observer = [NSNotificationCenter.defaultCenter
@@ -444,6 +446,7 @@ void play_strong_pattern(CHHapticPattern *p_pattern) {
return;
}
remove_joypad(controller);
+ OS::get_singleton()->controller_disconnected();
}];
if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) {
diff --git a/drivers/apple_embedded/os_apple_embedded.h b/drivers/apple_embedded/os_apple_embedded.h
index 6bbf31852315..090256a3151b 100644
--- a/drivers/apple_embedded/os_apple_embedded.h
+++ b/drivers/apple_embedded/os_apple_embedded.h
@@ -64,8 +64,6 @@ class OS_AppleEmbedded : public OS_Unix {
virtual void initialize_core() override;
virtual void initialize() override;
- virtual void initialize_joypads() override;
-
virtual void set_main_loop(MainLoop *p_main_loop) override;
virtual MainLoop *get_main_loop() const override;
@@ -81,7 +79,9 @@ class OS_AppleEmbedded : public OS_Unix {
static _FORCE_INLINE_ String get_framework_executable(const String &p_path);
- void deinitialize_modules();
+protected:
+ virtual void initialize_joypads() override;
+ virtual void deinitialize_modules() = 0;
public:
static OS_AppleEmbedded *get_singleton();
@@ -93,7 +93,7 @@ class OS_AppleEmbedded : public OS_Unix {
bool iterate();
- void start();
+ virtual void start() = 0;
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
diff --git a/platform/ios/SCsub b/platform/ios/SCsub
index a313442e2ae3..0989bcf2b9fb 100644
--- a/platform/ios/SCsub
+++ b/platform/ios/SCsub
@@ -14,6 +14,7 @@ ios_lib = [
"godot_view_ios.mm",
"main_ios.mm",
"os_ios.mm",
+ "virtual_controller_ios.mm",
]
env_ios = env.Clone()
diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h
index 89eeab1fffd3..e6a6cd80f474 100644
--- a/platform/ios/os_ios.h
+++ b/platform/ios/os_ios.h
@@ -33,8 +33,14 @@
#ifdef IOS_ENABLED
#import "drivers/apple_embedded/os_apple_embedded.h"
+#import "virtual_controller_ios.h"
class OS_IOS : public OS_AppleEmbedded {
+private:
+ IOSVirtualController *virtual_controller = nullptr;
+ virtual void deinitialize_modules() override;
+ virtual void initialize_joypads() override;
+
public:
static OS_IOS *get_singleton();
@@ -42,6 +48,10 @@ class OS_IOS : public OS_AppleEmbedded {
~OS_IOS();
virtual String get_name() const override;
+ virtual void start() override;
+ virtual VirtualController *get_virtual_controller() const override;
+ virtual void controller_connected() const override;
+ virtual void controller_disconnected() const override;
};
#endif // IOS_ENABLED
diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm
index 6aa35c5404e2..ae427c971a09 100644
--- a/platform/ios/os_ios.mm
+++ b/platform/ios/os_ios.mm
@@ -31,6 +31,7 @@
#import "os_ios.h"
#import "display_server_ios.h"
+#include
#ifdef IOS_ENABLED
@@ -49,4 +50,44 @@
return "iOS";
}
+void OS_IOS::start() {
+ OS_AppleEmbedded::start();
+
+ if (virtual_controller) {
+ virtual_controller->update_state();
+ std::cout << "SHOW VIRTUAL CONTROLLER\n";
+ }
+}
+
+void OS_IOS::deinitialize_modules() {
+ if (virtual_controller) {
+ memdelete(virtual_controller);
+ }
+ OS_AppleEmbedded::deinitialize_modules();
+}
+
+void OS_IOS::initialize_joypads() {
+ OS_AppleEmbedded::initialize_joypads();
+
+ std::cout << "CREATE VIRTUAL CONTROLLER\n";
+
+ virtual_controller = memnew(IOSVirtualController);
+}
+
+VirtualController *OS_IOS::get_virtual_controller() const {
+ return virtual_controller;
+}
+
+void OS_IOS::controller_connected() const {
+ if (virtual_controller) {
+ virtual_controller->controller_connected();
+ }
+}
+
+void OS_IOS::controller_disconnected() const {
+ if (virtual_controller) {
+ virtual_controller->controller_disconnected();
+ }
+}
+
#endif // IOS_ENABLED
diff --git a/platform/ios/virtual_controller_ios.h b/platform/ios/virtual_controller_ios.h
new file mode 100644
index 000000000000..85fc3baf0973
--- /dev/null
+++ b/platform/ios/virtual_controller_ios.h
@@ -0,0 +1,83 @@
+/**************************************************************************/
+/* virtual_controller_ios.h */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#pragma once
+
+#import "core/input/virtual_controller.h"
+
+#import
+
+class IOSVirtualController : public VirtualController {
+private:
+#if defined(__IPHONE_15_0)
+ API_AVAILABLE(ios(15.0))
+ GCVirtualController *gcv_controller;
+#endif
+
+public:
+ IOSVirtualController();
+ ~IOSVirtualController();
+
+ virtual void enable() override;
+ virtual void disable() override;
+ virtual bool is_enabled() override;
+ virtual void set_enabled_left_thumbstick(bool p_enabled) override;
+ virtual bool is_enabled_left_thumbstick() override;
+ virtual void set_enabled_right_thumbstick(bool p_enabled) override;
+ virtual bool is_enabled_right_thumbstick() override;
+ virtual void set_enabled_button_a(bool p_enabled) override;
+ virtual bool is_enabled_button_a() override;
+ virtual void set_enabled_button_b(bool p_enabled) override;
+ virtual bool is_enabled_button_b() override;
+ virtual void set_enabled_button_x(bool p_enabled) override;
+ virtual bool is_enabled_button_x() override;
+ virtual void set_enabled_button_y(bool p_enabled) override;
+ virtual bool is_enabled_button_y() override;
+
+ void controller_connected();
+ void controller_disconnected();
+ void update_state();
+
+private:
+ void connect_controller();
+ void disconnect_controller();
+ void initialize();
+ void elements_changed(GCInputElementName p_name, bool p_hidden);
+ void read_project_settings();
+
+private:
+ bool enabled = false;
+ bool enabled_left_thumbstick = true;
+ bool enabled_right_thumbstick = true;
+ bool enabled_button_a = true;
+ bool enabled_button_b = true;
+ bool enabled_button_x = true;
+ bool enabled_button_y = true;
+};
diff --git a/platform/ios/virtual_controller_ios.mm b/platform/ios/virtual_controller_ios.mm
new file mode 100644
index 000000000000..546f75fed20d
--- /dev/null
+++ b/platform/ios/virtual_controller_ios.mm
@@ -0,0 +1,247 @@
+/**************************************************************************/
+/* virtual_controller_ios.mm */
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#import "virtual_controller_ios.h"
+
+#include "core/config/project_settings.h"
+
+IOSVirtualController::IOSVirtualController() {
+ initialize();
+}
+
+IOSVirtualController::~IOSVirtualController() {
+ if (@available(iOS 15.0, *)) {
+ gcv_controller = nullptr;
+ }
+}
+
+void IOSVirtualController::initialize() {
+ if (@available(iOS 15.0, *)) {
+ if (!gcv_controller) {
+ read_project_settings();
+
+ GCVirtualControllerConfiguration *config = [[GCVirtualControllerConfiguration alloc] init];
+
+ NSMutableSet *elements = [[NSMutableSet alloc] init];
+
+ if (is_enabled_left_thumbstick()) {
+ [elements addObject:GCInputLeftThumbstick];
+ }
+ if (is_enabled_right_thumbstick()) {
+ [elements addObject:GCInputRightThumbstick];
+ }
+ if (is_enabled_button_a()) {
+ [elements addObject:GCInputButtonA];
+ }
+ if (is_enabled_button_b()) {
+ [elements addObject:GCInputButtonB];
+ }
+ if (is_enabled_button_x()) {
+ [elements addObject:GCInputButtonX];
+ }
+ if (is_enabled_button_y()) {
+ [elements addObject:GCInputButtonY];
+ }
+
+ config.elements = elements;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ gcv_controller = [[GCVirtualController alloc] initWithConfiguration:config];
+ });
+ }
+ }
+}
+
+void IOSVirtualController::read_project_settings() {
+ enabled = GLOBAL_GET("input_devices/virtual_controller/ios/enable_controller");
+ enabled_left_thumbstick = GLOBAL_GET("input_devices/virtual_controller/ios/enable_left_thumbstick");
+ enabled_right_thumbstick = GLOBAL_GET("input_devices/virtual_controller/ios/enable_right_thumbstick");
+ enabled_button_a = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_a");
+ enabled_button_b = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_b");
+ enabled_button_x = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_x");
+ enabled_button_y = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_y");
+}
+
+void IOSVirtualController::elements_changed(GCInputElementName p_name, bool p_hidden) {
+ if (@available(iOS 15.0, *)) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (gcv_controller) {
+ [gcv_controller updateConfigurationForElement:p_name
+ configuration:^(GCVirtualControllerElementConfiguration *configuration) {
+ configuration.hidden = p_hidden;
+ return configuration;
+ }];
+ }
+ });
+ }
+}
+
+void IOSVirtualController::enable() {
+ enabled = true;
+ update_state();
+}
+
+void IOSVirtualController::disable() {
+ enabled = false;
+ update_state();
+}
+
+void IOSVirtualController::update_state() {
+ if (is_enabled()) {
+ connect_controller();
+ } else {
+ disconnect_controller();
+ }
+}
+
+bool IOSVirtualController::is_enabled() {
+ return enabled;
+}
+
+void IOSVirtualController::connect_controller() {
+ if (@available(iOS 15.0, *)) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (GCController.controllers.count == 0 && gcv_controller != nil) {
+ [gcv_controller connectWithReplyHandler:nil];
+ }
+ });
+ }
+}
+
+void IOSVirtualController::disconnect_controller() {
+ if (@available(iOS 15.0, *)) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (gcv_controller) {
+ [gcv_controller disconnect];
+ }
+ });
+ }
+}
+
+void IOSVirtualController::controller_connected() {
+ if (@available(iOS 15.0, *)) {
+ if (gcv_controller != nil) {
+ BOOL has_physical_controller = NO;
+ for (GCController *controller in GCController.controllers) {
+ if (controller != gcv_controller.controller) {
+ has_physical_controller = YES;
+ break;
+ }
+ }
+ if (has_physical_controller) {
+ disconnect_controller();
+ }
+ }
+ }
+}
+
+void IOSVirtualController::controller_disconnected() {
+ if (is_enabled()) {
+ connect_controller();
+ }
+}
+
+void IOSVirtualController::set_enabled_left_thumbstick(bool p_enabled) {
+ if (enabled_left_thumbstick != p_enabled) {
+ enabled_left_thumbstick = p_enabled;
+ if (@available(iOS 15.0, *)) {
+ elements_changed(GCInputLeftThumbstick, !p_enabled);
+ }
+ }
+}
+
+bool IOSVirtualController::is_enabled_left_thumbstick() {
+ return enabled_left_thumbstick;
+}
+
+void IOSVirtualController::set_enabled_right_thumbstick(bool p_enabled) {
+ if (enabled_right_thumbstick != p_enabled) {
+ enabled_right_thumbstick = p_enabled;
+ if (@available(iOS 15.0, *)) {
+ elements_changed(GCInputRightThumbstick, !p_enabled);
+ }
+ }
+}
+
+bool IOSVirtualController::is_enabled_right_thumbstick() {
+ return enabled_right_thumbstick;
+}
+
+void IOSVirtualController::set_enabled_button_a(bool p_enabled) {
+ if (enabled_button_a != p_enabled) {
+ enabled_button_a = p_enabled;
+ if (@available(iOS 15.0, *)) {
+ elements_changed(GCInputButtonA, !p_enabled);
+ }
+ }
+}
+
+bool IOSVirtualController::is_enabled_button_a() {
+ return enabled_button_a;
+}
+
+void IOSVirtualController::set_enabled_button_b(bool p_enabled) {
+ if (enabled_button_b != p_enabled) {
+ enabled_button_b = p_enabled;
+ if (@available(iOS 15.0, *)) {
+ elements_changed(GCInputButtonB, !p_enabled);
+ }
+ }
+}
+
+bool IOSVirtualController::is_enabled_button_b() {
+ return enabled_button_b;
+}
+
+void IOSVirtualController::set_enabled_button_x(bool p_enabled) {
+ if (enabled_button_x != p_enabled) {
+ enabled_button_x = p_enabled;
+ if (@available(iOS 15.0, *)) {
+ elements_changed(GCInputButtonX, !p_enabled);
+ }
+ }
+}
+
+bool IOSVirtualController::is_enabled_button_x() {
+ return enabled_button_x;
+}
+
+void IOSVirtualController::set_enabled_button_y(bool p_enabled) {
+ if (enabled_button_y != p_enabled) {
+ enabled_button_y = p_enabled;
+ if (@available(iOS 15.0, *)) {
+ elements_changed(GCInputButtonY, !p_enabled);
+ }
+ }
+}
+
+bool IOSVirtualController::is_enabled_button_y() {
+ return enabled_button_y;
+}