- Home /
Generate many (32k) GameObjects approach
Here is the project in question. There are a bunch of scripts, but the one we're concerned with is "GenerateMap.cs", it's in the Assets/Scripts folder.
https://github.com/mikezila/uQuake1
This project loads unmodified Quake 1 maps, and recreates them in Unity, just like it says on the tin. Parsing the maps themselves into C# objects is very fast. Even massive (by Quake 1 standards) maps are fully parsed into ready to use C# objects in less than a second. The issue is when it's time to make GameObjects for them.
Currently I'm making every surface in the map (called faces in .bsp lingo) into its own GameObject. On small deathmatch maps this results in about 3k objects, startup time of around a second, and 100fps. Great. On the massive maps, I end up with 32k GameObjects. It takes around 20 seconds to start, though performance isn't too bad once it's loaded, considering.
I'm not so concerned about the performance once the game is running, as I can implement the "vis" data from the .bsp and turn off the renderer on gameobjects that aren't visible, but how can I speed up the generation of the GameObjects? Or is there a better way?
Each object has the following:
A mesh filter and renderer
A mesh collider
A texture that is generated at runtime from the .bsp itself
In case you can't be bothered to look at the code, the basic process I'm using is this. Foreach face, create a gameobject, calculate the verts and tris for the mesh, calculate the uvs for the mesh, add components I need, add the verts, tris, and uvs to the mesh, recalculate normals, mark the object as static, done and onto the next face.
It's very simple, but with so many objects, it's slow to start up on the big maps. I don't have Unity Pro, so I can't use the profiler. Is there a valgrind/cachegrind type inspection I can perform somehow? Anyone have any tips on how I can speed this process up?
Can you serialize the data to avoid the conversion process?
A quick thought - is it really necessary to convert each face to a game object?
@$$anonymous$$hada The conversion (I assume you mean parsing the .bsp itself) is fast enough. I do need to do dotproducts to figure out my texture coords, but otherwise there aren't any major calculations left to be done once the .bsp is parsed. it's all just grabbing things (like Vector3s and Texture2Ds) from arrays. If you mean something else I'm not following.
@ArkaneX That's why I'm here. I don't need each face as it's own object. I'd like to find out if it's slow to startup because of the number of GameObjects, or because of the work to make them. Really the only thing I could do to consolidate objects is to make one object per leaf (think section) of the .bsp and have all the faces be submeshes. Not sure how I'd to texturing in that case, since textures are defined per-face. This would be the same amount of work, though. I'd still need to make one mesh per face because of how they're stored in the .bsp.
I don't know if going submeshes way will help, but if you want to check how long a specific portions of your code take, you can use System.Diagnostics.Stopwatch class.
I suggest to divide your code to more than one foreach (creating empty game objects, creating verts and tris, recalculating normals, etc.) and measure completion time of these steps.
After you see that one particular step takes a lot more, you can think on optimizing it. For example, if creating empty objects takes 50% of time, maybe it's a good idea to create a pool of these objects before user selects a map, and then just take prepared objects and continue with next steps.
Using that video and some elbow grease I converted all of my Lists to static length arrays, now when I do a release build the startup has gone from 2 and a half $$anonymous$$utes, to 2 and a half seconds. Video is great.
Answer by Loius · Oct 18, 2013 at 04:44 PM
Some of the slowest things you can do in unity are creating gameobjects and creating meshes. The more stuff you can do before creating an object or assigning a mesh to it, the better.
I'm not convinced that you have to have every face as its own gameobject. Objects can have multiple materials - I think that's controlled by submeshes. Pretty sure. Haven't really touched them apart from directly copying them, though.
What I'd do is keep a dictionary of < Texture, Mesh > for face-textures to sub-meshes:
if ( dictionary.ContainsKey(currentFaceTexture) ) {
targetSubmesh = dictionary[currentFaceTexture];
} else {
targetSubmesh = new Mesh();
dictionary.Add(currentFaceTexture, targetSubmesh);
}
targetSubmesh.AddVerticesAndUVsAndStuff(faceInfo); // this function probably doesn't exist
You'll probably do well to generate chunks rather than a single massive object (one chunk per leaf is probably a good size). So build all your submeshes (remembering that the vertices have to be offset by the face's position now, since they're not each their own gameobject), then assign them to your main mesh, then assign that mesh to the chunk's game object.
I did some testing with .Stopwatch, and the results are very odd. All results are in milliseconds. "Stalwart" is a smaller map, "Ivory" is a huge map. Stalwart results in ~1.3k objects, Ivory results in ~32k objects.
BSP Parsing (Includes generating Texture2Ds)
Stalwart = 21
Ivory = 249
Create dummy gameObjects (Totally empty)
Stalwart = 113
Ivory = 137707, 132010, 139036
Create actual gameObjects (Fully recreate the map)
Stalwart = 303
Ivory = 68481, 70708, 72564
Why is it more expensive to generate empty gameobjects than it is to generate the same number of objects while also doing all the work needed to fill them with a mesh, uvs, and etc.?
With numbers that huge, Unity sometimes gets into unstable territory. There's probably some weird memory stuff going on; I'm no expert. $$anonymous$$y best guess would be that the other stuff you're doing takes 'so long' to do that the weird memory stuff can settle down in between creating objects.