Search code examples
c#unity-game-engine

Decreasing Unity resource usage when sitting in a menu


I was wondering if there is any way to lower the amount of resources that Unity uses when its ostensibly not doing anything - such as sitting in a menu that isn't animated. This is something that always annoys me in modern games, and I know it annoys others, that games sitting idle uses almost as many resources as it does while actually running.

In a low level application you could pause frame updates until a windows event triggers them, but this isn't possible in Unity as the update loop is triggered internally.

You can set the target framerate to be low, but this makes the menu unresponsive when you click on something, even if you immediately scale the framerate back up when you do, and it doesn't reduce the CPU usage much anyway.


Solution

  • Unity is a realtime game engine. This means that it is designed to use as much resources as it can. So generally speaking it's normal that Unity does that.


    How it does that? Every application that runs in the system, runs in a loop. But the classic UI applications use libraries provided by operating system to get the user input.

    On Windows, this is WinAPI under the hood. In case you create a UI application, you would use the GetMessage function, e.g.:

    while (GetMessage(...) != 0) {
        ...
    }
    

    GetMessage blocks the execution until a message is received

    In case you design a game loop you instead would use the PeekMessage function to process system messages:

    while (true) {
        if(PeekMessage(&msg,0,0,0,PM_REMOVE) {
            ...
        }
        
        ...
    }
    

    PeekMessage doesn't block the execution allowing the loop to execute as frequently as it can.

    Executing a loop this way will result in 100% load of the CPU core. That's not a good practice, so it is common to use std::this_thread::yield(); (C++, in C# this is Thread.Yield();) or system-provided equivalent to let other applications execute on the same core. In other words, the game doesn't really utilizes all available CPU time even if the Task Manager shows 100% usage.

    Now, everything in Unity, including input systems, operates within this loop, not touching the operating system. This is why you can't get the proper input when you lower FPS at which the game runs.


    How you can handle this:

    1. The easiest way would be to still rely on builtin settings, i.e. QualitySettings.vSyncCount and Application.targetFrameRate.

      You can't decrease the FPS numbers to 0 or 1, but you can try to use the values like 60, 30 or 15.

      There is also a way to limit framerate manually with code, which can't be switched off by the user, e.g.:

      const int _fpsLimit = 60;
      const double _desiredSecondsPerFrame = 1.0 / _fpsLimit;
      
      void Update() {
          var startOfFrameTicks = DateTime.Now.Ticks;
      
          while (true) {
              Thread.Yield(); // Let's the system to execute something else waiting for CPU time
      
              // Then we return back and check whether we need to wait more or exit the loop
              var currentTicks = DateTime.Now.Ticks;
              var elapsedTime = TimeSpan.FromTicks(currentTicks - startOfFrameTicks).TotalSeconds;
              if(elapsedTime >= _desiredSecondsPerFrame)
                  break;
          }
      }
      
    2. Unity can be embedded into an application. And that application can be implemented as a UI application. So you can create your UI outside of Unity and only use Unity to run the gameplay.

    3. It is probably possible to achieve the same by implementing your own UI loop using GetMessage. It should run in a separate thread and then trigger the update of the main thread. The questions here are how to not interfer with the already existing message loop of Unity, and how to actually trigger an update.