Browse Source

Merge pull request #107273 from DexterFstone/add-game-speed-controls

Add game speed controls to the embedded game window
Thaddeus Crews 2 months ago
parent
commit
3b04c8464c

+ 28 - 2
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 {

+ 11 - 0
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);

+ 100 - 1
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<EditorDebuggerSession> &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<EditorDebuggerSession> &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<Texture2D>()).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<GameViewDebugger> 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<GameViewDebugger> 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);

+ 17 - 0
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<String> &r_arguments);
 	void _instance_starting(int p_idx, List<String> &r_arguments);

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

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

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

+ 8 - 0
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;

+ 1 - 0
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);

+ 1 - 1
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