diff --git a/core/input/input.cpp b/core/input/input.cpp
index eba7ded267b..936e30a193e 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -149,11 +149,32 @@ 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("set_enable_ios_virtual_control", "enable"), &Input::set_enable_ios_virtual_control);
+ ClassDB::bind_method(D_METHOD("is_enable_ios_virtual_control"), &Input::is_enable_ios_virtual_control);
+ ClassDB::bind_method(D_METHOD("set_enable_left_thumbstick", "enable"), &Input::set_enable_left_thumbstick);
+ ClassDB::bind_method(D_METHOD("is_enable_left_thumbstick"), &Input::is_enable_left_thumbstick);
+ ClassDB::bind_method(D_METHOD("set_enable_right_thumbstick", "enable"), &Input::set_enable_right_thumbstick);
+ ClassDB::bind_method(D_METHOD("is_enable_right_thumbstick"), &Input::is_enable_right_thumbstick);
+ ClassDB::bind_method(D_METHOD("set_enable_button_a", "enable"), &Input::set_enable_button_a);
+ ClassDB::bind_method(D_METHOD("is_enable_button_a"), &Input::is_enable_button_a);
+ ClassDB::bind_method(D_METHOD("set_enable_button_b", "enable"), &Input::set_enable_button_b);
+ ClassDB::bind_method(D_METHOD("is_enable_button_b"), &Input::is_enable_button_b);
+ ClassDB::bind_method(D_METHOD("set_enable_button_x", "enable"), &Input::set_enable_button_x);
+ ClassDB::bind_method(D_METHOD("is_enable_button_x"), &Input::is_enable_button_x);
+ ClassDB::bind_method(D_METHOD("set_enable_button_y", "enable"), &Input::set_enable_button_y);
+ ClassDB::bind_method(D_METHOD("is_enable_button_y"), &Input::is_enable_button_y);
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");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emulate_mouse_from_touch"), "set_emulate_mouse_from_touch", "is_emulating_mouse_from_touch");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emulate_touch_from_mouse"), "set_emulate_touch_from_mouse", "is_emulating_touch_from_mouse");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_ios_virtual_control"), "set_enable_ios_virtual_control", "is_enable_ios_virtual_control");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_left_thumbstick"), "set_enable_left_thumbstick", "is_enable_left_thumbstick");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_right_thumbstick"), "set_enable_right_thumbstick", "is_enable_right_thumbstick");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_button_a"), "set_enable_button_a", "is_enable_button_a");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_button_b"), "set_enable_button_b", "is_enable_button_b");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_button_x"), "set_enable_button_x", "is_enable_button_x");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_button_y"), "set_enable_button_y", "is_enable_button_y");
BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE);
BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN);
@@ -954,6 +975,62 @@ bool Input::is_emulating_touch_from_mouse() const {
return emulate_touch_from_mouse;
}
+void Input::set_enable_ios_virtual_control(bool p_enable) {
+ enable_ios_virtual_control = p_enable;
+}
+
+bool Input::is_enable_ios_virtual_control() const {
+ return enable_ios_virtual_control;
+}
+
+void Input::set_enable_left_thumbstick(bool p_enable) {
+ enable_left_thumbstick = p_enable;
+}
+
+bool Input::is_enable_left_thumbstick() const {
+ return enable_left_thumbstick;
+}
+
+void Input::set_enable_right_thumbstick(bool p_enable) {
+ enable_right_thumbstick = p_enable;
+}
+
+bool Input::is_enable_right_thumbstick() const {
+ return enable_right_thumbstick;
+}
+
+void Input::set_enable_button_a(bool p_enable) {
+ enable_button_a = p_enable;
+}
+
+bool Input::is_enable_button_a() const {
+ return enable_button_a;
+}
+
+void Input::set_enable_button_b(bool p_enable) {
+ enable_button_b = p_enable;
+}
+
+bool Input::is_enable_button_b() const {
+ return enable_button_b;
+}
+
+void Input::set_enable_button_x(bool p_enable) {
+ enable_button_x = p_enable;
+}
+
+bool Input::is_enable_button_x() const {
+ return enable_button_x;
+}
+
+void Input::set_enable_button_y(bool p_enable) {
+ enable_button_y = p_enable;
+}
+
+bool Input::is_enable_button_y() const {
+ return enable_button_y;
+}
+
// 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 95dd623cc03..7df82d89c09 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -135,6 +135,14 @@ private:
bool agile_input_event_flushing = false;
bool use_accumulated_input = true;
+ bool enable_ios_virtual_control = false;
+ bool enable_left_thumbstick;
+ bool enable_right_thumbstick;
+ bool enable_button_a;
+ bool enable_button_b;
+ bool enable_button_x;
+ bool enable_button_y;
+
int mouse_from_touch_index = -1;
struct VibrationInfo {
@@ -376,6 +384,21 @@ public:
void set_use_accumulated_input(bool p_enable);
bool is_using_accumulated_input();
+ void set_enable_ios_virtual_control(bool p_enable);
+ bool is_enable_ios_virtual_control() const;
+ void set_enable_left_thumbstick(bool p_enable);
+ bool is_enable_left_thumbstick() const;
+ void set_enable_right_thumbstick(bool p_enable);
+ bool is_enable_right_thumbstick() const;
+ void set_enable_button_a(bool p_enable);
+ bool is_enable_button_a() const;
+ void set_enable_button_b(bool p_enable);
+ bool is_enable_button_b() const;
+ void set_enable_button_x(bool p_enable);
+ bool is_enable_button_x() const;
+ void set_enable_button_y(bool p_enable);
+ bool is_enable_button_y() const;
+
void release_pressed_events();
void set_event_dispatch_function(EventDispatchFunc p_function);
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index 6fe5b7a8022..b194d70a007 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -425,6 +425,20 @@
If [code]true[/code], sends touch input events when clicking or dragging the mouse. See also [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse].
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Controls the mouse mode. See [enum MouseMode] for more information.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 7b6d8d0cd36..306c9ab57f4 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1405,6 +1405,20 @@
If [code]false[/code], no input will be lost.
[b]Note:[/b] You should in nearly all cases prefer the [code]false[/code] setting. The legacy behavior is to enable supporting old projects that rely on the old logic, without changes to script.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Specifies the tablet driver to use. If left empty, the default driver will be used.
[b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
diff --git a/main/main.cpp b/main/main.cpp
index 9014bfad294..25b87837ce4 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -3191,6 +3191,13 @@ Error Main::setup2(bool p_show_boot_logo) {
}
id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true)));
+ id->set_enable_ios_virtual_control(bool(GLOBAL_DEF_BASIC("input_devices/controllers/ios/enable_ios_virtual_control", false)));
+ id->set_enable_left_thumbstick(bool(GLOBAL_DEF_BASIC("input_devices/controllers/ios/enable_left_thumbstick", true)));
+ id->set_enable_right_thumbstick(bool(GLOBAL_DEF_BASIC("input_devices/controllers/ios/enable_right_thumbstick", true)));
+ id->set_enable_button_a(bool(GLOBAL_DEF_BASIC("input_devices/controllers/ios/enable_button_a", true)));
+ id->set_enable_button_b(bool(GLOBAL_DEF_BASIC("input_devices/controllers/ios/enable_button_b", true)));
+ id->set_enable_button_x(bool(GLOBAL_DEF_BASIC("input_devices/controllers/ios/enable_button_x", true)));
+ id->set_enable_button_y(bool(GLOBAL_DEF_BASIC("input_devices/controllers/ios/enable_button_y", true)));
}
OS::get_singleton()->benchmark_end_measure("Startup", "Setup Window and Boot");
diff --git a/platform/ios/SCsub b/platform/ios/SCsub
index 959a657aacc..936af9d23df 100644
--- a/platform/ios/SCsub
+++ b/platform/ios/SCsub
@@ -78,6 +78,7 @@ ios_lib = [
"keyboard_input_view.mm",
"key_mapping_ios.mm",
"ios_terminal_logger.mm",
+ "virtual_controller.mm",
]
env_ios = env.Clone()
diff --git a/platform/ios/joypad_ios.mm b/platform/ios/joypad_ios.mm
index 38d3ce728aa..7f7d2c0cc42 100644
--- a/platform/ios/joypad_ios.mm
+++ b/platform/ios/joypad_ios.mm
@@ -184,6 +184,8 @@ void JoypadIOS::start_processing() {
} else {
[self addiOSJoypad:controller];
}
+
+ OS_IOS::get_singleton()->controller_connected();
}
- (void)controllerWasDisconnected:(NSNotification *)notification {
@@ -203,6 +205,8 @@ void JoypadIOS::start_processing() {
// and remove it from our dictionary
[self.connectedJoypads removeObjectForKey:key];
}
+
+ OS_IOS::get_singleton()->controller_disconnected();
}
- (GCControllerPlayerIndex)getFreePlayerIndex {
diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h
index b7c5a730656..65993420d76 100644
--- a/platform/ios/os_ios.h
+++ b/platform/ios/os_ios.h
@@ -35,6 +35,7 @@
#import "ios.h"
#import "joypad_ios.h"
+#import "virtual_controller.h"
#import "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
@@ -62,6 +63,8 @@ private:
MainLoop *main_loop = nullptr;
+ VirtualController *virtual_controller = nullptr;
+
virtual void initialize_core() override;
virtual void initialize() override;
@@ -132,6 +135,10 @@ public:
void on_enter_background();
void on_exit_background();
+
+ void controller_connected();
+
+ void controller_disconnected();
};
#endif // IOS_ENABLED
diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm
index 35b87ea6470..f4516db6575 100644
--- a/platform/ios/os_ios.mm
+++ b/platform/ios/os_ios.mm
@@ -135,9 +135,16 @@ void OS_IOS::initialize_modules() {
Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
joypad_ios = memnew(JoypadIOS);
+
+ virtual_controller = memnew(VirtualController);
}
void OS_IOS::deinitialize_modules() {
+ if (virtual_controller) {
+ virtual_controller->disconnect();
+ memdelete(virtual_controller);
+ }
+
if (joypad_ios) {
memdelete(joypad_ios);
}
@@ -184,6 +191,11 @@ void OS_IOS::start() {
if (joypad_ios) {
joypad_ios->start_processing();
}
+
+ if (virtual_controller) {
+ virtual_controller->initialize();
+ virtual_controller->connect();
+ }
}
void OS_IOS::finalize() {
@@ -651,4 +663,16 @@ void OS_IOS::on_exit_background() {
}
}
+void OS_IOS::controller_connected() {
+ if (virtual_controller) {
+ virtual_controller->controller_connected();
+ }
+}
+
+void OS_IOS::controller_disconnected() {
+ if (virtual_controller) {
+ virtual_controller->controller_disconnected();
+ }
+}
+
#endif // IOS_ENABLED
diff --git a/platform/ios/virtual_controller.h b/platform/ios/virtual_controller.h
new file mode 100644
index 00000000000..e77e3e58bdb
--- /dev/null
+++ b/platform/ios/virtual_controller.h
@@ -0,0 +1,49 @@
+/**************************************************************************/
+/* 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. */
+/**************************************************************************/
+
+#import
+
+class VirtualController {
+private:
+#if defined(__IPHONE_15_0)
+ API_AVAILABLE(ios(15.0))
+ GCVirtualController *gcv_controller;
+#endif
+
+public:
+ VirtualController();
+ ~VirtualController();
+
+ void initialize();
+ void connect();
+ void disconnect();
+ void controller_connected();
+ void controller_disconnected();
+};
diff --git a/platform/ios/virtual_controller.mm b/platform/ios/virtual_controller.mm
new file mode 100644
index 00000000000..af35266d15b
--- /dev/null
+++ b/platform/ios/virtual_controller.mm
@@ -0,0 +1,115 @@
+/**************************************************************************/
+/* virtual_controller.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.h"
+#include "core/input/input.h"
+
+VirtualController::VirtualController() {
+}
+
+VirtualController::~VirtualController() {
+ if (@available(iOS 15.0, *)) {
+ gcv_controller = nullptr;
+ }
+}
+
+void VirtualController::initialize() {
+ if (@available(iOS 15.0, *)) {
+ if (!gcv_controller) {
+ GCVirtualControllerConfiguration *config = [[GCVirtualControllerConfiguration alloc] init];
+
+ NSMutableSet *elements = [[NSMutableSet alloc] init];
+
+ Input *input = Input::get_singleton();
+
+ if (input->is_enable_left_thumbstick()) {
+ [elements addObject:GCInputLeftThumbstick];
+ }
+ if (input->is_enable_right_thumbstick()) {
+ [elements addObject:GCInputRightThumbstick];
+ }
+ if (input->is_enable_button_a()) {
+ [elements addObject:GCInputButtonA];
+ }
+ if (input->is_enable_button_b()) {
+ [elements addObject:GCInputButtonB];
+ }
+ if (input->is_enable_button_x()) {
+ [elements addObject:GCInputButtonX];
+ }
+ if (input->is_enable_button_y()) {
+ [elements addObject:GCInputButtonY];
+ }
+
+ config.elements = elements;
+ gcv_controller = [[GCVirtualController alloc] initWithConfiguration:config];
+ }
+ }
+}
+
+void VirtualController::connect() {
+ if (!Input::get_singleton()->is_enable_ios_virtual_control()) {
+ return;
+ }
+ if (@available(iOS 15.0, *)) {
+ if (GCController.controllers.count == 0 && gcv_controller != nil) {
+ [gcv_controller connectWithReplyHandler:nil];
+ }
+ }
+}
+
+void VirtualController::disconnect() {
+ if (@available(iOS 15.0, *)) {
+ if (gcv_controller) {
+ [gcv_controller disconnect];
+ }
+ }
+}
+
+void VirtualController::controller_connected() {
+ if (@available(iOS 15.0, *)) {
+ if (gcv_controller != nil) {
+ BOOL hasPhysicalController = NO;
+ for (GCController *controller in GCController.controllers) {
+ if (controller != gcv_controller.controller) {
+ hasPhysicalController = YES;
+ break;
+ }
+ }
+ if (hasPhysicalController) {
+ disconnect();
+ }
+ }
+ }
+}
+
+void VirtualController::controller_disconnected() {
+ connect();
+}