New Input System Doesn't Work After Load Scene

I load a “Battle Scene” by loading scene additive, and deactivating the old scene.
Then when battle is over I unload the battle scene and re-activate the old scene.

However after re-activating the old scene my Player receives no input in Player Input.
I think this is because Player Input may still be trying to act upon the “Battle Scene” version of my character.

How do I return control to my player after returning to my old scene?

I actually have an answer for this!

But first, the obvious answer: if you can reuse the PlayerInput object in your main scene (that is, don’t disable it, and don’t create a new one in the additive scene), that will be the most efficient way to solve this problem.

But sometimes you really want a new PlayerInput, perhaps because the new scene really is a new scene and the old scene is not being displayed or used at all. That’s what I’m doing for the “map view” in my game – add a new scene additively, then disable everything in the old scene.

The Answer

Surprisingly easy: disable the old PlayerInput object before the new scene loads.

FindObjectOfType<PlayerInput>().gameObject.SetActive(false);
// load scene or whatever
var loader = SceneManager.LoadSceneAsync("MapScene", LoadSceneMode.Additive);
while (!loader.isDone)
{
    yield return null;
}

or something like that will do it. Obviously it disables input when the scene starts to load and doesn’t restore it until the scene has finished loading, which might not be acceptable for long load times, but it’s good enough for simple cases.

Explanation

When you create (or specifically, enable) a PlayerInput object, here’s part of the code that runs:

AssignPlayerIndex();
InitializeActions();
AssignUserAndDevices();
ActivateInput();

which causes the devices (e.g. your mouse and keyboard) to be assigned to that user. If you create another PlayerInput, there are no devices left to bind to that user! At least, not the same devices.

If you disable the PlayerInput, this is some of the code that runs:

DeactivateInput();
UnassignUserAndDevices();
UninitializeActions();

and as you can see, the devices are unbound.

Binding and unbinding devices is probably expensive, which is why it’d be nice to avoid if you can.

I fixed a similar issue I was having with my Input-System so I’ll throw my solution here in case anyone is as dumb as me.

At some point in the scene I was setting Time.timeScale to 0 to halt certain gameplay inputs/calculations. Since Time.timeScale is a static variable, it stays between scenes.

I fixed my issue by initializing Time.timeScale = 1 in the beginning of my new scene.