Add ios virtual controller support.

This commit is contained in:
Roland Vigh 2024-09-27 09:22:26 +02:00
parent 506d6e427a
commit 6e765250e3
11 changed files with 335 additions and 0 deletions

View File

@ -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() {

View File

@ -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);

View File

@ -425,6 +425,20 @@
<member name="emulate_touch_from_mouse" type="bool" setter="set_emulate_touch_from_mouse" getter="is_emulating_touch_from_mouse">
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].
</member>
<member name="enable_button_a" type="bool" setter="set_enable_button_a" getter="is_enable_button_a">
</member>
<member name="enable_button_b" type="bool" setter="set_enable_button_b" getter="is_enable_button_b">
</member>
<member name="enable_button_x" type="bool" setter="set_enable_button_x" getter="is_enable_button_x">
</member>
<member name="enable_button_y" type="bool" setter="set_enable_button_y" getter="is_enable_button_y">
</member>
<member name="enable_ios_virtual_control" type="bool" setter="set_enable_ios_virtual_control" getter="is_enable_ios_virtual_control">
</member>
<member name="enable_left_thumbstick" type="bool" setter="set_enable_left_thumbstick" getter="is_enable_left_thumbstick">
</member>
<member name="enable_right_thumbstick" type="bool" setter="set_enable_right_thumbstick" getter="is_enable_right_thumbstick">
</member>
<member name="mouse_mode" type="int" setter="set_mouse_mode" getter="get_mouse_mode" enum="Input.MouseMode">
Controls the mouse mode. See [enum MouseMode] for more information.
</member>

View File

@ -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.
</member>
<member name="input_devices/controllers/ios/enable_button_a" type="bool" setter="" getter="" default="true">
</member>
<member name="input_devices/controllers/ios/enable_button_b" type="bool" setter="" getter="" default="true">
</member>
<member name="input_devices/controllers/ios/enable_button_x" type="bool" setter="" getter="" default="true">
</member>
<member name="input_devices/controllers/ios/enable_button_y" type="bool" setter="" getter="" default="true">
</member>
<member name="input_devices/controllers/ios/enable_ios_virtual_control" type="bool" setter="" getter="" default="false">
</member>
<member name="input_devices/controllers/ios/enable_left_thumbstick" type="bool" setter="" getter="" default="true">
</member>
<member name="input_devices/controllers/ios/enable_right_thumbstick" type="bool" setter="" getter="" default="true">
</member>
<member name="input_devices/pen_tablet/driver" type="String" setter="" getter="">
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].

View File

@ -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");

View File

@ -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()

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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 <GameController/GameController.h>
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();
};

View File

@ -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();
}