When In Execution Order Can I Manipulate Objects On New Scene Load

I’m currently working on scene loading and loading save games. I’m a bit confused about when I can manipulate the scene objects in order to restore the state from the save game.

For example, lets say I want to load a scene and then restore an object’s saved position. If I write the following code, it will not work:

SceneManager.LoadScene(sceneName);
restoreGameObjectState();

So then I was trying to decide, at what point can I set the position or otherwise manipulate the scene object? I looked at the order of execution doc here: Unity - Manual: Order of execution for event functions . Do I need to use the WaitForEndOfFrame thing? I’m not sure I really understand it. I’ve done plenty of async stuff in other languages but not in C#.

I looked at this doc: Unity - Scripting API: WaitForEndOfFrame

But I still don’t get it.

I tried something like this:

private loadSave() {
  SceneManager.LoadScene(sceneName);
  restoreGameObjectState();
}

private IEnumerable restoreGameObjectState() {
  yield return new WaitForEndOfFrame();
  // change object positions here
  yield return null;
}

But then it just never calls the code that updates the object positions.

Am I turning restoreGameObjectState into an async method with that code? If it’s yielding, something has to wake it back up, no? Why do they yield twice in the example from the doc. I tried to replicate that to a degree. I think this is maybe me not understanding C#?

I’m starting to get a better understanding just reading up more on C#.

It returns the IEnumerator. And it is intended to return multiple things potentially, thus why the two yields. The second one that returns null tells the caller that it’s done with the enumeration. And presumably, it is Unity that is generally intended to read this enumeration. So it’s probably meant to go back to one of the standard Unity lifecycle methods.

Trouble with this is, loading a game is triggered by a button click. I’m using the UI Toolkit. The button click is a callback function registered to the button. That callback method is the one calling the code to actually load the save. I’m not sure how that would get back to Unity. Maybe I can return the IEnumerator from the callback function? Also I noticed that when calling another method in a chain, in the example, they are not doing anything with the returned IEnumerator, they just call the function. I’m guessing that’s a C# thing. I guess I’ll try that and report back.

Just to give an idea of the sequence and how I imagine it could work:

private IEnumerator buttonCallback() {
	controller.loadCurrentGame();
	yield return null;
}

public IEnumerator loadCurrentGame() {
	persistService.loadGame();
	// maybe do other stuff
	yield return null;
}

public IEnumerator loadGame() {
	SceneManager.loadScene(sceneName);
	yield return new WaitForEndOfFrame();
	// set positions and stuff here
	yield return null;
}

But this would only be if I’m not still confused and if Unity will actually allow that from a UI Toolkit button callback.

Returning an IEnumerator from the button callback function doesn’t work. Complains of wrong return type.

Should I be doing something like this sceneloaded thing? How to change transform.position after loadScene?

I feel like there is a right way to do this, and I haven’t found it yet.

I mean to start co-routine you need to actually start the Coroutine via MonoBehaviour.StartCoroutine: Unity - Scripting API: MonoBehaviour.StartCoroutine

Just calling an IEnumerator method does not make it execute as a coroutine.

So, I have a couple of options I can try:

Coroutine method:

public void loadGame() {
	SceneManager.loadScene(sceneName);
	IEnumerator prepSceneCoroutine = prepScene(data);
	StartCoroutine(prepSceneCoroutine);
}

private IEnumperator prepScene(data) {
	yield return new WaitForEndOfFrame() // or optionally, maybe just yield return null
	// do stuff with data
}

SceneLoaded method:

void OnEnable() {
	SceneManager.sceneLoaded += prepScene;
}

public void loadGame() {
	data = whatever
	SceneManager.loadScene(sceneName);
}

void prepScene(Scene scene, LoadSceneMode mode) {
	// do stuff with data
}

void OnDisable() {
	SceneManager.sceneLoaded -= prepScene;
}

So far the coroutine method isn’t working. It runs without errors, and I can see it try to move the object. But when scene starts the object is back to the original starting position. I thought maybe this was complicated by the fact that because a menu is open, I have Time.timeScale set to 0, but I took that out and it didn’t change anything.

I tried the sceneLoaded method and it mostly works. It was able to restore the position of a generic game object. However, my player object, which has more complicated scripts attached, was not restored to the saved position. I’ll have to do more debugging on that to understand what else can interfere with restoring state. I think the player already thinks it knows where it is and when I try to change the position, it’s overridden in the control script. I need to implement some way to halt player control until after state is loaded.

EDIT: I bet the CharacterController is gumming up the works. I probably have to use the CharacterController.Move method for the player character since it has a CharacterController attached.

EDIT(2): It worked to use the CharacterController.Move method. Though since that method has relative movement, the actual line of code looks like this:

this.characterController.Move(state.Position - gameObject.transform.position);