diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 0e078c32c978..deffcc3f50cd 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1852,6 +1852,7 @@ 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/enable_controller", false); // 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/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 14e3133aee36..6651884df5f4 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1716,6 +1716,9 @@ If [code]true[/code], the magnetometer sensor is enabled and [method Input.get_magnetometer] returns valid data. + + If [code]true[/code], the virtual controller is enabled and will be created in the scene. + 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/VirtualJoystick.xml b/doc/classes/VirtualJoystick.xml index 4538c0acf19e..4c8e6836b190 100644 --- a/doc/classes/VirtualJoystick.xml +++ b/doc/classes/VirtualJoystick.xml @@ -62,6 +62,12 @@ Emitted when the tip moved outside the deadzone and the joystick is released. The [param input_vector] contains the last input direction and strength before release. Its length is between [code]0.0[/code] and [code]1.0[/code]. + + + + Emitted when the input vector changes. + + Emitted when the joystick is pressed. diff --git a/scene/gui/virtual_controller.cpp b/scene/gui/virtual_controller.cpp new file mode 100644 index 000000000000..5c28dfa4d131 --- /dev/null +++ b/scene/gui/virtual_controller.cpp @@ -0,0 +1,587 @@ +/**************************************************************************/ +/* 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" + +#include "core/config/engine.h" +#include "core/input/input.h" +#include "core/object/callable_mp.h" +#include "scene/gui/button.h" +#include "scene/gui/virtual_joystick.h" +#include "scene/theme/theme_db.h" + +void VirtualController::_setup_controls() { + float min_size = MIN(get_size().x, get_size().y); + int margin = min_size * 0.05f; + Size2 joystick_size(min_size * 0.25f, min_size * 0.25f); + + left_joystick = memnew(VirtualJoystick); + left_joystick->set_joystick_size(joystick_size.x); + left_joystick->set_h_grow_direction(Control::GROW_DIRECTION_END); + left_joystick->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + left_joystick->set_custom_minimum_size(joystick_size); + left_joystick->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + left_joystick->set_offset(Side::SIDE_BOTTOM, -margin); + left_joystick->set_offset(Side::SIDE_LEFT, margin + min_size * 0.3f); + add_child(left_joystick, true, Node::INTERNAL_MODE_FRONT); + _update_left_joystick_style(left_joystick); + + right_joystick = memnew(VirtualJoystick); + right_joystick->set_joystick_size(joystick_size.x); + right_joystick->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_joystick->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_joystick->set_custom_minimum_size(joystick_size); + right_joystick->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + right_joystick->set_offset(Side::SIDE_BOTTOM, -margin); + right_joystick->set_offset(Side::SIDE_RIGHT, -margin + min_size * -0.3f); + add_child(right_joystick, true, Node::INTERNAL_MODE_FRONT); + _update_right_joystick_style(right_joystick); + + Size2 button_size(min_size * 0.125f, min_size * 0.125f); + + left_joystick_button = memnew(Button); + left_joystick_button->set_text("L3"); + left_joystick_button->set_focus_mode(FOCUS_NONE); + left_joystick_button->set_h_grow_direction(Control::GROW_DIRECTION_END); + left_joystick_button->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + left_joystick_button->set_custom_minimum_size(button_size); + left_joystick_button->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + left_joystick_button->set_offset(SIDE_BOTTOM, -margin); + left_joystick_button->set_offset(SIDE_LEFT, margin); + add_child(left_joystick_button, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(left_joystick_button); + + right_joystick_button = memnew(Button); + right_joystick_button->set_text("R3"); + right_joystick_button->set_focus_mode(FOCUS_NONE); + right_joystick_button->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_joystick_button->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_joystick_button->set_custom_minimum_size(button_size); + right_joystick_button->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + right_joystick_button->set_offset(SIDE_BOTTOM, -margin); + right_joystick_button->set_offset(SIDE_RIGHT, -margin); + add_child(right_joystick_button, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(right_joystick_button); + + float start_bottom_offset = min_size * -0.25f; + + dpad_down = memnew(Button); + dpad_down->set_text("D"); + dpad_down->set_focus_mode(FOCUS_NONE); + dpad_down->set_h_grow_direction(Control::GROW_DIRECTION_END); + dpad_down->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + dpad_down->set_custom_minimum_size(button_size); + dpad_down->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + dpad_down->set_offset(SIDE_BOTTOM, start_bottom_offset); + dpad_down->set_offset(SIDE_LEFT, margin + button_size.x); + add_child(dpad_down, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(dpad_down); + + dpad_up = memnew(Button); + dpad_up->set_text("U"); + dpad_up->set_focus_mode(FOCUS_NONE); + dpad_up->set_h_grow_direction(Control::GROW_DIRECTION_END); + dpad_up->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + dpad_up->set_custom_minimum_size(button_size); + dpad_up->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + dpad_up->set_offset(SIDE_BOTTOM, start_bottom_offset - 2 * button_size.y); + dpad_up->set_offset(SIDE_LEFT, margin + button_size.x); + add_child(dpad_up, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(dpad_up); + + dpad_left = memnew(Button); + dpad_left->set_text("L"); + dpad_left->set_focus_mode(FOCUS_NONE); + dpad_left->set_h_grow_direction(Control::GROW_DIRECTION_END); + dpad_left->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + dpad_left->set_custom_minimum_size(button_size); + dpad_left->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + dpad_left->set_offset(SIDE_BOTTOM, start_bottom_offset - button_size.y); + dpad_left->set_offset(SIDE_LEFT, margin); + add_child(dpad_left, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(dpad_left); + + dpad_right = memnew(Button); + dpad_right->set_text("R"); + dpad_right->set_focus_mode(FOCUS_NONE); + dpad_right->set_h_grow_direction(Control::GROW_DIRECTION_END); + dpad_right->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + dpad_right->set_custom_minimum_size(button_size); + dpad_right->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + dpad_right->set_offset(SIDE_BOTTOM, start_bottom_offset - button_size.y); + dpad_right->set_offset(SIDE_LEFT, margin + 2 * button_size.x); + add_child(dpad_right, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(dpad_right); + + button_a = memnew(Button); + button_a->set_text("A"); + button_a->set_focus_mode(FOCUS_NONE); + button_a->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_a->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_a->set_custom_minimum_size(button_size); + button_a->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + button_a->set_offset(SIDE_BOTTOM, start_bottom_offset); + button_a->set_offset(SIDE_RIGHT, -margin - button_size.x); + add_child(button_a, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(button_a); + + button_y = memnew(Button); + button_y->set_text("Y"); + button_y->set_focus_mode(FOCUS_NONE); + button_y->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_y->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_y->set_custom_minimum_size(button_size); + button_y->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + button_y->set_offset(SIDE_BOTTOM, start_bottom_offset - 2 * button_size.y); + button_y->set_offset(SIDE_RIGHT, -margin - button_size.x); + add_child(button_y, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(button_y); + + button_b = memnew(Button); + button_b->set_text("B"); + button_b->set_focus_mode(FOCUS_NONE); + button_b->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_b->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_b->set_custom_minimum_size(button_size); + button_b->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + button_b->set_offset(SIDE_BOTTOM, start_bottom_offset - button_size.y); + button_b->set_offset(SIDE_RIGHT, -margin); + add_child(button_b, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(button_b); + + button_x = memnew(Button); + button_x->set_text("X"); + button_x->set_focus_mode(FOCUS_NONE); + button_x->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_x->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + button_x->set_custom_minimum_size(button_size); + button_x->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + button_x->set_offset(SIDE_BOTTOM, start_bottom_offset - button_size.y); + button_x->set_offset(SIDE_RIGHT, -margin - 2 * button_size.x); + add_child(button_x, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(button_x); + + left_trigger = memnew(Button); + left_trigger->set_text("LT"); + left_trigger->set_focus_mode(FOCUS_NONE); + left_trigger->set_h_grow_direction(Control::GROW_DIRECTION_END); + left_trigger->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + left_trigger->set_custom_minimum_size(button_size); + left_trigger->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + left_trigger->set_offset(SIDE_BOTTOM, start_bottom_offset - 3 * button_size.y - margin); + left_trigger->set_offset(SIDE_LEFT, margin); + add_child(left_trigger, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(left_trigger); + + left_shoulder = memnew(Button); + left_shoulder->set_text("LB"); + left_shoulder->set_focus_mode(FOCUS_NONE); + left_shoulder->set_h_grow_direction(Control::GROW_DIRECTION_END); + left_shoulder->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + left_shoulder->set_custom_minimum_size(button_size); + left_shoulder->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); + left_shoulder->set_offset(SIDE_BOTTOM, start_bottom_offset - 3 * button_size.y - margin); + left_shoulder->set_offset(SIDE_LEFT, margin + 2 * button_size.x); + add_child(left_shoulder, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(left_shoulder); + + right_trigger = memnew(Button); + right_trigger->set_text("RT"); + right_trigger->set_focus_mode(FOCUS_NONE); + right_trigger->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_trigger->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_trigger->set_custom_minimum_size(button_size); + right_trigger->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + right_trigger->set_offset(SIDE_BOTTOM, start_bottom_offset - 3 * button_size.y - margin); + right_trigger->set_offset(SIDE_RIGHT, -margin); + add_child(right_trigger, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(right_trigger); + + right_shoulder = memnew(Button); + right_shoulder->set_text("RB"); + right_shoulder->set_focus_mode(FOCUS_NONE); + right_shoulder->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_shoulder->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); + right_shoulder->set_custom_minimum_size(button_size); + right_shoulder->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_RIGHT); + right_shoulder->set_offset(SIDE_BOTTOM, start_bottom_offset - 3 * button_size.y - margin); + right_shoulder->set_offset(SIDE_RIGHT, -margin - 2 * button_size.x); + add_child(right_shoulder, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(right_shoulder); + + guide_button = memnew(Button); + guide_button->set_text("G"); + guide_button->set_focus_mode(FOCUS_NONE); + guide_button->set_h_grow_direction(Control::GROW_DIRECTION_BOTH); + guide_button->set_v_grow_direction(Control::GROW_DIRECTION_END); + guide_button->set_custom_minimum_size(button_size); + guide_button->set_anchors_and_offsets_preset(Control::PRESET_CENTER_TOP); + guide_button->set_offset(SIDE_TOP, margin); + add_child(guide_button, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(guide_button); + + back_button = memnew(Button); + back_button->set_text("<-"); + back_button->set_focus_mode(FOCUS_NONE); + back_button->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN); + back_button->set_v_grow_direction(Control::GROW_DIRECTION_END); + back_button->set_custom_minimum_size(button_size); + back_button->set_anchors_and_offsets_preset(Control::PRESET_CENTER_TOP); + back_button->set_offset(SIDE_TOP, margin); + back_button->set_offset(SIDE_RIGHT, -button_size.x - margin); + add_child(back_button, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(back_button); + + start_button = memnew(Button); + start_button->set_text("->"); + start_button->set_focus_mode(FOCUS_NONE); + start_button->set_h_grow_direction(Control::GROW_DIRECTION_END); + start_button->set_v_grow_direction(Control::GROW_DIRECTION_END); + start_button->set_custom_minimum_size(button_size); + start_button->set_anchors_and_offsets_preset(Control::PRESET_CENTER_TOP); + start_button->set_offset(SIDE_TOP, margin); + start_button->set_offset(SIDE_LEFT, button_size.x + margin); + add_child(start_button, true, Node::INTERNAL_MODE_FRONT); + _update_button_style(start_button); + + left_joystick->connect("input_changed", callable_mp(this, &VirtualController::_on_left_joystick_motion)); + left_joystick_button->connect("button_down", callable_mp(this, &VirtualController::_on_left_joystick_pressed)); + left_joystick_button->connect("button_up", callable_mp(this, &VirtualController::_on_left_joystick_released)); + right_joystick->connect("input_changed", callable_mp(this, &VirtualController::_on_right_joystick_motion)); + right_joystick_button->connect("button_down", callable_mp(this, &VirtualController::_on_right_joystick_pressed)); + right_joystick_button->connect("button_up", callable_mp(this, &VirtualController::_on_right_joystick_released)); + dpad_up->connect("button_down", callable_mp(this, &VirtualController::_on_dpad_up_pressed)); + dpad_up->connect("button_up", callable_mp(this, &VirtualController::_on_dpad_up_released)); + dpad_down->connect("button_down", callable_mp(this, &VirtualController::_on_dpad_down_pressed)); + dpad_down->connect("button_up", callable_mp(this, &VirtualController::_on_dpad_down_released)); + dpad_left->connect("button_down", callable_mp(this, &VirtualController::_on_dpad_left_pressed)); + dpad_left->connect("button_up", callable_mp(this, &VirtualController::_on_dpad_left_released)); + dpad_right->connect("button_down", callable_mp(this, &VirtualController::_on_dpad_right_pressed)); + dpad_right->connect("button_up", callable_mp(this, &VirtualController::_on_dpad_right_released)); + button_a->connect("button_down", callable_mp(this, &VirtualController::_on_button_a_pressed)); + button_a->connect("button_up", callable_mp(this, &VirtualController::_on_button_a_released)); + button_b->connect("button_down", callable_mp(this, &VirtualController::_on_button_b_pressed)); + button_b->connect("button_up", callable_mp(this, &VirtualController::_on_button_b_released)); + button_x->connect("button_down", callable_mp(this, &VirtualController::_on_button_x_pressed)); + button_x->connect("button_up", callable_mp(this, &VirtualController::_on_button_x_released)); + button_y->connect("button_down", callable_mp(this, &VirtualController::_on_button_y_pressed)); + button_y->connect("button_up", callable_mp(this, &VirtualController::_on_button_y_released)); + left_shoulder->connect("button_down", callable_mp(this, &VirtualController::_on_left_shoulder_pressed)); + left_shoulder->connect("button_up", callable_mp(this, &VirtualController::_on_left_shoulder_released)); + right_shoulder->connect("button_down", callable_mp(this, &VirtualController::_on_right_shoulder_pressed)); + right_shoulder->connect("button_up", callable_mp(this, &VirtualController::_on_right_shoulder_released)); + left_trigger->connect("button_down", callable_mp(this, &VirtualController::_on_left_trigger_pressed)); + left_trigger->connect("button_up", callable_mp(this, &VirtualController::_on_left_trigger_released)); + right_trigger->connect("button_down", callable_mp(this, &VirtualController::_on_right_trigger_pressed)); + right_trigger->connect("button_up", callable_mp(this, &VirtualController::_on_right_trigger_released)); + start_button->connect("button_down", callable_mp(this, &VirtualController::_on_start_button_pressed)); + start_button->connect("button_up", callable_mp(this, &VirtualController::_on_start_button_released)); + back_button->connect("button_down", callable_mp(this, &VirtualController::_on_back_button_pressed)); + back_button->connect("button_up", callable_mp(this, &VirtualController::_on_back_button_released)); + guide_button->connect("button_down", callable_mp(this, &VirtualController::_on_guide_button_pressed)); + guide_button->connect("button_up", callable_mp(this, &VirtualController::_on_guide_button_released)); +} + +void VirtualController::_bind_methods() { + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, button_normal); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, button_pressed); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, left_normal_joystick); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, left_normal_tip); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, left_pressed_joystick); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, left_pressed_tip); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, right_normal_joystick); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, right_normal_tip); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, right_pressed_joystick); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, VirtualController, right_pressed_tip); +} + +void VirtualController::_notification(int p_notification) { + ERR_MAIN_THREAD_GUARD; + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + device_id = Input::get_singleton()->get_unused_joy_id(); + Dictionary info; + info["mapping_handled"] = true; + Input::get_singleton()->joy_connection_changed(device_id, true, "Virtual Controller", "virtual_controller", info); + } break; + + case NOTIFICATION_EXIT_TREE: { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + Input::get_singleton()->joy_connection_changed(device_id, false, "", "", Dictionary()); + device_id = -1; + } break; + + case NOTIFICATION_PROCESS: { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + //set_visible(Input::get_singleton()->is_virtual_controller_enabled()); + } break; + + case NOTIFICATION_THEME_CHANGED: { + _update_left_joystick_style(left_joystick); + _update_right_joystick_style(right_joystick); + _update_button_style(left_joystick_button); + _update_button_style(right_joystick_button); + _update_button_style(dpad_up); + _update_button_style(dpad_down); + _update_button_style(dpad_left); + _update_button_style(dpad_right); + _update_button_style(button_a); + _update_button_style(button_b); + _update_button_style(button_x); + _update_button_style(button_y); + _update_button_style(left_shoulder); + _update_button_style(right_shoulder); + _update_button_style(left_trigger); + _update_button_style(right_trigger); + _update_button_style(start_button); + _update_button_style(back_button); + _update_button_style(guide_button); + } break; + } +} + +void VirtualController::_create_button_event(JoyButton p_button, bool p_pressed) { + Ref button_event; + button_event.instantiate(); + button_event->set_button_index(p_button); + button_event->set_pressed(p_pressed); + button_event->set_device(device_id); + + Input::get_singleton()->parse_input_event(button_event); +} + +void VirtualController::_create_motion_event(JoyAxis p_axis, float p_value) { + Ref motion_event; + motion_event.instantiate(); + motion_event->set_axis(p_axis); + motion_event->set_axis_value(p_value); + motion_event->set_device(device_id); + + Input::get_singleton()->parse_input_event(motion_event); +} + +void VirtualController::_update_left_joystick_style(VirtualJoystick *p_joystick) { + if (p_joystick == nullptr) { + return; + } + + p_joystick->add_theme_style_override("normal_joystick", theme_cache.left_normal_joystick); + p_joystick->add_theme_style_override("normal_tip", theme_cache.left_normal_tip); + p_joystick->add_theme_style_override("pressed_joystick", theme_cache.left_pressed_joystick); + p_joystick->add_theme_style_override("pressed_tip", theme_cache.left_pressed_tip); +} + +void VirtualController::_update_right_joystick_style(VirtualJoystick *p_joystick) { + if (p_joystick == nullptr) { + return; + } + + p_joystick->add_theme_style_override("normal_joystick", theme_cache.right_normal_joystick); + p_joystick->add_theme_style_override("normal_tip", theme_cache.right_normal_tip); + p_joystick->add_theme_style_override("pressed_joystick", theme_cache.right_pressed_joystick); + p_joystick->add_theme_style_override("pressed_tip", theme_cache.right_pressed_tip); +} + +void VirtualController::_update_button_style(Button *p_button) { + if (p_button == nullptr) { + return; + } + + p_button->add_theme_style_override(CoreStringName(normal), theme_cache.button_normal); + p_button->add_theme_style_override(SceneStringName(hover), theme_cache.button_normal); + p_button->add_theme_style_override(SceneStringName(pressed), theme_cache.button_pressed); +} + +void VirtualController::_on_left_joystick_motion(Vector2 p_value) { + _create_motion_event(JoyAxis::LEFT_X, p_value.x); + _create_motion_event(JoyAxis::LEFT_Y, p_value.y); +} + +void VirtualController::_on_left_joystick_pressed() { + _create_button_event(JoyButton::LEFT_STICK, true); +} + +void VirtualController::_on_left_joystick_released() { + _create_button_event(JoyButton::LEFT_STICK, false); +} + +void VirtualController::_on_right_joystick_motion(Vector2 p_value) { + _create_motion_event(JoyAxis::RIGHT_X, p_value.x); + _create_motion_event(JoyAxis::RIGHT_Y, p_value.y); +} + +void VirtualController::_on_right_joystick_pressed() { + _create_button_event(JoyButton::RIGHT_STICK, true); +} + +void VirtualController::_on_right_joystick_released() { + _create_button_event(JoyButton::RIGHT_STICK, false); +} + +void VirtualController::_on_dpad_up_pressed() { + _create_button_event(JoyButton::DPAD_UP, true); +} + +void VirtualController::_on_dpad_up_released() { + _create_button_event(JoyButton::DPAD_UP, false); +} + +void VirtualController::_on_dpad_down_pressed() { + _create_button_event(JoyButton::DPAD_DOWN, true); +} + +void VirtualController::_on_dpad_down_released() { + _create_button_event(JoyButton::DPAD_DOWN, false); +} + +void VirtualController::_on_dpad_left_pressed() { + _create_button_event(JoyButton::DPAD_LEFT, true); +} + +void VirtualController::_on_dpad_left_released() { + _create_button_event(JoyButton::DPAD_LEFT, false); +} + +void VirtualController::_on_dpad_right_pressed() { + _create_button_event(JoyButton::DPAD_RIGHT, true); +} + +void VirtualController::_on_dpad_right_released() { + _create_button_event(JoyButton::DPAD_RIGHT, false); +} + +void VirtualController::_on_button_a_pressed() { + _create_button_event(JoyButton::A, true); +} + +void VirtualController::_on_button_a_released() { + _create_button_event(JoyButton::A, false); +} + +void VirtualController::_on_button_b_pressed() { + _create_button_event(JoyButton::B, true); +} + +void VirtualController::_on_button_b_released() { + _create_button_event(JoyButton::B, false); +} + +void VirtualController::_on_button_x_pressed() { + _create_button_event(JoyButton::X, true); +} + +void VirtualController::_on_button_x_released() { + _create_button_event(JoyButton::X, false); +} + +void VirtualController::_on_button_y_pressed() { + _create_button_event(JoyButton::Y, true); +} + +void VirtualController::_on_button_y_released() { + _create_button_event(JoyButton::Y, false); +} + +void VirtualController::_on_left_shoulder_pressed() { + _create_button_event(JoyButton::LEFT_SHOULDER, true); +} + +void VirtualController::_on_left_shoulder_released() { + _create_button_event(JoyButton::LEFT_SHOULDER, false); +} + +void VirtualController::_on_right_shoulder_pressed() { + _create_button_event(JoyButton::RIGHT_SHOULDER, true); +} + +void VirtualController::_on_right_shoulder_released() { + _create_button_event(JoyButton::RIGHT_SHOULDER, false); +} + +void VirtualController::_on_left_trigger_pressed() { + _create_motion_event(JoyAxis::TRIGGER_LEFT, 1.0f); +} + +void VirtualController::_on_left_trigger_released() { + _create_motion_event(JoyAxis::TRIGGER_LEFT, 0.0f); +} + +void VirtualController::_on_right_trigger_pressed() { + _create_motion_event(JoyAxis::TRIGGER_RIGHT, 1.0f); +} + +void VirtualController::_on_right_trigger_released() { + _create_motion_event(JoyAxis::TRIGGER_RIGHT, 0.0f); +} + +void VirtualController::_on_start_button_pressed() { + _create_button_event(JoyButton::START, true); +} + +void VirtualController::_on_start_button_released() { + _create_button_event(JoyButton::START, false); +} + +void VirtualController::_on_back_button_pressed() { + _create_button_event(JoyButton::BACK, true); +} + +void VirtualController::_on_back_button_released() { + _create_button_event(JoyButton::BACK, false); +} + +void VirtualController::_on_guide_button_pressed() { + _create_button_event(JoyButton::GUIDE, true); +} + +void VirtualController::_on_guide_button_released() { + _create_button_event(JoyButton::GUIDE, false); +} + +VirtualController::VirtualController() { + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + set_process(true); + set_focus_mode(FOCUS_NONE); + set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + + callable_mp(this, &VirtualController::_setup_controls).call_deferred(); +} diff --git a/scene/gui/virtual_controller.h b/scene/gui/virtual_controller.h new file mode 100644 index 000000000000..4dc2bb07795f --- /dev/null +++ b/scene/gui/virtual_controller.h @@ -0,0 +1,128 @@ +/**************************************************************************/ +/* 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 "scene/gui/control.h" + +class Button; +class VirtualJoystick; + +class VirtualController : public Control { + GDCLASS(VirtualController, Control); + +private: + int device_id = -1; + + struct ThemeCache { + Ref button_normal; + Ref button_pressed; + Ref left_normal_joystick; + Ref left_normal_tip; + Ref left_pressed_joystick; + Ref left_pressed_tip; + Ref right_normal_joystick; + Ref right_normal_tip; + Ref right_pressed_joystick; + Ref right_pressed_tip; + } theme_cache; + + VirtualJoystick *left_joystick = nullptr; + VirtualJoystick *right_joystick = nullptr; + Button *left_joystick_button = nullptr; + Button *right_joystick_button = nullptr; + Button *dpad_up = nullptr; + Button *dpad_down = nullptr; + Button *dpad_left = nullptr; + Button *dpad_right = nullptr; + Button *button_a = nullptr; + Button *button_b = nullptr; + Button *button_x = nullptr; + Button *button_y = nullptr; + Button *left_shoulder = nullptr; + Button *right_shoulder = nullptr; + Button *left_trigger = nullptr; + Button *right_trigger = nullptr; + Button *start_button = nullptr; + Button *back_button = nullptr; + Button *guide_button = nullptr; + + void _setup_controls(); + + void _create_button_event(JoyButton p_button, bool p_pressed); + void _create_motion_event(JoyAxis p_axis, float p_value); + void _update_left_joystick_style(VirtualJoystick *p_joystick); + void _update_right_joystick_style(VirtualJoystick *p_joystick); + void _update_button_style(Button *p_button); + + void _on_left_joystick_motion(Vector2 p_value); + void _on_right_joystick_motion(Vector2 p_value); + void _on_left_joystick_pressed(); + void _on_left_joystick_released(); + void _on_right_joystick_pressed(); + void _on_right_joystick_released(); + void _on_dpad_up_pressed(); + void _on_dpad_up_released(); + void _on_dpad_down_pressed(); + void _on_dpad_down_released(); + void _on_dpad_left_pressed(); + void _on_dpad_left_released(); + void _on_dpad_right_pressed(); + void _on_dpad_right_released(); + void _on_button_a_pressed(); + void _on_button_a_released(); + void _on_button_b_pressed(); + void _on_button_b_released(); + void _on_button_x_pressed(); + void _on_button_x_released(); + void _on_button_y_pressed(); + void _on_button_y_released(); + void _on_left_shoulder_pressed(); + void _on_left_shoulder_released(); + void _on_right_shoulder_pressed(); + void _on_right_shoulder_released(); + void _on_left_trigger_pressed(); + void _on_left_trigger_released(); + void _on_right_trigger_pressed(); + void _on_right_trigger_released(); + void _on_start_button_pressed(); + void _on_start_button_released(); + void _on_back_button_pressed(); + void _on_back_button_released(); + void _on_guide_button_pressed(); + void _on_guide_button_released(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + VirtualController(); +}; diff --git a/scene/gui/virtual_joystick.cpp b/scene/gui/virtual_joystick.cpp index b05d332cefb7..5399bc072a49 100644 --- a/scene/gui/virtual_joystick.cpp +++ b/scene/gui/virtual_joystick.cpp @@ -56,6 +56,7 @@ void VirtualJoystick::gui_input(const Ref &p_event) { } else if (touch->get_index() == touch_index) { is_pressed = false; emit_signal(SNAME("released"), input_vector); + emit_signal(SNAME("input_changed"), Vector2()); if (!is_flick_canceled && !has_moved) { emit_signal(SNAME("tapped")); @@ -127,6 +128,8 @@ void VirtualJoystick::_update_joystick(const Vector2 &p_pos) { input_vector = Vector2(); } + emit_signal(SNAME("input_changed"), input_vector); + if (!is_flick_canceled && was_pressed && !has_input) { is_flick_canceled = true; emit_signal(SNAME("flick_canceled")); @@ -247,6 +250,7 @@ void VirtualJoystick::_bind_methods() { ADD_SIGNAL(MethodInfo("released", PropertyInfo(Variant::VECTOR2, "input_vector"))); ADD_SIGNAL(MethodInfo("flicked", PropertyInfo(Variant::VECTOR2, "input_vector"))); ADD_SIGNAL(MethodInfo("flick_canceled")); + ADD_SIGNAL(MethodInfo("input_changed", PropertyInfo(Variant::VECTOR2, "input_vector"))); BIND_ENUM_CONSTANT(JOYSTICK_FIXED); BIND_ENUM_CONSTANT(JOYSTICK_DYNAMIC); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index dd92f0f498c9..64a6e6807c9f 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -45,6 +45,8 @@ STATIC_ASSERT_INCOMPLETE_TYPE(class, RenderingServer); #include "scene/animation/tween.h" #include "scene/debugger/scene_debugger.h" #include "scene/gui/control.h" +#include "scene/gui/virtual_controller.h" +#include "scene/main/canvas_layer.h" #include "scene/main/multiplayer_api.h" #include "scene/main/node.h" #include "scene/main/viewport.h" @@ -1887,6 +1889,43 @@ bool SceneTree::is_multiplayer_poll_enabled() const { return multiplayer_poll; } +void SceneTree::set_virtual_controller_visible(bool p_visible) { + if (p_visible) { + if (virtual_controller_canvas_layer == nullptr) { + virtual_controller_canvas_layer = memnew(CanvasLayer); + // Begin name with an underscore to avoid conflict with project nodes. + virtual_controller_canvas_layer->set_name("_VirtualControllerCanvasLayer"); + get_root()->add_child(virtual_controller_canvas_layer, false, Node::INTERNAL_MODE_BACK); + + virtual_controller = memnew(VirtualController); + virtual_controller->set_name("_VirtualController"); + virtual_controller_canvas_layer->add_child(virtual_controller, false, Node::INTERNAL_MODE_BACK); + } else { + ERR_PRINT("Virtual controller already exists."); + } + } else { + if (virtual_controller_canvas_layer != nullptr) { + // Free when closing to avoid reserving memory during the project's run duration. + virtual_controller_canvas_layer->queue_free(); + virtual_controller_canvas_layer = nullptr; + virtual_controller = nullptr; + } + } +} + +bool SceneTree::is_virtual_controller_visible() const { + if (virtual_controller_canvas_layer) { + return virtual_controller_canvas_layer->is_visible(); + } + + // Licenses dialog isn't created yet. Therefore, it's not visible. + return false; +} + +void SceneTree::_project_settings_changed() { + set_virtual_controller_visible(ProjectSettings::get_singleton()->get("input_devices/virtual_controller/enable_controller")); +} + void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_root"), &SceneTree::get_root); ClassDB::bind_method(D_METHOD("has_group", "name"), &SceneTree::has_group); @@ -2178,6 +2217,8 @@ SceneTree::SceneTree() { Viewport::SDFScale sdf_scale = Viewport::SDFScale(int(GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/2d/sdf/scale", PROPERTY_HINT_ENUM, "100%,50%,25%"), 1))); root->set_sdf_scale(sdf_scale); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &SceneTree::_project_settings_changed)); + #ifndef _3D_DISABLED { // Load default fallback environment. // Get possible extensions. diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index b7098290b411..851b7ec0da1d 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -41,6 +41,7 @@ #include class ArrayMesh; +class CanvasLayer; class InputEvent; class Material; class MultiplayerAPI; @@ -48,6 +49,7 @@ class Node; class PackedScene; class Tween; class Viewport; +class VirtualController; class Window; #ifndef _3D_DISABLED @@ -204,6 +206,9 @@ class SceneTree : public MainLoop { ObjectID prev_scene_id; ObjectID pending_new_scene_id; + CanvasLayer *virtual_controller_canvas_layer = nullptr; + VirtualController *virtual_controller = nullptr; + Color debug_collisions_color; Color debug_collision_contact_color; Color debug_paths_color; @@ -283,6 +288,8 @@ class SceneTree : public MainLoop { //used by viewport void _call_input_pause(const StringName &p_group, CallInputType p_call_type, const Ref &p_input, Viewport *p_viewport); + void _project_settings_changed(); + protected: void _notification(int p_notification); static void _bind_methods(); @@ -451,6 +458,9 @@ class SceneTree : public MainLoop { void set_multiplayer_poll_enabled(bool p_enabled); bool is_multiplayer_poll_enabled() const; + void set_virtual_controller_visible(bool p_visible); + bool is_virtual_controller_visible() const; + static void add_idle_callback(IdleCallback p_callback); void set_disable_node_threading(bool p_disable); diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index 3de32c443a7e..37a91d765569 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -681,6 +681,25 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const theme->set_stylebox("pressed_joystick", "VirtualJoystick", style_joystick); theme->set_stylebox("pressed_tip", "VirtualJoystick", style_joystick_tip); + // Virtual Controller + + Ref style_virtual_controller_button = make_flat_stylebox(style_normal_color, 0, 0, 0, 0, 10000, false, 4 * scale); + style_virtual_controller_button->set_corner_detail(24 * scale); + + Ref style_virtual_controller_button_pressed = make_flat_stylebox(style_pressed_color, 0, 0, 0, 0, 10000, true, 4 * scale); + style_virtual_controller_button_pressed->set_corner_detail(24 * scale); + + theme->set_stylebox("button_normal", "VirtualController", style_virtual_controller_button); + theme->set_stylebox("button_pressed", "VirtualController", style_virtual_controller_button_pressed); + theme->set_stylebox("left_normal_joystick", "VirtualController", style_joystick); + theme->set_stylebox("left_normal_tip", "VirtualController", style_joystick_tip); + theme->set_stylebox("left_pressed_joystick", "VirtualController", style_joystick); + theme->set_stylebox("left_pressed_tip", "VirtualController", style_joystick_tip); + theme->set_stylebox("right_normal_joystick", "VirtualController", style_joystick); + theme->set_stylebox("right_normal_tip", "VirtualController", style_joystick_tip); + theme->set_stylebox("right_pressed_joystick", "VirtualController", style_joystick); + theme->set_stylebox("right_pressed_tip", "VirtualController", style_joystick_tip); + // Window theme->set_stylebox("embedded_border", "Window", sb_expand(make_flat_stylebox(style_popup_color, 10, 28, 10, 8), 8, 32, 8, 6));