diff --git a/core/config/engine.cpp b/core/config/engine.cpp index fa1be2d30d5..154fed8fa17 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -38,24 +38,40 @@ #include "core/version.h" #include "servers/rendering/rendering_device.h" +void Engine::_update_time_scale() { + _time_scale = _user_time_scale * _game_time_scale; + user_ips = MAX(1, ips * _user_time_scale); + max_user_physics_steps_per_frame = MAX(max_physics_steps_per_frame, max_physics_steps_per_frame * _user_time_scale); +} + void Engine::set_physics_ticks_per_second(int p_ips) { ERR_FAIL_COND_MSG(p_ips <= 0, "Engine iterations per second must be greater than 0."); ips = p_ips; + _update_time_scale(); } int Engine::get_physics_ticks_per_second() const { return ips; } +int Engine::get_user_physics_ticks_per_second() const { + return user_ips; +} + void Engine::set_max_physics_steps_per_frame(int p_max_physics_steps) { ERR_FAIL_COND_MSG(p_max_physics_steps <= 0, "Maximum number of physics steps per frame must be greater than 0."); max_physics_steps_per_frame = p_max_physics_steps; + _update_time_scale(); } int Engine::get_max_physics_steps_per_frame() const { return max_physics_steps_per_frame; } +int Engine::get_user_max_physics_steps_per_frame() const { + return max_user_physics_steps_per_frame; +} + void Engine::set_physics_jitter_fix(double p_threshold) { if (p_threshold < 0) { p_threshold = 0; @@ -112,11 +128,21 @@ uint32_t Engine::get_frame_delay() const { } void Engine::set_time_scale(double p_scale) { - _time_scale = p_scale; + _game_time_scale = p_scale; + _update_time_scale(); } double Engine::get_time_scale() const { - return freeze_time_scale ? 0 : _time_scale; + return freeze_time_scale ? 0.0 : _game_time_scale; +} + +void Engine::set_user_time_scale(double p_scale) { + _user_time_scale = p_scale; + _update_time_scale(); +} + +double Engine::get_effective_time_scale() const { + return freeze_time_scale ? 0.0 : _time_scale; } double Engine::get_unfrozen_time_scale() const { diff --git a/core/config/engine.h b/core/config/engine.h index ab03111f242..4cacdb55283 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -59,13 +59,17 @@ private: double _process_step = 0; int ips = 60; + int user_ips = 60; double physics_jitter_fix = 0.5; double _fps = 1; int _max_fps = 0; int _audio_output_latency = 0; double _time_scale = 1.0; + double _game_time_scale = 1.0; + double _user_time_scale = 1.0; uint64_t _physics_frames = 0; int max_physics_steps_per_frame = 8; + int max_user_physics_steps_per_frame = 8; double _physics_interpolation_fraction = 0.0f; bool abort_on_gpu_errors = false; bool use_validation_layers = false; @@ -101,14 +105,19 @@ private: bool freeze_time_scale = false; +protected: + void _update_time_scale(); + public: static Engine *get_singleton(); virtual void set_physics_ticks_per_second(int p_ips); virtual int get_physics_ticks_per_second() const; + virtual int get_user_physics_ticks_per_second() const; virtual void set_max_physics_steps_per_frame(int p_max_physics_steps); virtual int get_max_physics_steps_per_frame() const; + virtual int get_user_max_physics_steps_per_frame() const; void set_physics_jitter_fix(double p_threshold); double get_physics_jitter_fix() const; @@ -132,6 +141,8 @@ public: void set_time_scale(double p_scale); double get_time_scale() const; + void set_user_time_scale(double p_scale); + double get_effective_time_scale() const; double get_unfrozen_time_scale() const; void set_print_to_stdout(bool p_enabled); diff --git a/editor/run/game_view_plugin.cpp b/editor/run/game_view_plugin.cpp index 18677397a10..fac3b1c8e71 100644 --- a/editor/run/game_view_plugin.cpp +++ b/editor/run/game_view_plugin.cpp @@ -134,6 +134,28 @@ void GameViewDebugger::next_frame() { } } +void GameViewDebugger::set_time_scale(double p_scale) { + Array message; + message.append(p_scale); + + for (Ref &I : sessions) { + if (I->is_active()) { + I->send_message("scene:speed_changed", message); + } + } +} + +void GameViewDebugger::reset_time_scale() { + Array message; + message.append(1.0); + + for (Ref &I : sessions) { + if (I->is_active()) { + I->send_message("scene:speed_changed", message); + } + } +} + void GameViewDebugger::set_node_type(int p_type) { node_type = p_type; @@ -497,6 +519,8 @@ void GameView::_update_debugger_buttons() { suspend_button->set_disabled(empty); camera_override_button->set_disabled(empty); + speed_state_button->set_disabled(empty); + reset_speed_button->set_disabled(empty); PopupMenu *menu = camera_override_menu->get_popup(); @@ -509,6 +533,8 @@ void GameView::_update_debugger_buttons() { camera_override_button->set_pressed(false); } next_frame_button->set_disabled(!suspend_button->is_pressed()); + + _reset_time_scales(); } void GameView::_handle_shortcut_requested(int p_embed_action) { @@ -589,6 +615,51 @@ void GameView::_size_mode_button_pressed(int size_mode) { _update_embed_window_size(); } +void GameView::_reset_time_scales() { + if (!is_visible_in_tree()) { + return; + } + time_scale_index = DEFAULT_TIME_SCALE_INDEX; + debugger->reset_time_scale(); + _update_speed_buttons(); +} + +void GameView::_speed_state_menu_pressed(int p_id) { + time_scale_index = p_id; + debugger->set_time_scale(time_scale_range[time_scale_index]); + _update_speed_buttons(); +} + +void GameView::_update_speed_buttons() { + bool disabled = time_scale_index == DEFAULT_TIME_SCALE_INDEX; + reset_speed_button->set_disabled(disabled); + speed_state_button->set_text(vformat(U"%s×", time_scale_label[time_scale_index])); + _update_speed_state_color(); +} + +void GameView::_update_speed_state_color() { + Color text_color; + if (time_scale_index == DEFAULT_TIME_SCALE_INDEX) { + text_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)); + } else if (time_scale_index > DEFAULT_TIME_SCALE_INDEX) { + text_color = get_theme_color(SNAME("success_color"), EditorStringName(Editor)); + } else if (time_scale_index < DEFAULT_TIME_SCALE_INDEX) { + text_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor)); + } + speed_state_button->add_theme_color_override(SceneStringName(font_color), text_color); +} + +void GameView::_update_speed_state_size() { + if (!speed_state_button) { + return; + } + float min_size = 0; + for (const String lbl : time_scale_label) { + min_size = MAX(speed_state_button->get_minimum_size_for_text_and_icon(vformat(U"%s×", lbl), Ref()).x, min_size); + } + speed_state_button->set_custom_minimum_size(Vector2(min_size, 0)); +} + GameView::EmbedAvailability GameView::_get_embed_available() { if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) { return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED; @@ -775,9 +846,14 @@ void GameView::_notification(int p_what) { _update_ui(); } break; + case NOTIFICATION_POST_ENTER_TREE: { + _update_speed_state_size(); + } break; + case NOTIFICATION_THEME_CHANGED: { suspend_button->set_button_icon(get_editor_theme_icon(SNAME("Suspend"))); next_frame_button->set_button_icon(get_editor_theme_icon(SNAME("NextFrame"))); + reset_speed_button->set_button_icon(get_editor_theme_icon(SNAME("Reload"))); node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE]->set_button_icon(get_editor_theme_icon(SNAME("InputEventJoypadMotion"))); node_type_button[RuntimeNodeSelect::NODE_TYPE_2D]->set_button_icon(get_editor_theme_icon(SNAME("2DNodes"))); @@ -796,6 +872,9 @@ void GameView::_notification(int p_what) { camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera"))); camera_override_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl"))); + + _update_speed_state_size(); + _update_speed_state_color(); } break; case NOTIFICATION_READY: { @@ -1073,6 +1152,26 @@ GameView::GameView(Ref p_debugger, EmbeddedProcessBase *p_embe next_frame_button->set_accessibility_name(TTRC("Next Frame")); next_frame_button->set_shortcut(ED_GET_SHORTCUT("editor/next_frame_embedded_project")); + speed_state_button = memnew(MenuButton); + main_menu_hbox->add_child(speed_state_button); + speed_state_button->set_text(U"1.0×"); + speed_state_button->set_theme_type_variation(SceneStringName(FlatButton)); + speed_state_button->set_tooltip_text(TTRC("Change the game speed.")); + speed_state_button->set_accessibility_name(TTRC("Speed State")); + + PopupMenu *menu = speed_state_button->get_popup(); + menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_speed_state_menu_pressed)); + for (String lbl : time_scale_label) { + menu->add_item(vformat(U"%s×", lbl)); + } + + reset_speed_button = memnew(Button); + main_menu_hbox->add_child(reset_speed_button); + reset_speed_button->set_theme_type_variation(SceneStringName(FlatButton)); + reset_speed_button->set_tooltip_text(TTRC("Reset the game speed.")); + reset_speed_button->set_accessibility_name(TTRC("Reset Speed")); + reset_speed_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_reset_time_scales)); + main_menu_hbox->add_child(memnew(VSeparator)); node_type_button[RuntimeNodeSelect::NODE_TYPE_NONE] = memnew(Button); @@ -1154,7 +1253,7 @@ GameView::GameView(Ref p_debugger, EmbeddedProcessBase *p_embe camera_override_menu->set_h_size_flags(SIZE_SHRINK_END); camera_override_menu->set_tooltip_text(TTRC("Camera Override Options")); - PopupMenu *menu = camera_override_menu->get_popup(); + menu = camera_override_menu->get_popup(); menu->connect(SceneStringName(id_pressed), callable_mp(this, &GameView::_camera_override_menu_id_pressed)); menu->add_item(TTRC("Reset 2D Camera"), CAMERA_RESET_2D); menu->add_item(TTRC("Reset 3D Camera"), CAMERA_RESET_3D); diff --git a/editor/run/game_view_plugin.h b/editor/run/game_view_plugin.h index d5df1406554..12078120fc0 100644 --- a/editor/run/game_view_plugin.h +++ b/editor/run/game_view_plugin.h @@ -82,6 +82,9 @@ public: void set_suspend(bool p_enabled); void next_frame(); + void set_time_scale(double p_scale); + void reset_time_scale(); + void set_node_type(int p_type); void set_select_mode(int p_mode); @@ -172,6 +175,14 @@ class GameView : public VBoxContainer { EmbeddedProcessBase *embedded_process = nullptr; Label *state_label = nullptr; + int const DEFAULT_TIME_SCALE_INDEX = 5; + Array time_scale_range = { 0.0625f, 0.125f, 0.25f, 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 4.0f, 8.0f, 16.0f }; + Array time_scale_label = { "1/16", "1/8", "1/4", "1/2", "3/4", "1.0", "1.25", "1.5", "1.75", "2.0", "4.0", "8.0", "16.0" }; + int time_scale_index = DEFAULT_TIME_SCALE_INDEX; + + MenuButton *speed_state_button = nullptr; + Button *reset_speed_button = nullptr; + void _sessions_changed(); void _update_debugger_buttons(); @@ -185,6 +196,12 @@ class GameView : public VBoxContainer { void _embed_options_menu_menu_id_pressed(int p_id); void _size_mode_button_pressed(int size_mode); + void _reset_time_scales(); + void _speed_state_menu_pressed(int p_id); + void _update_speed_buttons(); + void _update_speed_state_color(); + void _update_speed_state_size(); + void _play_pressed(); static void _instance_starting_static(int p_idx, List &r_arguments); void _instance_starting(int p_idx, List &r_arguments); diff --git a/main/main.cpp b/main/main.cpp index 5be591b4fe8..9f26970c0df 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4604,10 +4604,10 @@ bool Main::iteration() { const uint64_t ticks_elapsed = ticks - last_ticks; - const int physics_ticks_per_second = Engine::get_singleton()->get_physics_ticks_per_second(); + const int physics_ticks_per_second = Engine::get_singleton()->get_user_physics_ticks_per_second(); const double physics_step = 1.0 / physics_ticks_per_second; - const double time_scale = Engine::get_singleton()->get_time_scale(); + const double time_scale = Engine::get_singleton()->get_effective_time_scale(); MainFrameTime advance = main_timer_sync.advance(physics_step, physics_ticks_per_second); double process_step = advance.process_step; @@ -4626,7 +4626,7 @@ bool Main::iteration() { last_ticks = ticks; - const int max_physics_steps = Engine::get_singleton()->get_max_physics_steps_per_frame(); + const int max_physics_steps = Engine::get_singleton()->get_user_max_physics_steps_per_frame(); if (fixed_fps == -1 && advance.physics_steps > max_physics_steps) { process_step -= (advance.physics_steps - max_physics_steps) * physics_step; advance.physics_steps = max_physics_steps; diff --git a/modules/jolt_physics/joints/jolt_hinge_joint_3d.cpp b/modules/jolt_physics/joints/jolt_hinge_joint_3d.cpp index 300a5319561..496897319d0 100644 --- a/modules/jolt_physics/joints/jolt_hinge_joint_3d.cpp +++ b/modules/jolt_physics/joints/jolt_hinge_joint_3d.cpp @@ -49,8 +49,8 @@ constexpr double HINGE_DEFAULT_RELAXATION = 1.0; double estimate_physics_step() { Engine *engine = Engine::get_singleton(); - const double step = 1.0 / engine->get_physics_ticks_per_second(); - const double step_scaled = step * engine->get_time_scale(); + const double step = 1.0 / engine->get_user_physics_ticks_per_second(); + const double step_scaled = step * engine->get_effective_time_scale(); return step_scaled; } diff --git a/scene/3d/velocity_tracker_3d.cpp b/scene/3d/velocity_tracker_3d.cpp index 822fb3c5106..2b92f72f886 100644 --- a/scene/3d/velocity_tracker_3d.cpp +++ b/scene/3d/velocity_tracker_3d.cpp @@ -71,7 +71,7 @@ Vector3 VelocityTracker3D::get_tracked_linear_velocity() const { if (position_history_len) { if (physics_step) { uint64_t base = Engine::get_singleton()->get_physics_frames(); - base_time = double(base - position_history[0].frame) / Engine::get_singleton()->get_physics_ticks_per_second(); + base_time = double(base - position_history[0].frame) / Engine::get_singleton()->get_user_physics_ticks_per_second(); } else { uint64_t base = Engine::get_singleton()->get_frame_ticks(); base_time = double(base - position_history[0].frame) / 1000000.0; @@ -84,7 +84,7 @@ Vector3 VelocityTracker3D::get_tracked_linear_velocity() const { Vector3 distance = position_history[i].position - position_history[i + 1].position; if (physics_step) { - delta = double(diff) / Engine::get_singleton()->get_physics_ticks_per_second(); + delta = double(diff) / Engine::get_singleton()->get_user_physics_ticks_per_second(); } else { delta = double(diff) / 1000000.0; } diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index f242f53e1ec..5c70c186364 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -206,6 +206,13 @@ Error SceneDebugger::_msg_next_frame(const Array &p_args) { return OK; } +Error SceneDebugger::_msg_speed_changed(const Array &p_args) { + ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); + double time_scale_user = p_args[0]; + Engine::get_singleton()->set_user_time_scale(time_scale_user); + return OK; +} + Error SceneDebugger::_msg_debug_mute_audio(const Array &p_args) { ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); bool do_mute = p_args[0]; @@ -528,6 +535,7 @@ void SceneDebugger::_init_message_handlers() { message_handlers["clear_selection"] = _msg_clear_selection; message_handlers["suspend_changed"] = _msg_suspend_changed; message_handlers["next_frame"] = _msg_next_frame; + message_handlers["speed_changed"] = _msg_speed_changed; message_handlers["debug_mute_audio"] = _msg_debug_mute_audio; message_handlers["override_cameras"] = _msg_override_cameras; message_handlers["transform_camera_2d"] = _msg_transform_camera_2d; diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 92d4723743f..1f183ec0a92 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -89,6 +89,7 @@ private: static Error _msg_clear_selection(const Array &p_args); static Error _msg_suspend_changed(const Array &p_args); static Error _msg_next_frame(const Array &p_args); + static Error _msg_speed_changed(const Array &p_args); static Error _msg_debug_mute_audio(const Array &p_args); static Error _msg_override_cameras(const Array &p_args); static Error _msg_set_object_property(const Array &p_args); diff --git a/scene/gui/video_stream_player.cpp b/scene/gui/video_stream_player.cpp index b08ee8cab59..79da7a42cb6 100644 --- a/scene/gui/video_stream_player.cpp +++ b/scene/gui/video_stream_player.cpp @@ -157,7 +157,7 @@ void VideoStreamPlayer::_notification(int p_notification) { double delta = first_frame ? 0 : get_process_delta_time(); first_frame = false; - resampler.set_playback_speed(Engine::get_singleton()->get_time_scale() * speed_scale); + resampler.set_playback_speed(Engine::get_singleton()->get_effective_time_scale() * speed_scale); playback->update(delta * speed_scale); // playback->is_playing() returns false in the last video frame