Hey Chris, I hope you can shed a light on an issue I've detected.
My game has pretty strict NPC schedules that need to be followed perfectly, no matter when or how a scene is loaded. To account for this, a long time ago I created a script that interpolates the start time of a path, the expected arrival time, and resizes an AC path based on the current in-game time global variable. So if the player enters the scene before the start time, the path from point A to B is not changed, and an actionlist will then tell the NPC to follow the path. If the player enters the scene after the NPC is supposed to have started the path, but before the expected arrival time, the path nodes that the NPC is supposed to have already walked through are moved to the exact interpolated point along the path, and an actionlist will teleport/move the NPC from the relocated first node. If the player enter the scene after the expected arrival time, ALL nodes will have been moved to the position of the last node, so the same actionlist in practice works as a simple teleport.
Now, over a year ago (I can't remember exactly when, but I could possibly find out if necessary), an AC update broke this script. All calculations and manipulation of paths used to be done under void Start(), but with the AC update, if I remember correctly, the actionlist that runs on start of the scene started running BEFORE my script had the time to reorganize the paths, so the NPCs would start moving from the wrong place. At the time, I fixed this by changing void Start() to void Awake(). I worked on the game for months without noticing any issues related to this.
Now, after a rather long hiatus, I'm back to working on the game. I updated Unity to 6000.0.24f1 and AC to 1.81.7. I have noticed that on a normal playthrough (without saving/loading), my script works the same as it ever did. If in the previous scene the global time variable was 21500, upon changing scenes, my script in the new scene will automatically rearrange the paths to reflect that value. The problem happens when I load a save. I have the option "Always reload scene when loading a save file?" checked (so that the path component is returned to its original form before my script manipulates it). The problem here is that if I create the save at time == 21500, let the game run to 25000, then load the save, the scene will be reloaded, and my script will run BEFORE the global variable changes from 25000 to 21500.
I would like to suggest a minor change in AC: is it possible for all global settings (Global variables, inventories, menus, etc) to be loaded before the scene is reloaded, in this order:
Currently the order appears to be:
Or, if you think this could cause other issues, please advise on how to get a script to run before the OnStart/OnLoad actionlists, but after all the data from a save is loaded?
I'm aware these systems are very complex (I remember us taking a while to troubleshoot the loading order of Component Variables on the Player prefab, which were sometimes loaded incorrectly after the OnStart/Onload cutscene), so any help is greatly appreciated.
It looks like you're new here. If you want to get involved, click one of these buttons!
Comments
I've considered moving the whole method out of void Awake() and calling it at the start of the OnStart/OnLoad actionlists (i.e. after everything is loaded but before the rest of the actionlist sets up the NPCs), but because this is a component attached to each path, I'd have to run an action for each and every path in the scene, which soon becomes cumbersome.
I'd be very hesitant against making any changes to the loading process, as there'd be major potential for knock-on issues.
The exact order of loading is:
In terms of custom events, we have:
I can consider a third event, but it would need to go in between "Global settings" and "Scene data". Would this work as a hook for your initialisation code?
I wasn't sure it would be necessary to create a new hook, as long as OnAfterChangeScene ran after the scene data was loaded, but definitely before the OnStart/OnLoad actionlists. But when I tried to adapt my script to test this, I ran into a different problem.
This is the relevant code that I added to my PathsSolution script:
I attached a PathsSolution component to the player prefab, and left its Paths field empty (so that it wouldn't add itself to the allPaths list). Then I created an OnAfterChangeScene event, and made an action that sends the player prefab a "SetAllPaths" message.
When I click play, all the PathsSolution objects in the scene instantly "say hello", as expected. However, when I change scenes (either by entering a new scene, returning to the initial one or loading the game), it throws this error:
I have tried doing this with a scene object as well - the action saves its constant ID correctly, then when I change scenes it can't find the constant ID (correctly, because the object isn't in the scene), and when i return to the original scene, it STILL throws the error above, even though an object with that constant ID very much still exists.
Any idea why this would be happening?
Are you unhooking from the event in your OnDisable function? This is a necessary step to prevent events firing on deleted objects.
The approach I'd look into myself would be to have a single instance of a script in the scene that hooks into OnAfterChangeScene, uses this to handle all the PathSolution business, and then manually calls any "OnLoad" cutscenes previously (now unset) from the Scene Manager, i.e.:
Thanks! I wasn't hooking it through code, I used the Events Editor to create a OnAfterChangeScene event and run an actionlist that sent a message to my script.
What I was having issues with was my code above. It was supposed to create a list, which each instance of PathsSolution would add itself to, and then I could use OnAfterChangeScene to iterate through that list, running their SetPath() functions. For some reason that I haven't figured out yet, creating a list of all paths to be iterated through only worked the first time I clicked play - all subsequent times the scene was loaded (either via save or scene switch), the console gave me the reference exception instead.
I tried your script above, but because the issue I was having was exactly with how to update all PathSolutions objects in the scene at once, I couldn't get it to work.
The last thing I tried was simply to use OnAfterChangeScene instead of void Start or Awake in the PathsSolution script itself. I was worried this could cause running order issues (which you did account for in your code, but I couldn't include that part because I didn't want every single instance to run the load cutscene).
That seemed to work though. The path updates seem to always be happening before the OnLoad or OnStart cutscenes sends the NPCs on their paths. I worry this is not guaranteed (depending on PC performance or other unknown factors), so ideally I would use the code you provided above, but I'm a bit stuck. Is my worry correct, or can I trust the order will always be right?
Anyway, if the order is not guaranteed, instead of trying to create a list by having each instance add itself to it, should I just use FindObjectsOfTypeAll in the external script, iterate through that list to run the SetPath() function, and then run the the load cutscene?
If so, I was also wondering how to account for Start and Load cutscenes (not just Load ones). I have one of each. The OnStart cutscene runs on start and then calls the OnLoad one at the end. The OnLoad one of course just runs on load.
AC has a few hooks of its own into the OnAfterChangeScene event, so by default I wouldn't call the order guaranteed. You should be safe, however, so long as the script is present in the scene file, and has a negative default execution order (so that it registers the event before AC does).
Otherwise, FindObjectsOfType would be the way I'd go about it - so long as you know all the objects you want to affect are in the scene at the time.
You can amend the script to have a separate array of "on start" cutscenes and run them if the loadingGame != LoadingGame.No block is not executed.
Cheers, all good now!