Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "StreamingScene" node and associated level streaming editor tools #2889

Open
NineAlexT opened this issue Jun 19, 2021 · 4 comments
Open

Comments

@NineAlexT
Copy link

Describe the project you are working on

A Metroid Prime/Zelda like 3rd person action game

Describe the problem or limitation you are having in your project

Worlds are made up of many rooms that streamed in dynamically. Editing those rooms and ensuring they fit together properly with no holes and no overlap is both time consuming and difficult. Ensuring baked lighting is consistent across rooms is also not currently possible, and shoving all the rooms into one file is untenable due to memory use.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

A new "StreamingScene" node that inherits from Node, and a set of editor tools for working with it.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

StreamingScene Node

The StreamingScene node would have to have at least 3 properties, maybe more in the future, a reference to a PackedScene that it will load asynchronously, a "Start loaded" boolean that would ensure the scene starts streamed in, and an optional Mesh to be used as a proxy when scene is unloaded, though this feature would only work for 3D games and I don't believe 2D games would need an equivalent feature.

Internally the node will need and enumerator to store it's current state, UNLOADED, LOADING, LOADED, and possibly ERROR for if something goes wrong. In the UNLOADED and LOADING states the node would just have an attached MeshInstance that renders the proxy geometry. The LOADING state could be used by the developer to prevent the player from going into an area that isn't loaded yet, for instance by preventing a door from opening until it finishes, or in drastic cases pausing the game entirely while it finishes. Finally, in the LOADED state the proxy geometry will be hidden and the streamed scene will be visible.

The nodes state would be changed by calling .load_scene() and .unload_scene() from any GDScript, and it would be up to users to decide where. Most would probably use Areas, while fully open world games might make their game in a tiled fashion, and load based on player location. The status of the node could be checked by calling .is_loaded() which would return a boolean. Once a scene is loaded it would be added as a child of this node, and when it's unloaded all children would be disposed of. This behavior would be important to inform users of in documentation to ensure they are not caught off guard when their player or an enemy de-spawns when the scene they placed it in is unloaded.

Editor Tools

Along with this there should be some associated editor tools and changes to facilitate working with this node. This where this enhancement necessitates being core. Typical use of this node will be to have a main scene that acts as a persistent level. This scene would have multiple StreamingScene nodes as it's children as well as anything the user want to keep loaded at all times. When a scene is first loaded it should load the proxy geometry for all StreamingScene nodes and add it as it's child except for the ones with "Start loaded" checked for whom it should load the full scene and add that as it's child. All children of these nodes should be locked from being edited by default, and this should be indicated in some way, like by greying out the text for instance, and it's children should also not be selectable in the viewport. When the user has a StreamingScene selected a new Streaming tool should appear in the tool bar. This tool should have the following buttons "Load Scene", "Unload Scene", and "Make Current Scene". Load and unload scene are self explanatory and are there for the user to manage editor performance. "Make Current Scene" should be greyed out for unloaded levels, and for loaded levels it should make it's children editable. The "current" scene should be highlighted in the scene tree, and it should behave as if it is in every way a native part of the parent scene, not like an external.

Only one StreamingScene should be editable, or "current", at time to prevent users from unintentionally placing objects in the wrong scene. When a user attempts to unload a modified scene with the tools, the changes should either be cached on disk for when the user saves or attempts to load the scene again, or the user should be warned about loosing changes to the modified scene. When a scene with StreamingScene nodes is saved, unlike with "editable children", all changes to the children should be saved in their respective scene files, and not in the parent scene that is being saved. If any scenes are unloaded, but have changes cached on disk they should also be saved to their respective scene files. If on an attempted save the user has more than 2-3 of these nodes in a scene with "Start loaded" checked they should be given a warning that it may impact performance or even crash the editor next time they open the scene. Nesting of StreamingScenes should be highly discouraged, if not completely forbidden, and should throw an error as there is little to no reason to do it and it creates a risk of circular dependencies.

Additional Changes

The generation of proxy meshes is outside the scope of this proposal, but one of two changes to the new Export GLTF feature would make it easier for users to do so manually if this proposal was implemented. Option one is to split if into two buttons, "Export Scene" and "Export Selected" like in Unreal Engine. Option two is to add an tick box in the file dialogue to export selected only like in blender. Either way I recommend moving it from Project>Tools>... to Scene>... with all the saving/loading option where it will be more visible and to me at least, make more sense. This would allow the user to select all the nodes in a StreamingScene, export them, then use boolean, remeshing, mesh decimation, baking, and other tools in an external program to create the optional proxy mesh.

Finally, changes to light baking. Ensuring that lighting is seamless across multiple steamed scenes is crucial, especially for games that have long flowing levels and can't disguise the seams with doors or other geometry, for instance fps games. While this should mostly be a non-issue for SDFGI, and SSGI, for baked lighting this presents a unique challenge. My recommended solution is to expect the user to place a LightmapGI node each StreamingScene, have it detect that it's inside a streamed scene, and include all geometry external to the scene inside of it's bounds in the bake, but not the lightmap. This would ensure the lightmaps are as close to seamless as possible without needing to include all the geometry in all the StreamingScene nodes. For the auto-placement of lightmap probes it may be a good idea to have it auto limit to the bounding box of the actual scene itself. A LightmapGI places as a sibling to a group of StreamingScene nodes would bake lighting for the proxy geometry of those node, and any geometry that is outside all of the StreamingScenes.

If this enhancement will not be used often, can it be worked around with a few lines of script?

No, as far as I know there is no way to change how scenes are saved and loaded in editor or worked with to this extent, or to include geometry in a LightmapGI that is not in the lightmap, but is included in the calculation of global illumination.

Is there a reason why this should be core and not an add-on in the asset library?

Level streaming is an important tool in many genre of modern games to increase level size while ensuring a seamless player experience. It is used in games with long linear levels, hub-world based games, and open world games. Lastly as stated above this is not possible to do as a plugin with current API as far as I can tell, and must be core.

@Xrayez
Copy link
Contributor

Xrayez commented Jun 19, 2021

Looks like there's an existing proposal for this: #1197.

@NineAlexT
Copy link
Author

NineAlexT commented Jun 19, 2021

I did see that, but I thought it seemed really vague, with no description for how this would work in Godot, and focused too much on open world games alone and not a generalized solution to the problem. Also it had discussion had seemed to have stalled out, and it was tagged as core, and rendering when this proposal is mostly about editor. If this is felt to be to similar I can always put a summary of the main body of this proposal in a comment there instead?

@YuriSizov
Copy link
Contributor

It's fine to have a specific alternative proposal, even if there is a related one already.

@mthreis
Copy link

mthreis commented Jun 21, 2021

To me, some of this functionality should come built into regular Nodes. I see that some changes are expected to be made to how pausing works and since it's going to be per-node, Godot should allow you to mess with any child of a disabled node asynchronously because the Tree is ignoring that branch of nodes entirely.

At the moment, it's possible to both load and instance nodes async but can't you add them to the scene. If the root object is disabled, it'd be nice if godot allowed that workflow, especially if there was a _on_load or _on_before_ready method for setup:

  1. Load and instance a scene asynchronously;
  2. Add them, disabled, to the scene async and do any pre _ready setup;
  3. As soon as it's done setting things up, notify the Tree, which will activate that node in the upcoming frame.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants