- Home /
Is there a more efficient way to assign parent object for many instantiated prefabs?
In the spirit of 7DRL, I'm creating a little roguelike game. I'm instantiating 3D "tile" representation of my randomly generated map and so far there is only 2 types of tiles: floor (0), and wall (everything else). In code below, I'm creating a container object for all these level "tiles" and would like to assign it as a parent for my instantiated level geometry objects.
I have narrowed down the problem to be the line that is commented out in the code. Everything is fine, until I assign a parent to those objects. With that line commented, a normal 100x100 tile map loads in under 1 second, but when I uncomment the line to make it assign the parent object it takes about 15 seconds longer. The reason I'd like to assign parent for these objects is not to have my object hierarchy tab be cluttered by thousands of objects while I run and debug my game within Unity. Is parenting objects just this resource intensive or am I doing something inefficiently? How can I improve the performance while still having non-cluttered hierarchy view in Unity?
private void LoadLevel(DungeonLevel level) {
// TileObjects is an ArrayList of tile data
GameObject LevelContainer = new GameObject("LevelContainer");
Object obj;
List<GameObject> TileInstances = new List<GameObject>();
foreach (KeyValuePair<LevelTileLocation, LevelTile> entry in level.GetTiles()) {
if (entry.Value.TileType == 0) {
// Floor
obj = Instantiate(TileObjects[entry.Value.TileType] as GameObject, new Vector3(entry.Key.y + 0.5f, -0.5f, -entry.Key.x - 0.5f), new Quaternion(0, 0, 0, 0));
} else {
// Wall
obj = Instantiate(TileObjects[entry.Value.TileType] as GameObject, new Vector3(entry.Key.y + 0.5f, 0.5f, -entry.Key.x - 0.5f), new Quaternion(0, 0, 0, 0));
}
TileInstances.Add(obj as GameObject);
}
foreach (GameObject item in TileInstances) {
//item.transform.parent = LevelContainer.transform;
}
}
Added information after more experiments:
This only happens inside Unity. If I "Build & Run" the game there is no big delay loading the level.
Reducing map size from 100x100 to 30x30 doesn't cause the big delay anymore while assigning parent to objects.
I attached some code to trying to find out exact times. Here's some data and the script modified to time the delay:
Average results over 10 tests:
100x100 map (10000 objects) without assigning parent to objects: ~340ms
100x100 map (10000 objects) with assigning parent to objects: ~16700ms
30x30 map (900 objects) without assigning parent to objects: ~38ms
30x30 map (900 objects) with assigning parent to objects: ~204ms
Modified script to measure the delay:
private void LoadLevel(DungeonLevel level) {
int count = 0;
Debug.Log("--- Starting to load level ---");
System.DateTime start = System.DateTime.Now;
Transform LevelContainer = (new GameObject("LevelContainer")).transform;
Object obj;
foreach (KeyValuePair<LevelTileLocation, LevelTile> entry in level.GetTiles()) {
if (entry.Value.TileType == 0) {
obj = Instantiate(TileObjects[entry.Value.TileType] as GameObject, new Vector3(entry.Key.y + 0.5f, -0.5f, -entry.Key.x - 0.5f), new Quaternion(0, 0, 0, 0));
} else {
obj = Instantiate(TileObjects[entry.Value.TileType] as GameObject, new Vector3(entry.Key.y + 0.5f, 0.5f, -entry.Key.x - 0.5f), new Quaternion(0, 0, 0, 0));
}
//(obj as GameObject).transform.parent = LevelContainer;
count++;
}
System.DateTime stop = System.DateTime.Now;
Debug.Log("Done loading " + count + " level geometry objects in " + (stop - start).TotalMilliseconds + "ms.");
}
Problem is not so noticeable with 30x30 map size but timed test data would seem to indicate that the problem is still there. I think it's safe to say that major part of the delay is caused by assigning a parent to the objects.
don't do it separately man - do it in the loop.
consider this long post ...
http://answers.unity3d.com/questions/321762/how-to-assign-variable-to-a-prefabs-child.html
so move line s19 to line 16 and delete the foreach loop at 18.
"only happens inside Unity": there are a lot of little staggers running in the editor. It's got to maintain all the other windows, extra debug info, keep the display list of all gameObjects... .
If it really is only in the editor, don't worry about it. You might just run most of our tests with a smaller map.
For now, I've succumbed into running my tests with smaller maps. I really do hope though, that I can find a solution to be able to eventually run my tests using the original 100x100 map size and all level geometry arranged as child objects under one GameObject (LevelContainer) without the performance hit.
Answer by Owen-Reynolds · Mar 10, 2013 at 04:18 PM
Sounds like the operating system is having trouble with memory management (after it makes those 10,000 items, it stores them on disk or something.) Maybe assign in the loop :`obi.transform.parent=...` while the object is sill "on deck."
Then the standard advice is also to grab a reference: Transform levT = LevelContainer.transform;
(but that wasn't your slowdown.)
[edit, new from here:] Second guess: The usual way to make something is by Instantiating a prefab. new GameObject
seems to work, but the Transform may not be properly "set in" until next frame, say.
Maybe try spawning LevelContainer from an empty prefab (and you may want to later add a layer to it, start with a small script on it... , which is easier with a prefab anyway.)
[even newer edit]:
Third idea: it may be an issue with inserting into a size 10,000 list -- the Inspector may not be optimized for that. Maybe, first making a child for each row would help. Then child each item to the row: obj.go.parent = levelBase.go.Find("row"+myRow).go;
.
Sorry, I forgot to mention my first attempt was to assign the parent in the loop. I only moved it out while I was trying to isolate the problem. Original code was as follows:
private void LoadLevel(DungeonLevel level) {
GameObject LevelContainer = new GameObject("LevelContainer");
Object obj;
foreach ($$anonymous$$eyValuePair<LevelTileLocation, LevelTile> entry in level.GetTiles()) {
if (entry.Value.TileType == 0) {
obj = Instantiate(TileObjects[entry.Value.TileType] as GameObject, new Vector3(entry.$$anonymous$$ey.y + 0.5f, -0.5f, -entry.$$anonymous$$ey.x - 0.5f), new Quaternion(0, 0, 0, 0));
} else {
obj = Instantiate(TileObjects[entry.Value.TileType] as GameObject, new Vector3(entry.$$anonymous$$ey.y + 0.5f, 0.5f, -entry.$$anonymous$$ey.x - 0.5f), new Quaternion(0, 0, 0, 0));
}
(obj as GameObject).transform.parent = LevelContainer.transform;
}
}
Is this what you meant with assigning in the loop? Odd thing is, while looking at Task manager and system resources the memory, CPU and disk use is constant (memory 50%, CPU 30% and Disk around 0%) during this (memory is that high only because of some other programs running in the background, it's not just Unity). It's roughly the same during the actual gameplay when loading is done. In Task manager, the process status shows up as "Not responding" for the duration of the loading, but it should have resources to spare for that if needed.
Yep -- that was what I was thinking for "in the loop."
malaporop (clever name!) .. just look at the 90,000 word essay I linked to above on the topic. it includes full code for doing this. Also, FWIW you could just buy a Pool kit from the app store.
Answer by Bunny83 · Mar 10, 2013 at 06:22 PM
The only optimisation i would suggest is to use Transform references instead of GameObject references. That way Unity doesn't need to find the transform of each object. My version of your function / script would look like this:
public Transform[] TileObjects;
private void LoadLevel(DungeonLevel level)
{
Transform LevelContainer = (new GameObject("LevelContainer")).transform;
foreach (KeyValuePair<LevelTileLocation, LevelTile> entry in level.GetTiles())
{
Transform obj;
if (entry.Value.TileType == 0)
{
obj = (Transform)Instantiate(TileObjects[entry.Value.TileType], new Vector3(entry.Key.y + 0.5f, -0.5f, -entry.Key.x - 0.5f), new Quaternion(0, 0, 0, 0));
}
else
{
obj = (Transform)Instantiate(TileObjects[entry.Value.TileType], new Vector3(entry.Key.y + 0.5f, 0.5f, -entry.Key.x - 0.5f), new Quaternion(0, 0, 0, 0));
}
obj.parent = LevelContainer;
}
}
Keep in mind when you change the type from GameObject to Transform you have to reassign all prefabs to the TileObjects array / List
I think I've isolated the problem being mainly about assigning parent to the objects. I've now posted timed test data to the original post to show this. What you say is true, and thanks for the optimization suggestion, but I don't think it'd get rid of the major delay caused by assigning parent to the objects which seems to be the major issue. This is the issue that continues to baffle me.
hi @$$anonymous$$alapropos.
I have not totally followed this conversation so I may be repeating something that has already been discussed:
Why are you using an object and casting it to a gameobject?
your code would just look like this. recall that you must keep a List of all the different aspects of the items.
List ggs
List tts
List sss
List sos
List rrs
List ccs
var gg gameObject .. make each new instantiate here
var tt transform
var ss SomeScript
var so SomeOtherScript
var rr Renderer
var cc collider
for i = 1 to 500
{
gg = Instantiate ( your model thingy )
tt = gg.transform;
ss = gg.GetComponent(SomeScript)
so = gg.GetComponent(SomeOtherScript)
rr = gg.rigidbody
rr = gg.renderer
cc = gg.collider
ss.someSetUpRoutine(13.2);
tt.position = whatever
tt.parent = whatever
ggs.Add(gg)
tts .Add(tt)
sss.Add(ss)
sos.Add(so)
rrs.Add(rr)
ccs.Add(cc)
}
during game play use like this
ggg[14] is your game object for 14 sss[14] is the script for 14
Hi Fattie, thanks for your input. To answer your question, I'm casting Object to GameObject for the purpose of getting access to it's transform component. I don't think that is within the scope of my current problem, though. $$anonymous$$y issue is with the weird overhead of assigning a parent to my objects, not that I can't access all the different components of my prefabs.
I did read your write-up about pools of objects and how to access their components. I think I understood it and it really helped me with another problem I was going to face. I now know how to solve it, thanks to you. It was a good read! With that said, did you look at my timed test data? I think I've isolated the bottleneck where the issue is. I don't really understand how would treating my level geometry as pool of objects improve the performance of parenting them under one GameObject? Could you give me a little bit more theory on why the performance would be better? Thanks for all your help so far.
hi malap,
"I'm casting Object to GameObject for the purpose of getting access to it's transform component"
O$$anonymous$$, FWIW, what I meant wasL I suggest in fact do not use "object"
generally never use "object' in Unity.
just make a variable that is a GameObject, and use that.
So you don't have to cast.
If I missed some specific reason you are using "object", again sorry, but for the purpose of testing and so on change to the let's say usual Unity idiom:
Instantiate in to a game object. do not use any variables that are "object".
Regarding the ti$$anonymous$$g. it's great you posted the data and thanks for that.
Could I suggest. Do the same thing actually running on a device. (not tethered to your $$anonymous$$ac, not running XCode, not running Unity. just running on an iPad. Oh and don't forget NOT a so-called "Development" checkbox build in Unity.)
have it bring up the data on the screen (just use some crappy gui text or whatever) the ti$$anonymous$$g figures.
notice too that in Bunny's example which I just saw he uses a Transform as the variable that you instantiate in to.
so again i suggest change your code and do not (ever! never do it! :) ) use an "object" to instantiate in to. instantiate in to either a GO or a T.
you ask why do I suggest this? when there is a bizarre general problem like this. everyone has to get on the "same page". it's best if everyone uses the same usual Unity idiom before experimenting too much.
What we don't want is any iconoclasts or individuals :) it's like in monty pyhton ... "we are all individuals..."
so, it needs to be tested on an ipad (careful not check Development build) bring up your results with text on screen
secondly it is "more difficult to get to the heart of the matter" if you are "off idiom". it's confusing that you use an object. change to Transform or GameObject so as to be like everyone else
Well, it doesn't make a real difference. You're right that usually it makes no sense to use a reference of type object, but Instantiate always returns an UnityEngine.Object, so you have to cast it anyways. However usually you cast it right after the Instantiate so you only cast it once.
Your answer
![](https://koobas.hobune.stream/wayback/20220613094144im_/https://answers.unity.com/themes/thub/images/avi.jpg)