Hello, we have a project that use a lot of reusable structures, and found that Level instances was the thing that worked best for us in the beginning. However, as we go one, we have the need to pass some variables to the level instances, which was not as easy as I’d expected.
Additionally, when working on Level Instances, I’m also struggling with ensuring that the level is fully loaded. Optimally, I’d like to use an event like OnLevelShown, but I cannot seem to find a way to bind that event if I’m not dynamically spawning the instances. I guess polling IsLoaded is a solution, but not a pretty one.
Passing data down instances from “root→Parent Instance→ Child instance” seems rather difficult, so far the only way we have been able to access specific objects, is by comparing translations or other hacky solutions. This despite having the level instance reference, which I’d hoped I could access the internal actors from.
To me, this should be a simple use case, so I’m questioning if I’m just attacking this from the wrong angle. Does anyone have any experiences with working with Level instances?
Theres a way to load level instances where you obtain a sort of promise for it, i dont remember for sure, but im confident 70% that theres a delegate for loding finished.
Ive mode a plugin with tthat ill post it if i remember tomorrow.
Its not really a simple use case. The process of loading assets and initializing actors is rather complex and very lenghty. So by necesity everything is async, which is even more complex.
Passing parameters to level instances is really not what they are made for. I wonder if a big actor would be better. Or maybe some sort of manager on each level instance that holds soft refereces to actors.
Im struggling to imagine what are you trying to do.
You can get alr the actors in a level instance when working on cpp. By getting a ref to its world, then iterating the actors.
Though i feel you might have more succes fliping it around, and having a subsystem that provides the init data, and the actors query it. But you might have the same problem of beiing able to identify them.
In my level, I have a bunch of template structures that we reuse. Inside these structures, there are even more structures that we reuse. These leaf structures are a collection of actors that has some functionality. Up until now, these has been independent, so no problem. However, recently we have the need to put some unique labels on some of the structures inside the level instances.
In the spirit of giving a simple example, imagine one level instance as a neighborhood, which has more level instances of houses. The game has multiple identical neighborhoods. Now I need to give the houses an unique address.
The quick and dirty solution was to have a manager that passes the labels directly to the actors by comparing the positions. As you can imagine, this falls apart really fast when we make some changes.
I think you’re referring to OnLevelShown(), which I can get from the ULevelStreaming objects. I think that should trigger once that level is loaded. Definetly interesting.
In this context, I’m not sure what push/pull would refer to. I assume that I should try to push my string from my level manager down to my leaf blueprint/Level instance. Though, you could call this a pull, since the leaf is pulling the string from my manager .
However, my main issue is that I don’t know how to figure out which instance the actor came from. How do I know which actor I should push my string to?
I tried earlier to solve this with the level blueprint, but I have now realized that I have better access to StreamingLevels in C++. This could give me access to the OnLevelShown() delegate, which could solve the loading issue. Maybe I’ll find some way to sort my actors by levels there.
Edit: I suppose sorting the actors into Levels only solves it partially. I also need to know which object in the level it is. (House1, House2… etc.). It would be easy if I could use the display name, but that is being renamed into an unique name as far as I know.
thanks for the explanation. you were pretty clear actually.
as soon as you have actors that don’t behave genericly. generic structure are not ideal. that’s just a logical rule. so that’s the start of your issues. let me see what i can suggest.
imho i would use a manager to spawn those entities. specially if they are interactive.
these systems, i’m gonna mention, are very complex so quite possible they are overkill, but you might wanna look at pcg and mass system.
not ideal in my book.
unfortunately i can’t recall exactly. but i’m sure you’ll find it. it’s bound to have caveats too.
a quick search in ddg had this as a first result
which gives you a few alternatives. so it’s definitely easy to search. i do recommend to make a few searches.
push or pull refers to the direction of the intention, initiator, and data. if the manager tells the actors which data to use, it’s push. if the actor asks the manager, it’s pull. but since you have the issue detecting loads, a push is hard. so maybe pull is more natural. for things you can control, using push is preferable, for async things you end up using pull.
indeed. you should focus on that. which leads me to think that you might be approaching the problem in a not-ideal way.
i don’t think the levels solve much, but can be used as help.
yeah, it’s unfortunate that the actor label gets stripped on builds as it’s pretty useful. i was forced to implement my own label. i think that might be a good option for you. these objects of you have enough special behavior to make a base class a reasonable option.
in my game i have tons of interactives. and they use their labels to get the data they need. or to notify their events. but i also have other means.
the problem is that the label will be unique in the level, but not across the instances, so you might need to use LevelName + ActorLabel to get an unique id.
another option is to use something like custom primitive data, maybe you can store an id. but it’s going to be the same issue with uniqueness.
level streaming is one of those things that is poorly exposed to bps. in cpp you’ll get much more options.
like this.
LevelStreamingDynamic* const Stream = ULevelStreamingDynamic::LoadLevelInstanceBySoftObjectPtr(
O, Level, Location, Rotator, LoadOk);
beware that Stream→SetShouldBeVisible determines if the level is considered fully loaded and blocks beginplay on its actors or smth. so it has caveats.
that last param allows you to subscribe to an onload delegate
i recommend to take a bit of time to learn the diff between streaming levels and instanced levels because the naming is a bit confusing at first. they end up using similar code in the background though. so global delegates might work with both.
i really think there should be a better way but i haven’t thought of one. having that level of interaction in a level instance sounds not ideal to me.
i’m inclined to have a manager that spawns these groups you want. and try to compartmentalize these groups in bps somehow. maybe have the bps spawn the children they need. you can use childactors for that too.
then have a data table, structure, or helper actors (e.g. aInfo actors that work as markers which you then do a getallactorsofclass and spawn. or you can make the marker actor self transmute into a new instance of such bp (eg spawn and destroy self)).
you can either have the manager tell these markers what to spawn. or you can set the data on these lightway markers. you can use a data table and store a name. or use data assets and store a (soft) ref.
a manager and dynamic spawn will also make it easier to optimize and dynamically load (like the mass system does for example).
but it will have some impact in your workflow. so it’s worth trying, testing, and consider. both will. your approach right now will limit your design. so if your team really wants to do weird things, it’s going to be a constant argument with the team and everyone will be unhappy, it happens too often. on the other hand, it might be the simpler to implement and maintain, if the design doesn’t change.
This was the hardest part tbh, but I was inspired by your customed Labels, and ended up using Tags for that same reason. I set Tag = Display_Name for every Level Instance. This way I could manage the structures in a given sublevel. Now I just needed to build a hierarchy of the Level Instances. I did this by using the pattern World→PersistentLevel→Actors→Loop(Find all LevelInstances)→GetTag→GetLoadedLevel→Actors→ …Loop until all level instances are mapped. However, GetLoadedLevel is dangerous to run without ensuring the level is done loading.
I end up with a folder like TMap<TArray<FString>, ALevelInstance*>. Assuming everything is loaded, I can access any Level through map[{“Neighbouthood1”,”House4”}]→GetLoadedLevel→Actors. This way, I know which Level Instance each of the structures came from, as I can access the specific level context.
One issue down.
Ensuring level is loaded
I found the hook FWorldDelegate::FOnLevelChanged. This has the constructor OnLevelAddedToWorld(ULevel* InLevel, UWorld* InWorld.
Getting the ULevel reference is really important, as it lets me search through the Level Instances that I haven’t processed yet to match the Level this hook completed for. Once I have matched the level with my existing reference, I can bring over the tags, and continue scanning the level.
When no more levels are loading, I can start processing knowing everything is loaded.