5
\$\begingroup\$

I want to instantiate a prefab within the context menu (Components). I got the code to create the entry in the context menu, but I can't figure out how to place my prefab in the scene. My current code is somehow like this :

using UnityEngine;
using UnityEditor;
using System.Collections;

public static class MenuItemGameobject {

    [MenuItem("GameObject/Create RedBlue GameObject", false, 12)]
    static void Init()
    {
        Debug.Log("here");
        //new GameObject("RedBlue GameObject"); // This adds an empty GO
        //new GameObject((GameObject.Find("UIPlane"))); //This duplicates the GO if it is already in the scene
        //PrefabUtility.InstantiatePrefab(); //This is the method which I guess is needed, but I don't know how to get the GO parameter
    }
}

So again, I want to add my prefab exactly like you add a cube within the context menu.

Edit : This is how a public variable looks like in the inspector :

enter image description here

\$\endgroup\$
13
  • 1
    \$\begingroup\$ Did you search? I found this almost instantly. \$\endgroup\$ – Droppy Aug 10 '16 at 10:14
  • 1
    \$\begingroup\$ Please in the future, don't leave information like that out. You're wasting everyone's time. \$\endgroup\$ – Tyyppi_77 Aug 10 '16 at 10:18
  • 1
    \$\begingroup\$ Okay, and btw the GameObject.Find method doesn't work to get a prefab, which isn't already in the scene \$\endgroup\$ – TobiasW Aug 10 '16 at 10:20
  • 1
    \$\begingroup\$ Well yes, but still the same problem, I don't get the 'GameObject' parameter from the 'InstantiatePrefab' Method. Since I can't have a public variable, where I could drag my prefab onto \$\endgroup\$ – TobiasW Aug 10 '16 at 10:24
  • 1
    \$\begingroup\$ Since I'm not completely sure about the correctness of PrefabUtility.InstantiatePrefab in this case no, maybe the correct way is your method new GameObject() and then InstantiatePrefab? I'm not sure, that's why I formulated the question like it is and I don't want to spam the comments about the correctness of this question since a good answer would have an example solving all the open problems... \$\endgroup\$ – TobiasW Aug 10 '16 at 10:29
2
\$\begingroup\$

Create a central-control script to manage your prefabs

If you would prefer the "drag and drop" approach of being able to drag your prefabs in from the inspector, there is an easy work around to working with a script that is not inherited from MonoBehaviour; create a separate MonoBehaviour to work as the go-between. This script can also handle any other management functionality you might need, in regards to prefabs, now or in the future.


The first step is to set up a global controller script. I tend to favor the static self-reference approach, as I work in a very controlled environment.

using UnityEngine;
using System.Collections.Generic;

public class PrefabManager: MonoBehaviour 
{
    public static PrefabManager control;  // cheeky self-reference

    void Start ()
    {
        control = this;                   // linking the self-reference
    }
}

In this example, I can access my PrefabManager script from any other script, using the line PrefabManager.control. You have to ensure that you only have one instance running, for it to work correctly, but it can be a simple and helpful workaround. There are other, and far better, ways to approach creating a central 'control script'.

Though I am still fairly new to the concept, I am told that Singletons can be helpful in this area. In XNA, I have used services to the same effect, but can not comment on how they relate to or work in Unity. If you are working within a team, discuss other options with them. There will likely be a specific method used to create these static classes, throughout your project, as you will often find need to do so.


From here, all you need to do is create an interface to allow you to store your prefab references through the Unity interface. There are several approaches you can take for this, two of which I will mention:

Arrays or Lists

You can create a simple GameObject array to store your references. You can do this with a list or array.

public List<GameObject> prefabList;

A list allows greater manipulation functionality, and are more suitable when you intend to add more items to the list past the initial instantiation. It sounds like all objects would be referenced before the game is run, so you will probably be better off with an array.

public GameObject[] prefabArray;

A more generic array has its limitations. As I have always been taught, C# is particularly troublesome with increasing the maximum size of an array, past the initial instantiation. There are always ways around that, but it sounds like you may not need to, in the first place. Most importantly, arrays store the elements in a more generic format. You can easily reference the elements in the array by their index, using prefabArray[0], prefabArray[1] and prefabArray[2] to access the first, second and third prefabs listed in the array.

Serialised Struct

You might prefer to create your own structure, in order to store extra values with your prefab reference. A text-based tag for easier searching, for example.

public PrefabElement[] prefabStruct;

[System.Serializable]           // Expose the struct inside the Unity Inspector
public struct PrefabElement
{
    public GameObject prefab;
    public string tag;
}

In this example, we have an array of elements that each store a GameObject reference and a string. Note the use of [System.Serializable]. This is important, as it tells Unity to serialise the struct, allowing it to display the fields in the inspector. Without it, you do not have the ability to drag items in, or edit it from the inspector, at all. This sort of implementation allows you to bind other variables to your prefab reference. If it would be helpful to search for a particular prefab using text-based naming conventions, for example, this would be the better option.

You can access the elements with prefabStruct[i].prefab and prefabStructure[i].tag. In the example implementation, where you only have two variables, it might be more beneficial to use Dictionary<GameObject, string> prefabDictionary. The Dictionary type is more suited to handling sets, in this case. I have not been able to determine a way to make it accessible via the inspector, however, and am unable to suggest a good method to implement a Dictionary for supplying prefabs via the inspector.

What if I can not use public variables?

Of course, in the examples given so far, the variables need to be public in order to allow you to drop values and references in from the inspector. But you can enable variables from the inspector, without making them publicly accessible to everything else. Simply use [SerializeField].

public float a;                      // shown in inspector
private float b;                     // not shown in inspector
[SerializeField] private float c;    // shown in inspector

You would also need to include some sort of Get() function in your prefab controller, to allow access to the prefab references from outside the controller.

Loading resources with the Resource.Load() function

If you instead prefer to load in the prefabs using script, the Resource.Load() function will help you with that. I am not as familiar with it, but you can find documentation on it here.

Resource.Load(string) looks in your resources folder for any prefab matching the string. You can look directly for a prefab with Resource.Load("prefabName"), or look in a specific folder with Resource.Load("folder/prefabName". Note that we always use forward slashes. This is important, as while you may be use interchanging the forward slash (/) with a backward slash (\) for use with alternate operating systems, Unity always accepts the forward slash (/), regardless of the operating system.

You can overload the function with a type, in order to narrow your selection down. Resource.Load("prefabName", typeof(GameObject) will only return GameObjects, even if you have a texture that is also named "prefabName".


Note that this does not immediately instantiate the prefab, although you can load the resource inline with an instantiation. You should also consider the possible return structure, as follows:

  • If the search returns multiple prefabs, it will return an array of the prefabs. If your code is set up to only interpret a single instance, this can lead to errors. Unless you intend to load an array of like-named prefabs, I would recommend ensuring your prefabs each use different names. Regardless, this is generally a good idea for the purpose of keeping your project tidy. 50 tips for working with Unity (Best Practices) offers some sound advice for naming conventions, as well as a plethora of other useful tips.
  • If the search returns no prefab, it will instead return null. Obviously, this should not happen. It might still be a good idea to double check that your prefab reference != null after your Resource.Load(), to prevent possible issues, later.

You would generally load and/or instantiate a prefab as so:

GameObject prefab

// Loading in the reference to your prefab
prefab = Resource.Load("prefabName", typeof(GameObject)) as GameObject;

// Loading in and instantiating an instance of your prefab
prefab = Instantiate(Resource.Load("prefabName", typeof(GameObject))) as GameObject;

Note the as GameObject at the end. Documentation infers that you must confirm that you wish to load it as the particular type you are setting it as. This concept is also new to me, but I am sure others can explain why you have to do this, if you are curious.

\$\endgroup\$
4
  • \$\begingroup\$ Apologies if this is a bit more complex than required. I had assumed the need to instantiate multiple different prefabs. If only a single prefab instance is being instantiated, you probably should not go to the trouble of creating a PrefabController class. \$\endgroup\$ – Gnemlock Aug 10 '16 at 12:03
  • \$\begingroup\$ This is really great and it's totally fine :) But it's lacking one part of the scripting since I didn't know how to instantiate a prefab with Resource.Load() \$\endgroup\$ – TobiasW Aug 12 '16 at 5:27
  • \$\begingroup\$ @TobiasW, I have tried to include what I can on the function. I have also linked Unitys manual page on the function. Normally I would play around with it to get a better understanding, but if you have any questions, by all means ask (though if not directly related to the specific problem, consider asking as a new question). When I am more available, I am happy to have a play around with it for a better understanding. \$\endgroup\$ – Gnemlock Aug 12 '16 at 7:04
  • \$\begingroup\$ Great now it's complete! One more problem : I am placing these prefabs with the context menu in the scene hierarchy. Is there a way to place this prefab at the position where I clicked so e.g. as the child of the clicked object and so on. \$\endgroup\$ – TobiasW Aug 12 '16 at 7:39
1
\$\begingroup\$

You need 2 scripts. One Editor-script and one MonoBehaviour-script.

  1. Put the prefab "filenameXYZ.prefab" in your "\Assets\Resources" folder.

  2. In your MonoBehaviour-Script copy this:

    using UnityEngine;
    using System.Collections;
    
    public class myMonoScript : MonoBehaviour{
    
        public GameObject myPrefab;
    
        public void createObject()
        {
            if (myPrefab != null)
            {
                myPrefab = Resources.Load("filenameXYZ") as GameObject;
            }
    
        GameObject myInstance = (GameObject)Instantiate(myPrefab, new Vector3(0, 0, 0), new Quaternion(0, 0, 0, 0));
    
        myInstance.name = "new Instance";
    
        //... set other parameters
        }
    }
    
  3. In your inspector script copy this:

    using UnityEngine;
    using System.Collections;
    using UnityEditor;
    
    [CustomEditor(typeof(myMonoScript))]
    
    public class myEditorScript : Editor
    {
    
        void Update()
        {
            if (target != null)
            {
                myMonoScript myScript = (myMonoScript)target;
            }
        }
    
        public override void OnInspectorGUI()
        {
            DrawDefaultInspector();
            myMonoScript myScript = (myMonoScript)target;
    
            if (GUILayout.Button("Instantiate Object"))
            {
                myScript.createObject();
            }
        }
    }
    
  4. Adjust the code.

\$\endgroup\$
9
  • \$\begingroup\$ This looks good! I'll test it and give you the vote when it's working :) \$\endgroup\$ – TobiasW Aug 10 '16 at 11:32
  • \$\begingroup\$ Wait, I want a new Entry in the Context menu not in the Editor \$\endgroup\$ – TobiasW Aug 10 '16 at 11:45
  • \$\begingroup\$ I dont quite understand. you can call "myScript.instantiateObject();" from anywhere. \$\endgroup\$ – OC_RaizW Aug 10 '16 at 12:05
  • \$\begingroup\$ under Debug.Log("here"); myMonoScript myScript = (myMonoScript)myGameObject; myScript.instantiateObject(); \$\endgroup\$ – OC_RaizW Aug 10 '16 at 12:41
  • 1
    \$\begingroup\$ Yeah thanks, the resources part did the trick, I didn't know about that... And it took me a minute to figure out, that you misspelled resource because it's written with 'ss' in my native language... ;D \$\endgroup\$ – TobiasW Aug 12 '16 at 5:20
0
\$\begingroup\$

Both answers helped me a lot, but to solve the problem for my particular case, I will post the solution here :

You need a MonoBehaviour Script :

using UnityEngine;
using System.Collections;

public class mymonoscript : MonoBehaviour
{
    public void createObject()
    {
        GameObject instance = Instantiate(Resources.Load("UIPlane", typeof(GameObject))) as GameObject;
    }
}

And another static class for the CustomMenu stuff :

using UnityEngine;
using UnityEditor; // Dont forget to add this as we are extending the Editor
using System.Collections;

public static class MenuItemGameobject
{
    [MenuItem("GameObject/CustomUI/Element1", false, 12)]
    static void Init()
    {
        mymonoscript myScript = (mymonoscript)new mymonoscript();
        myScript.createObject();
    }
}

Not sure whether my script call is perfect, but it does the job in this case, maybe someone can edit it.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.