Search Unity

Creating prefabs from classes, rather than using the Prefab -> Component hierarchy

Discussion in 'Getting Started' started by viveleroi, Jan 4, 2017.

  1. viveleroi

    viveleroi

    Joined:
    Jan 2, 2017
    Posts:
    91
    Can I programmatically generate and instantiate a prefab from inside a class, instead of having my class always be a component on the prefab?

    It seems like Unity expects everything to be a subcomponent to something in the game hierarchy.

    As an example, if I want to create a dozen "items" the player might find in treasure chests, I need to create a prefab and add that item's class as a component. This really becomes a problem for any code that passes Item(s) around - it has to actually pass GameObjects around.

    If a user "picks up" an item, I used to use a method like this:

    public void addItem(Item item) { ... }

    But with Unity, I can't pass the Item without losing access to the sprite/prefab, so I have to get verbose:

    public void addItem(GameObject object) {
    var item = object.GetComponent<Item>();
    }

    This just seems a lot of extra work. I don't want to create a prefab for every single item, I don't want everything my code to be based on GameObjects.

    I hope I'm missing something simple or there are some better Unity-specific patterns I should use.
     
  2. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,524
    If you pass the component, you can get the gameobject via the 'item.gameObject' field. Either way, you'll be doing a 'GetComponent<Item>()' somewhere because if it is a worldspace 'item' then its going to have a GameObject displaying it which is how pretty much everything is going to be interfacing. The key is just minimizing how many times you do this. Do it up front, then pass the item component and if you need the gameobject then every component has a '.gameObject' field you can use.
     
  3. viveleroi

    viveleroi

    Joined:
    Jan 2, 2017
    Posts:
    91
    Ok thanks. I'll try that now - I was thinking about manually adding a gameObject type field but wasn't sure how unity would handle it, it felt like a circular reference which can sometimes cause problems.

    Also, how does this work for prefabs that haven't been spawned? That's mainly my issue right now.

    I want make a List<Item> to describe what a treasure chest holds, but those are all prefabs that haven't been spawned yet.
     
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    A prefab? Well, yes, if your script is running within the editor. At runtime, this question doesn't even make sense. I think the word "prefab" doesn't mean what you think it means.

    What I think you really mean to ask is: can you create a GameObject from scratch at runtime, and the answer is yes. Just do obj = new GameObject(), and then obj.AddComponent<type>() to add whatever components you need.

    Yep, that's the basic idea.

    No, you need to create a GameObject. You don't need to create a prefab.

    No it doesn't. You can pass around references to the individual components (and always get to the game object, if you ever need it, via the .gameObject property.)

    Not true. Pass the Item, if you like. You still have access to the GameObject via .gameObject.

    You're not creating prefabs, and as for your code being based on GameObjects, well, that's a good thing. It's the component-based architecture on which Unity is built. But the inconvenience you imagined does not exist.

    What do you mean? You mean references to prefabs in your project? It works the same way, but you have to be careful not to modify those objects, since they live in your project and not in the scene. Usually all you do with prefab references is Instantiate them.

    OK. I don't see the problem.

    I think you were missing the big idea here: every Component (including any MonoBehaviour subclasses you write) lives on a GameObject, and you can easily get from the component to the game object, or from a game object to its components.
     
    Marbelous likes this.
  5. viveleroi

    viveleroi

    Joined:
    Jan 2, 2017
    Posts:
    91
    Honestly the only reason I have the prefab is so I can get access to a sprite - in some cases I've assigned the sprite to a public property, I just haven't seen an easier way to get a sprite reference when I need to generate one.

    LaneFox mentioned the gameObject property which works for me, I can get by with it. I think my problem is really the paradigm shift that the code is a child of the gameobject, but it just gets weird when I have something that really isn't a gameobject, or at least isn't one all the time.

    I've been reading more on the prefab today and it probably is unnecessary for my current needs, but as I mentioned I need a way to get a sprites reference (for sprites used when generating the tile map, when decorating it, etc). I don't want any of these in my game scene until I need them, which is why I use prefabs.

    Edit:

    So, here's an example of what I've encountered. There's likely room for improvement, but given what I've learned so far, seems like the only solution.

    I'll need a way to "spawn" a new item into the game. It can be from anywhere in my code so I don't want to associate GameObjects with each one, meaning I need a sort of "directory" object/class.

    That's easy enough to implement since I've done it a few times. Problem is, that means the code needed to access an item becomes:

    var gameObject = GameObject.Find("ItemController");
    var itemCtrl = gameObject.GetComponent<ItemController>();
    var itemGameObject = itemCtrl.items[0]; // this should expanded to use ENUMs, so this will become a find() call
    var item = itemGameObject.GetComponent<Item>();

    I can abstract the last two lines into a "itemCtrl.getItem" method, and possibly inline the first two calls depending on their performance, but compared to how I'd do this outside of unity, it's verbose.

    I'd appreciate any suggestions you might have.
     
    Last edited: Jan 5, 2017
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Code tags first ;)

    So if I grasp you correctly, you want to have a list of all of the items in a chest or similar, without actually going ahead and creating all of the GameObject?

    There are several ways to approach this. But at the heart of the matter you are asking for the data about an item to exist without the item existing.

    Code (CSharp):
    1. public class ItemData {
    2.     public string name;
    3.     public float someRandomProperties;
    4. }
    You can then create a set of random ItemData classes and use those as your template to Instantiate GameObjects later.
     
  7. viveleroi

    viveleroi

    Joined:
    Jan 2, 2017
    Posts:
    91
    Thanks, I didn't see toolbar buttons for the code formatting but realized in another thread I could do it with bbcode.

    If Unity requires a GameObject, then I guess I am, so I'll live with it. It's not a huge issue now that I understand things a bit better, but there will be a bit more code needed to create new instances than I'd like. I'm sure after some time I can clean stuff up and streamline my code for my needs.

    I think the real answer is that my paradigm just doesn't match Unity's. Because a GameObject is essentially a wrapper for any actual code I may write, there's just an extra layer that feels backwards and in the way to me.

    In Java, Objective-C, and Javascript game work I've done, both on my own projects and third party libs, it's usually reversed. For example it might be:

    Code (csharp):
    1.  
    2. class Rock extends Entity {
    3.    public final Image sprite = Assets.getSpriteByName("rock.png");
    4. }
    5.  
    In this context, I can call every rock a Rock, I can store the objects without having to assume they're rendered, and creating a new instance is as clear as saying "new Rock();".

    The gameObject property everyone's mentioned does help, it means I can pass around my classes rather than the GameObject directly.
     
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    It doesn't. Not until you actually want to render an object on screen. You can handle everything by throwing around vanilla C# classes. Then only create a GameObject when you need the call backs or to render something.

    Many of my games live primarily in data structures without explicit GameObjects.
     
    Schneider21 and Ryiah like this.
  9. Schneider21

    Schneider21

    Joined:
    Feb 6, 2014
    Posts:
    3,512
    Exactly. Unity has its way of managing the resource pipeline and rendering content to the screen and everything, but there's nothing stopping you from doing things in a more traditional way. I know people like @GarBenjamin, for example, almost entirely ignore the Unity workflow and do things the "old fashioned" way.

    So you have an Item class, and you have a list of instances of that class. You can do whatever you want with those, and like others have said, not worry about the Unity implementation until you render them to the screen. One way to manage this might be to have a property of the class keep a reference to the GameObject.
    Code (CSharp):
    1. private GameObject gameObject;
    When you instantiate the object, save the reference to it for handling later.
    Code (CSharp):
    1. gameObject = new GameObject();
    2. gameObject.AddComponent<Item> ();
    Since it's a sprite you're showing here, add whatever other component types you'll need, optionally saving a reference to them as well to change later.
    Code (CSharp):
    1. SpriteRenderer spriteRenderer = gameObject.AddComponent<SpriteRenderer>();
    2. spriteRenderer.material.mainTexture = myObjectsStoredTexture;
    3.  
    Bear in mind you don't have to save references to other components as long as you can get to the GameObject. You can always call:
    Code (CSharp):
    1. gameObject.GetComponent<SpriteRenderer>()
    later, but my understanding is this should be done at a minimum to avoid unnecessary searching all the time.
     
    Last edited: Jan 5, 2017
    Ryiah and Kiwasi like this.
  10. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,524
    The problem is you're mixed up between your item data structure and your implementation of that data structure.

    Your list of items is not something you can spawn into a playable game, it's literally just a list of scripts. There is no implementation of it to use. What you might want is a list of prefabs (gameObjects in the project folders) and use linq to search the list for a prefab with name of xyz, then spawn that prefab into the scene and use GetComponent<>() to grab the item data component on the prefab you just instantiated.

    Another option might be to have a template prefab for all items, then assign its data after you spawn the template into the world. For example have a list of scriptableobjects as your database, use a linq find on the db to grab the item you want, spawn the generic template prefab into the world, then assign the data to the item component on the instanced gameobject and initialize it.

    But again, the point is you have to create implementation of the data structure you've created, you can't just spawn scripts into the world.
     
  11. Bill_Martini

    Bill_Martini

    Joined:
    Apr 19, 2016
    Posts:
    445
    I'd like to add, when I first started using Unity, I tried to use or make Unity work the same way as the previous development package I was using. This of course led to failure. Every development system as their own 'zen' and you must go with the flow or spend your whole time fighting the current.

    Never underestimate the power of arrays. Make a class to reference all items you need a reference to. And add an array for each type you need. Fill in what you need in the inspector to populate the fields. And now you have a reference to all the items you need in game.

    Code (csharp):
    1.  
    2. public GameObject [] myScenery;
    3. public GameObject [] myPrefabs;
    4. public Sprites [] mySprites;
    5.  
    To use...

    Code (csharp):
    1.  
    2. // old fashion sprite animation
    3. myScenery [7].GetComponent<SpriteRenderer>().sprite = mySprites [thisFrame]
    4.  
    If you are accessing anything often then create a reference to the component in Awake or Start and avoid the lookup each time. I use this all the time and don't need to pass objects in function parameters. The downside is you loose all identification of what myScenery [192] is and I have to comment what that item is each time I use an array element. But that's a small price to pay for the connivence of having access to everything.
     
    SPEAKERBUG and JoeStrout like this.
  12. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    If you find yourself directly referencing particular entries in your array by magic number, I might offer two improvements:

    1. Instead of an array, declare a bunch of named properties:
    Code (csharp):
    1. public Sprite smallTree;
    2. public Sprite bigTree;
    3. public Sprite bush1;
    4. ...
    It's a lot more typing up front, of course, but it makes it perfectly clear what you're referring to both in your code, and when dragging sprites into these slots in the Inspector. But the disadvantage is that you can't easily iterate over the whole lot of them (to, I don't know, recolor them all or something).

    2. Keep the array, but use an enum to define constants that you then use to access the array.
    Code (csharp):
    1. enum Scenery {
    2.      SmallTree = 0,
    3.      BigTree,
    4.      Bush1,
    5.     ...
    6. };
    7. ...
    8. foo.sprite = scenerySprites[(int)Scenery.BigTree];
    The only drawback here is the annoying need to explicitly typecast your enum to an int. But it's still shorter than writing a comment every time you inflict a magic number on your code. :)
     
    Harinezumi and Ryiah like this.
  13. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I prefer a third method. That's to wrap all of the relevant properties up in a single entity. Something like this:

    Code (CSharp):
    1. public Scenery[] scenery;
    2.  
    3. pubic class Scenery {
    4.     public string name;
    5.     public GameObject gameObject;
    6.     public Sprite sprite;
    7.     ...
    8. }
    If I'm in need of accessing by name, I'll use a dictionary instead of an array.
     
    Ryiah and Schneider21 like this.
  14. Bill_Martini

    Bill_Martini

    Joined:
    Apr 19, 2016
    Posts:
    445
    I got into commenting the hell out of my code a long time ago. Sometimes I do some pretty goofy stuff to work around issues and going back to an old project and trying to figure out why I did the goofy stuff is a mystery without the comments.

    I like the compactness of the array in the editor via collapsing and I"m commenting anyway. That's not to say those aren't great suggestions. I do use non-arrayed fields if there aren't a lot of them. I realize magic numbers are not ideal, but commenting mitigates that. Being completely self taught, my programming style might not be conventional.
     
    SPEAKERBUG and JoeStrout like this.
  15. Schneider21

    Schneider21

    Joined:
    Feb 6, 2014
    Posts:
    3,512
    More and more, this is becoming my preferred way of doing things. I only recently embraced the joy of structs, and using them effectively has helped organize things a lot better.
     
    Kiwasi likes this.
  16. J0hn4n

    J0hn4n

    Joined:
    May 11, 2015
    Posts:
    24
    Hello there, i know this is a old post its but i put this for future readers, i use a xml(some sort of recipes) file to create my prefabs(using AssetDatabase.LoadAssetAtPath<GameObject>(fullPath)) and add componets. That way i speed up development of new items/characters, also you can create folders and etc... its actually very usefull and avoids me having create a new character from a empty gameobject.

    NOTE : THIS ONLY WORKS IN EDITOR.