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; +}