- Home /
Combining prefabs into one game object before calling Instantiation
I want to write a class that puts a bunch of GameObject prefabs (walls, floors, windows, furniture, etc) into a random house-shaped Game Object and return that object to the caller. Once the caller has this single GameObject, it can Instantiate() it into being.
I know I can use this code:
SomeHousePiece.transform.parent = ParentObject.transform;
to join objects together, but since I'm trying to put this all together BEFORE Instantiation, I get this error:
Setting the parent of a transform which resides in a prefab is disabled to prevent data corruption.
I've read that this is because I need to call Instantiate() on all these components before I can group them under a single parent, but I really want to avoid calling Instantiate() in the House builder class. I want that class to be purely a thing that generates a Game Object for later use.
Is this possible? I have an impression that I'm fighting against the "Unity way" of doing things, but as a long-time C# developer, there are habits (like separating construction logic from display logic) that are hard to break.
Should I just call Instantiate() from within my Builder class and be done with it?
Answer by Owen-Reynolds · May 01, 2014 at 05:00 PM
I prefer keeping prefabs as invariants, so even if you could change the parent, it seems like a bad idea.
One thing you could do is create fake prefabs. Instantiate will gladly copy prefabs or existing gameObjects. So HouseMaker could Instantiate, position, child... everything, but into an object at -999 set to inactive. Then could return that as the "prefab." And, to repeat, later code will gladly Instantiate that.
Or, a more programmery way, and more what you're asking for, have the HouseMaker create and return a HouseBlueprint, which would be used to Instantiate the house. Your main program will never call Instantiate. Instead, it will call Transform H1 = housePattern1.spawn();
HouseBlueprint might look something like:
class HouseBlueprint {
Transform myPrefab;
Vector3 myLocalPos, myLocalRotation, myLocalScale;
HouseBluePrint[] Children;
// recursively instantiates, hooks up, positions all PFs that make me:
Transform spawn() { return _spawn(null); }
Transform _spawn(Transform myParent) {
Transform T = Instantiate(myPrefab, 0 ,0);
if(myParent!=null) {
T.parent = myParent;
T.localPosition = ...
}
...
foreach(HouseBlueprint b2 in Children)
_spawn(b2, T);
return T;
}
Can think of it as a standard recursive data structure, with an eval() function. The root should probably always be a "positioning" empty, which also solves the problem deciding the parent if you just have 4 walls. I use this trick -- I think it really is the correct solution, but it is very Computer Sciencey.
Okay, that's kind of what I was thinking I might have to do (Instantiate fully, but off-screen. The caller handles moving the object into frame). It still doesn't feel right, but I guess it's analogous to calling "new Object()" -- in code, "new" makes stuff appear in memory, in Unity Instantiate() makes stuff appear in the game world.
A few follow-up questions: 1. Is there a reason to return a Transform rather than a GameObject? Is this just good practice? 2. What's the cleanest way to give a class access to the Initiate() method? I'd rather not have RoomBuilder extend $$anonymous$$onoBehaviour since I don't consider it a behavior and it already extends an abstract class. Do I then store an instance of $$anonymous$$onoBehaviour in the builder?
Well, the first idea, I'm assu$$anonymous$$g you want to build "funnyRedHouse" then make arbitrarily many copies. So there are two Instantiates. One is done once, ever, to setup funnyRedHouse, making a fake offscreen prefab. The then the caller can Instantiate that exactly as if it was a real prefab. The caller can treat everything as normal prefabs, nice and clean.
I just prefer to throw around Transforms, and prefer writing T.position
to GO.transform.position
. Any class can Instantiate (need GameObject.Instantiate
for non-$$anonymous$$onoB's?) The tricky part is getting links to prefabs. Seems best to make a monobehaviour "holder" class, on an empty, just to keep all the prefabs, materials, user-set constants... , which might be what you're suggestion.
Oh, I see. So the house just sits off screen and serves as a template for further creation. $$anonymous$$akes sense.
I, too, am irritated by writing GO.transform.position, but when I'm designing classes, I can't help but feel like I should have access to the whole GO. I guess that's what transform.gameObject is for?
GameObject.Instantiate() syntax is exactly what I needed. For some reason I couldn't find a statically available Instantiate() method; the only way I'd found was to extend $$anonymous$$onoB.
So, just for the sake of completeness, you would say there is not a way to join together multiple game objects prior to any Instantiate() calls? At least, none that you know of?