From 6e765250e3eb83e69bff7b41b1483f4ed5398db0 Mon Sep 17 00:00:00 2001 From: Roland Vigh Date: Fri, 27 Sep 2024 09:22:26 +0200 Subject: [PATCH] Add ios virtual controller support. --- core/input/input.cpp | 77 +++++++++++++++++++ core/input/input.h | 23 ++++++ doc/classes/Input.xml | 14 ++++ doc/classes/ProjectSettings.xml | 14 ++++ main/main.cpp | 7 ++ platform/ios/SCsub | 1 + platform/ios/joypad_ios.mm | 4 + platform/ios/os_ios.h | 7 ++ platform/ios/os_ios.mm | 24 ++++++ platform/ios/virtual_controller.h | 49 ++++++++++++ platform/ios/virtual_controller.mm | 115 +++++++++++++++++++++++++++++ 11 files changed, 335 insertions(+) create mode 100644 platform/ios/virtual_controller.h create mode 100644 platform/ios/virtual_controller.mm 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(); +}