From 394860656ae809899ddecd7334f9fa3b277a1c03 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 27 Nov 2025 10:55:02 +0100 Subject: [PATCH] Add FPS limit and pause option when game is unfocused --- Source/Engine/Core/Config/TimeSettings.h | 12 ++++ Source/Engine/Engine/Engine.cpp | 21 +------ Source/Engine/Engine/Time.cpp | 55 ++++++++++++++++--- .../Platform/Linux/LinuxPlatformSettings.h | 2 + .../Engine/Platform/Mac/MacPlatformSettings.h | 2 + .../Windows/WindowsPlatformSettings.h | 2 + 6 files changed, 65 insertions(+), 29 deletions(-) diff --git a/Source/Engine/Core/Config/TimeSettings.h b/Source/Engine/Core/Config/TimeSettings.h index 991f505ab..4e308a32e 100644 --- a/Source/Engine/Core/Config/TimeSettings.h +++ b/Source/Engine/Core/Config/TimeSettings.h @@ -43,6 +43,18 @@ public: API_FIELD(Attributes="EditorOrder(20), Limit(0.1f, 1000.0f, 0.01f), EditorDisplay(\"General\")") float MaxUpdateDeltaTime = 0.1f; + /// + /// Limits maximum game framerate when application window loses focus. Use 0 to disable this feature. + /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Unfocused\")") + float UnfocusedMaxFPS = 30; + + /// + /// Enables pausing game when application window loses focus. + /// + API_FIELD(Attributes="EditorOrder(110), EditorDisplay(\"Unfocused\")") + bool UnfocusedPause = false; + public: /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 5e8224b4e..c0f1e06aa 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -40,15 +40,11 @@ #include "Engine/Scripting/ManagedCLR/MClass.h" #include "Engine/Scripting/ManagedCLR/MMethod.h" #include "Engine/Scripting/ManagedCLR/MException.h" -#include "Engine/Core/Config/PlatformSettings.h" #endif namespace EngineImpl { bool IsReady = false; -#if !USE_EDITOR - bool RunInBackground = false; -#endif String CommandLine = nullptr; int32 Fps = 0, FpsAccumulatedFrames = 0; double FpsAccumulated = 0.0; @@ -168,9 +164,6 @@ int32 Engine::Main(const Char* cmdLine) Platform::BeforeRun(); EngineImpl::InitMainWindow(); Application::BeforeRun(); -#if !USE_EDITOR && (PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC) - EngineImpl::RunInBackground = PlatformSettings::Get()->RunInBackground; -#endif LOG_FLOOR(); LOG_FLUSH(); Time::Synchronize(); @@ -353,20 +346,8 @@ void Engine::OnUpdate() UpdateCount++; - const auto mainWindow = MainWindow; - -#if !USE_EDITOR - // Pause game if window lost focus and cannot run in a background - bool isGameRunning = true; - if (mainWindow && !mainWindow->IsFocused()) - { - isGameRunning = EngineImpl::RunInBackground; - } - Time::SetGamePaused(!isGameRunning); -#endif - // Determine if application has focus (flag used by the other parts of the engine) - HasFocus = (mainWindow && mainWindow->IsFocused()) || Platform::GetHasFocus(); + HasFocus = (MainWindow && MainWindow->IsFocused()) || Platform::GetHasFocus(); // Simulate lags //Platform::Sleep(100); diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index e55e76971..7d268cf73 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -6,12 +6,29 @@ #include "Engine/Platform/Platform.h" #include "Engine/Core/Config/TimeSettings.h" #include "Engine/Serialization/Serialization.h" +#if !USE_EDITOR +#include "Engine/Engine/Engine.h" +#endif namespace { bool FixedDeltaTimeEnable; float FixedDeltaTimeValue; float MaxUpdateDeltaTime = 0.1f; +#if USE_EDITOR + constexpr bool _gamePausedUnfocsed = false; + #define GetFps(fps) fps +#else + bool _gamePausedUnfocsed = false; + float UnfocusedMaxFPS = 0.0f; + bool UnfocusedPause = false; + float GetFps(float fps) + { + if (UnfocusedMaxFPS > 0 && !Engine::HasFocus && fps > UnfocusedMaxFPS) + fps = UnfocusedMaxFPS; + return fps; + } +#endif } bool Time::_gamePaused = false; @@ -52,15 +69,16 @@ void TimeSettings::Apply() Time::DrawFPS = DrawFPS; Time::TimeScale = TimeScale; ::MaxUpdateDeltaTime = MaxUpdateDeltaTime; +#if !USE_EDITOR + ::UnfocusedMaxFPS = UnfocusedMaxFPS; + ::UnfocusedPause = UnfocusedPause; +#endif } void Time::TickData::Synchronize(float targetFps, double currentTime) { + OnReset(targetFps, currentTime); Time = UnscaledTime = TimeSpan::Zero(); - DeltaTime = UnscaledDeltaTime = targetFps > ZeroTolerance ? TimeSpan::FromSeconds(1.0f / targetFps) : TimeSpan::Zero(); - LastLength = static_cast(DeltaTime.Ticks) / TimeSpan::TicksPerSecond; - LastBegin = currentTime - LastLength; - LastEnd = currentTime; NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0; } @@ -115,7 +133,7 @@ void Time::TickData::OnTickEnd() void Time::TickData::Advance(double time, double deltaTime) { float timeScale = TimeScale; - if (_gamePaused) + if (_gamePaused || _gamePausedUnfocsed) timeScale = 0.0f; LastBegin = time; UnscaledDeltaTime = TimeSpan::FromSeconds(deltaTime); @@ -194,9 +212,11 @@ void Time::SetGamePaused(bool value) { if (_gamePaused == value) return; - _gamePaused = value; + if (_gamePausedUnfocsed) + return; + // Reset ticking const double time = Platform::GetTimeSeconds(); Update.OnReset(UpdateFPS, time); Physics.OnReset(PhysicsFPS, time); @@ -249,7 +269,24 @@ void Time::Synchronize() bool Time::OnBeginUpdate(double time) { - if (Update.OnTickBegin(time, UpdateFPS, MaxUpdateDeltaTime)) +#if !USE_EDITOR + // Pause game if window lost focus (based on game settings) + bool gamePausedUnfocsed = !Engine::HasFocus && UnfocusedPause; + if (gamePausedUnfocsed != _gamePausedUnfocsed) + { + _gamePausedUnfocsed = gamePausedUnfocsed; + if (!gamePausedUnfocsed) + { + // Reset ticking + const double time = Platform::GetTimeSeconds(); + Update.OnReset(UpdateFPS, time); + Physics.OnReset(PhysicsFPS, time); + Draw.OnReset(DrawFPS, time); + } + } +#endif + + if (Update.OnTickBegin(time, GetFps(UpdateFPS), MaxUpdateDeltaTime)) { Current = &Update; return true; @@ -259,7 +296,7 @@ bool Time::OnBeginUpdate(double time) bool Time::OnBeginPhysics(double time) { - if (Physics.OnTickBegin(time, PhysicsFPS, _physicsMaxDeltaTime)) + if (Physics.OnTickBegin(time, GetFps(PhysicsFPS), _physicsMaxDeltaTime)) { Current = &Physics; return true; @@ -269,7 +306,7 @@ bool Time::OnBeginPhysics(double time) bool Time::OnBeginDraw(double time) { - if (Draw.OnTickBegin(time, DrawFPS, 1.0f)) + if (Draw.OnTickBegin(time, GetFps(DrawFPS), 1.0f)) { Current = &Draw; return true; diff --git a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h index 6e9200cc6..dd63c0d78 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatformSettings.h +++ b/Source/Engine/Platform/Linux/LinuxPlatformSettings.h @@ -43,8 +43,10 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API /// /// Enables game running when application window loses focus. + /// [Deprecated in v1.12] /// API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + DEPRECATED("Use UnfocusedPause from TimeSettings.") bool RunInBackground = false; /// diff --git a/Source/Engine/Platform/Mac/MacPlatformSettings.h b/Source/Engine/Platform/Mac/MacPlatformSettings.h index f78b12167..9f6807fba 100644 --- a/Source/Engine/Platform/Mac/MacPlatformSettings.h +++ b/Source/Engine/Platform/Mac/MacPlatformSettings.h @@ -40,8 +40,10 @@ API_CLASS(Sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API /// /// Enables game running when application window loses focus. + /// [Deprecated in v1.12] /// API_FIELD(Attributes="EditorOrder(1010), EditorDisplay(\"Other\", \"Run In Background\")") + DEPRECATED("Use UnfocusedPause from TimeSettings.") bool RunInBackground = false; /// diff --git a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h index 9745b61c1..40e6bef9e 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatformSettings.h +++ b/Source/Engine/Platform/Windows/WindowsPlatformSettings.h @@ -43,8 +43,10 @@ API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API /// /// Enables game running when application window loses focus. + /// [Deprecated in v1.12] /// API_FIELD(Attributes="EditorOrder(1010), DefaultValue(false), EditorDisplay(\"Other\", \"Run In Background\")") + DEPRECATED("Use UnfocusedPause from TimeSettings.") bool RunInBackground = false; ///