#Dealing with delay between bringing in scene and have it subscribe to event?
1 messages · Page 1 of 1 (latest)
use Awake to subscribe, perhaps
Awake is run when the gameobject is first activated (including upon load), but Start is run on the first frame the gameobject is active and the component is enabled
if you need to use Start for some reason, then wait a frame before firing the event
Oh, that would be great. I'm refactoring the code. Hopefully I can test those ideas in the next 30 minutes.
Thank you!
@bleak ocean : Unfortunately that didn't work. I tried async and syncronous and it didn't make a difference. I just setup an event that triggers after my scene controller loads and even that doesn't load. Seems like awake() + start() don't happen until the next frame? Is there really no way to make this work without waiting to the next frame? That feels really hacky
How are you determining if the scene is loaded or not? Awake definitely should be called on the first frame the scene completed loading.
shouldn't it be called whne the scene loads directly, not frame-bound?
I think so. My point was that it should be called by the point of the first frame. Definitely not any later than that.
loadSceneAsync and ditch out awake..
Sorry...I left out something critical. I'm pulling in the scenes additively via a persistant scene.
Id load the scene, find and init some component myself so then it happens in time (hopefully)
I basically always init components manually and thus avoid this awake/start dance
Ok. Let me take a step back. This all stems from me not understanding how to communicate between scenes properly. As I said prior, I am running core logic in a persistent scene (P), there is currently another scene running (A), it pulls in a scene (B) and wants to run some code from that scene. The only way I know of to handle this is via delegates. The problem is, I need scene B to subscribe to the delegate. Even after I fully load in scene B, it has not run the code in awake() / has not subscribed so in script A, when I invoke the delegate, it doesn't run the code.
Sorry for being so stupid! I can't get my head around this yet.
So its a timing issue
As I said, you could load scene b, find a component in it, init it manually to cause the event sub to happen then.
When is the event invoked in relation to loading scene B?
Directly after.
How is the scene additively loaded, scene manager? addressables?
scene manager script in the persistant sene. I'm not sure what addressables means
(singleton)
what line of code does the load?
if SceneManager.LoadSceneAsync() is awaited I "think" that would be the best way to know for sure that scene B has been fully loaded
LoadScene() mentions that it completes the load 1 frame later
SceneController.instance.LoadQuick(sceneName);
scenecontroller =
public void LoadQuick(string sceneName)
{
scenesAdd.Add(sceneName);
Load();
}
public void Load()
{
// Remove scenes
for (int i = 0; i < scenesRemove.Count; i++)
{
if (LoadTest(scenesRemove[i]))
{
UnloadScene(scenesRemove[i]);
}
}
// Add in scenes
for (int i = 0; i < scenesAdd.Count; i++)
{
if (LoadTest(scenesAdd[i]) == false)
{
LoadScene(scenesAdd[i]);
}
}
// Track progress
StartCoroutine(LoadingProgress());
}
public IEnumerator LoadingProgress()
{
if (progressScene)
{
LoadingController.instance.Enable();
}
if (loadAsync && scenesList.Count > 0 && 1 ==2)
{
for (int i = 0; i < scenesList.Count; i++)
{
while (!scenesList[i].isDone)
{
float progress = 0;
foreach (AsyncOperation scene in scenesList)
{
progress += scene.progress;
}
progress = (progress / scenesList.Count);
EventController.loadingProgress?.Invoke(progress);
yield return null;
}
}
}
EventController.sceneComplete?.Invoke();
if (progressScene)
{
LoadingController.instance.Disable();
}
}
hmm the function that matters is not there
sorry
public void LoadScene(string sceneName)
{
SceneIndex sceneIndex = (SceneIndex)Enum.Parse(typeof(SceneIndex), sceneName);
if (loadAsync)
{
scenesList.Add(SceneManager.LoadSceneAsync((int)sceneIndex, LoadSceneMode.Additive));
}
else
{
SceneManager.LoadScene((int)sceneIndex, LoadSceneMode.Additive);
}
}
private List<AsyncOperation> scenesList = new List<AsyncOperation>();
does this use the sync or async load scene function?
(scene b)
Adding a single frame wait after the scene has loaded would probably solve this problem and I do this myself with addressable additive scene loads
I tried with both settings
and does this other logic use EventController.sceneComplete to know when the scene loaded?
yeah, it subscribes to it
scene b?
correct. The one that just got loaded in.
that makes no sense
haha....its the best I got
why would the NEWLY loaded scene use an event relating to its own loading?
Or did i miss understand?
The newly loaded scene (B) subscribes to an event that is being invoked in scene (A) directly after it has been loaded.
Thats the best way I could come up with to run code between scenes
why not just get a component in the newly loaded scene B and invoke a function directly?
All I can say is that I researched this hours and talked a lot of people at they made it sound like that was not possible.
I just want to run code in the other scene and adhere to best practices
its so so easy
a Find() would work because that looks over ALL loaded scenes
You can also get root objects from a newly loaded scene and search that way
https://docs.unity3d.com/ScriptReference/SceneManagement.Scene.GetRootGameObjects.html
Does it make a difference which one?
well second may be a little faster but I usually just do something like this:
await LoadNewSceneAsync(newLevelScene.address);
var levelInit = GameObject.FindObjectOfType<LevelInitBase>();
await InitLevelInitBase(levelInit, levelTemplate);
2022 hence older api
Ok. That seems a lot easier. I'm going to explore it. You can do something similar with tags right?
well ill point out that you already have a component that subs to this event so why not locate that specifically and call a function on that instance?
That sounds like what I want. Do you mean via FindObjectofType?
Yea or by searching the root gameobjects:
SomeComponent c = null;
foreach(var rootOb in scene.GetRootGameObjects())
{
if(rootOb.TryGetComponent(out c))
{
break;
}
}
if(c != null)
{
//Yay we found it!
}
else Debug.LogError("Special component was not found");
you could put a script in the loaded scene, in its Start() it finds some sort of Registry object in your persistent scene and calls Hello() on that registry. You can use a ServiceLocator singleton to do that or use FindObjectByType(). This would be the most 'unity' way to do it. Your event-subscription thing to execute code sounds weird. that would kinda invert the inversion of the observer pattern.
regardless of what does the "handshake" I hope the information is now there so you can pick
@willow flint : I'd like to understand all possible options. The problem that I had was my script wanted to run code from the newly additively added scene right away. So the registery object idea I think has the same issue as my event approach.
then get this component from the new scene via a method like we discussed above and then do the shit
there is zero issue. what i described works perfectly and i've done it 100s of times. The point is, you should not make stuff that cares about +/- one frame when it comes to loading scenes. You will likely want to do all sorts of 1-frame delays, e.g. to let physics settle down, synthesize trigger events on object destroy and other kinds of funky things down the road.
@willow flint : I'm sorry, I just don't understand your suggestion. How is the ServiceLocator approach different then a delegate system?
So in scene A, I want to bring in scene B and then run some code that requires passing in variables. I also want to load in scene B other times and run different code / not the same as this situation.
its your choice
I just want to learn all the different options
just find/get/whatever the component from scene b and bam happy days
I'm not opposed to doing find at all
Id say first get it working and then think about if you want to implement such a design
he said he did 100 times another way as far as I know there is literally only one way (what you said)
thats because we pick a design we like and use it a lot
ive worked in projects where we had 1 large context reference we would pass to EVERYTHING.
we all hated it but the project was too big and old to really change it so we just kept it
Yeah...its exactly that part that scares me. I don't want to make some big fundamental decision in the beginning and regret it down the road. I'm not talking about this part per say but the hundreds of choice I have to make.
what i showed above was something I had in a base shared LevelLoadService that was made to easily load some gameplay scene and initialize it
It was used in a few games
I learnt to avoid singletons because everything using some static field can be a pain in the ass to undo later
a service locator is a singleton because it has to be, its just like a dependency injection container or really anything that you NEED to get the one static reference to the app itself. there is no way around having it. Even if you use Find() you use effectively a static global variable. The key is that it is the only one and you don't use it to couple code to concrete implementations. All you do with that service locator (or DI container) is asking "please give me instance of type IMyInterface", and you only do that in Start() and cache it and make all services have a longer lifetime than any of their users. You use editor references where you can, service locator only for dynamically loaded things you don't yet have a reference to. you keep cross-scene comms in a persistent bootstrap/systems scene and never use any static fields for communication ever.
The only option that works completely without a service locator is forward-inejcting all dependencies from a main() method, but that is very anti-unity and blocks many very useful compositional workflows in the editor.
A ok...that makes sense. I am familar with the idea of dependency injection. Is there a script can you recommend to look at? If I went this approach is it something I should code one myself?
a service locator singleton I mean
a service locator is trivial to DIY, its like 50 lines of code
Ok..so that's about two days work for me...haha
I'm going to do some research...writing it will help me learn
Thank you @wild idol @willow flint so much. You really make a big difference
212 lines, sorry https://hastebin.com/share/yegekuziyi.csharp
Important: populate that service locator in Awake() of a bootstrap script of your persistent orchestration scene. Then never change it until your orchestration scene unloads (use the lifecycle binding API in the locator to automate that). Then just Services.Get<T>() the interfaces you need in Start() of dynamically loaded objects to find their 'Registry/System/Manager'. Once they have that reference they call Hello()/Register() on the -System. In OnDestroy() they call Bye()/Unregister() on any -System they previously joined. Each -SystemComponent only registers with one -System and is owned by it. Multiple different components can sit on the same GameObject, each registering with different -System to create complex behaviour objects.
Hastebin is a free web-based pastebin service for storing and sharing text and code snippets with anyone. Get started now.
one last point, in your journey of discovery you will encounter non-performance ECS (architecture only,), Dependency Injection Containers (Zenject, VContainer) and a minimalistic Service Locator like the one i shared + the "no architecture, do what you want" approaches. the latter one is often called "every project needs to reinvent the wheel". You'll eventually land in one of these camps, and you may switch camps a couple of times. Typically the ServiceLocator is the best compromise that works for solo to small teams, and gracefully can adopt ECS and DI patterns when you actually need them. It does the most important first step: lets you depend on interfaces and is zero extra work to set up (vs Singleton)
"How many ways can we invent to move around pointers"
some false alternative is the message broker/bus/pipe, they solve local problems not the overall architectural foundation of your app. If you go down the DI container route. the MessageBus often becomes necessary as a global mechanism. But when you're at a point to actually need it, you are also running a cash-cow live service game.
What is it about using a DI container that necessitates a message bus?
if you go full DI and IoC, 'clean' communication across systems becomes very tedious, you have to constantly pass messages to the parent scope up to the root and then back down to the target. this target logic is PITA and messes up all the benefits of DI, a message bus is an attempt to put a strong contract around messaging and make it less painful. The problem with DI is generally that it creates a lot of overhead, has very opaque dependency resolution logic and is very easy to produce config bugs that only surface late at runtime (you loose a lot of static code analysis).
Overall DI is very dangerous because it so powerful. Used carelessly it causes more problems than it solves. Its very easy to turn it into an overengineered singleton god object hellhole. Also the whole pattern is NOT inherently suited to game projects with objects that have wildly different dynamic lifecycles. Each game-DI framework has its own idea how to gameify the pattern.
So, I've been doing web development for a couple decades. We use DI everywhere but it makes much more sense in that architecture. Gaming is so different...and so hard.
but in large teams and live-services it becomes a necessary evil due to high turnover, extreme need for protection of the codebase against careless and low-skill developers. It ultimately helps to make your code more pluggable and testable (if you can afford the overhead)
web stuff has entirely different needs/dynamics almost nothing from that world translates well to games.
haha...yeah, I've learnt that.