FixedUpdate executed on game start despite Time.timeScale being set to 0

For my game I need precise control of (2d) physics and FixedUpdate, but I seem to have stumbled across a problem. I need my game to start ‘paused’, i.e. physics have to be frozen and FixedUpdate must not be called.
In order to accomplish this, I’m using the “Script” Simulation Mode (manual simulating with Physics2D.Simulate()) and I execute Time.timeScale=0.0f and Time.fixedDeltaTime = 0.0f as soon as possible, in the Awake() method of GameManager singleton object whose Script Execution Order is set to execute before any other script.

But still, all active objects in the scene that have a FixedUpdate method execute it once nonetheless (but afterwards it works as expected and is no longer being executed as long as Time.timeScale remains 0).

I made sure that Time.timeScale=0 command is being executed before any FixedUpdate calls (verified through debugger and simple Log output). I also inspected the value of Time.timeScale inside that FixedUpdate, and it is 0.

Is this intended behavior?

I know that there are many ways to ‘fix’ this, for instance by checking if Time.timeScale==0 at the beginning of every FixedUpdate in every object that uses it, but it seems like a very dirty workaround. It would be called endlessly throughout execution although it was needed only in that one instance, and it also significantly pollutes the source files.

Hello. I don’t understand why you want to use Time.timeScale so as to pause your game. Why don’t you use simply some boolean at the start in order to freeze the object’s behavior? However, respecting your will, and reading the main explanation about Time.timeScale, I notice that you have to multiply Time.FixedDeltaTime with Time.timeScale**.** It’s required in order to avoid strange behavior - like yours. I hope that it will help you ++

Thank you for your answer, Geckoo. Although I think you misunderstood my problem a bit, you pointed me in the right direction because I finally found this line in the documentation of Time.timeScale:

“Note that changing the timeScale only takes effect on the following frames.”

Since I’m changing timeScale in the very first frame of the game, it becomes effective only in the second frame. Thus all objects with FixedUpdate() get that method called once.

Consequently there is no way to achieve the behavior I am after without resorting to tricks like having all objects disabled on game start, or something similar.

In the player settings → physics you can disable “Auto Simulation”. I didn’t try but I strongly assume this will prevent any FixedUpdate function to be called until you either call Physics.Simulate() yourself or autoSimulate is reenabled in some other function.

Edit:
sorry, I was wrong:

“Note that MonoBehaviour.FixedUpdate will still be called at the rate defined by Time.fixedDeltaTime

FYI: It’s 2D physics here and there you have an enum of SimulationMode2D which allows you to control it to run in FixedUpdate, Update or Script. The dev is already using Script and manually simulating.

The thing is, it seems that this post is about FixedUpdate and its relationship to time-scale which isn’t a Physics thing at all. Physics uses FixedUpdate just like you do or any other Unity system such as Animation. This seems like a scripting question about FixedUpdate being called when time-scale is zero.

1 Like

Exactly, it’s about FixedUpdate and its relationship to time-scale.
It seemed like a physics question to me, but you’re right in that it is more connected to general scripting.

I might try asking in the Scripting group, but (unfortunately) it seems to be working exactly as stated in the documentation of time.timeScale.

Thank you everyone for your answers!

1 Like

I’m not necessarily suggesting you move your conversation, just a point of clarification here in decoupling the conversation of FixedUpdate and Physics. It’s clear you know the separation but plenty of devs couple the two as if physics is driving it rather than the other way around.

I cannot say I’ve ever tried this but is it an option to set Time.timescale in the project settings and does that stop the player-loop from calling FixedUpdate initially?

Alternately, I know it might be a pain, but could you early-out of your critical FixedUpdate calls if timescale is zero if the above doesn’t work for you?

1 Like

That was a great suggestion - I wasn’t even aware that there is a setting for Time Scale in the project settings!
(Project SettingsTimeTime Scale)
I tried setting it to zero, but it behaves exactly the same as when Time.timeScale is set to zero from a script on the first frame of execution: FixedUpdate is called once in the first frame.

I really wanted to avoid having pieces of “early-out” code in my FixedUpdate, partly because (after the first frame) they would unnecessarily get executed over and over again, but mostly because it kind of pollutes the sources.

But I did come up with a workaround: in the first frame, I disable a root object that is a parent to all the physical objects in the scene, and this makes them not execute FixedUpdate. On second frame, when timeScale has been updated internally, I re-enable that object and everything seems to be working ok.

1 Like

Well just for fun and giggles here’s something that officially I absolutely do not recommend you do if you want sanity in your life. For starters, you’ll need to turn-on “Allow Unsafe Code” in the Player Settings but this works by assigning a custom delegate to replace the script callbacks for fixed-update and then (the unsafe part) only call the native stuff if the time-scale isn’t zero. This would be completely safe if you had some singleton that managed calling your equivalent of FixedUpdate though. I cannot emphasise how unsupported and unsafe this is though but fun to try:

using System;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;

public static class MyPlayerLoop
{
    delegate void NativeFunction();
    private static NativeFunction m_NativeFunction;

    [RuntimeInitializeOnLoadMethod]
    private static void AppStart()
    {
        var defaultSystems = PlayerLoop.GetDefaultPlayerLoop();
        var customUpdate = new PlayerLoopSystem()
        {
            updateDelegate = CustomFixedUpdate,
            type = typeof(MyPlayerLoop)
        };
        var nativePtr = ReplaceSystem<FixedUpdate.ScriptRunBehaviourFixedUpdate>(ref defaultSystems, customUpdate);
        unsafe
        {
            nativePtr = new IntPtr(*((Int64*)nativePtr.ToPointer()));
        }
    
        m_NativeFunction = Marshal.GetDelegateForFunctionPointer(nativePtr, typeof(NativeFunction)) as NativeFunction;
        PlayerLoop.SetPlayerLoop(defaultSystems);
    }
 
    private static IntPtr ReplaceSystem<T>(ref PlayerLoopSystem system, PlayerLoopSystem replacement)
    {
        if (system.type == typeof(T))
        {
            var nativePtr = system.updateFunction;
            system = replacement;
            return nativePtr;
        }

        if (system.subSystemList == null)
            return IntPtr.Zero;
    
        for (var i = 0; i < system.subSystemList.Length; i++)
        {
            var nativePtr = ReplaceSystem<T>(ref system.subSystemList[i], replacement);
            if (nativePtr == IntPtr.Zero)
                continue;

            return nativePtr;
        }

        return IntPtr.Zero;
    }

    private static void CustomFixedUpdate()
    {
        if (Mathf.Approximately(Time.timeScale, 0f))
            return;
    
        Debug.Log("Custom update running!");
        m_NativeFunction();
    }
}

Part of this code came from here: Unity 2018 and PlayerLoop. Introduction | by Mika Notarnicola | Medium

2 Likes

Wow, that’s some really nice code. A bit out of the ordinary, but seriously cool! It will come in handy if my simple workaround proves to be inadequate.

This has opened up my eyes to beautiful things that I might use in other parts of my code.
Though it’s a bit out of scope of my original problem, I’d recommend that Medium article to anyone who’s using Unity in a more advanced way. It also links to this great read for anyone who’s interested:

1 Like

To be clear though, the only unsafe part is this. The backend is always likely to be 64-bit and TBH, the pointer is likely to always be a pointer to a pointer but there’s zero guarantee. I have no idea how/if this will work correctly on all platforms either.

But yeah, it does highlight some cool things that are exposed. :slight_smile:

1 Like

In my opinion, setting “Time.timescale = 0” in the time-manager should NOT result in FixedUpdate being called and therefore I personally would classify that as a bug and maybe should be reported as such.

Although I am not responsible for this part of the engine and it’s also a little difficult to know if this change would cause problems elsewhere, I can see the C++ part of the engine that causes this and a minor (hack/test) change fixes it.

bool TimeManager::StepFixedTime()
{
    if (m_TimeScale == 0.0f || !HasFixedTimeStep())
    ...
}
1 Like

It definitely looks like a bug, but to be fair, wouldn’t this have to be fixed just for that one special case when game is started with timeScale==0?
In all other cases I’d say it actually works as documented (“changing the timeScale only takes effect on the following frames”).
In other words, would checking if m_TimeScale == 0.0f be needed after the first frame? Or is TimeManager::StepFixedTime() perhaps cached in some way?

1 Like

Yes, I’m not suggesting the above is the fix, as I said it was a hack/test to verify it’s a trivial thing to fix. So yes, it’s a bug if the TimeManager already has a scale of zero in my opinion.

1 Like

Ok then, I’ll try reporting a bug and see if it gets accepted. Thank you for your support! :slight_smile:

1 Like

Hey, NickMode. How’s the bug report? I’m at the 2021.3.13 and there’s still a fixed update after setting timeScale to zero.