Unity: Comment attacher dynamicment un script inconnu à un GameObject (éditeur personnalisé)

Je suis en train de créer un système pour Unity Editor (inspecteur personnalisé et fenêtres personnalisées) qui automatisera et simplifiera le travail des artistes travaillant sur le jeu que nous réalisons, mais je me suis heurté à un mur.

J’essaie de trouver un moyen d’append dynamicment, via une entrée de l’éditeur Textfield et un bouton de l’interface graphique, un script inconnu à un object de jeu de la scène. L’artiste / programmeur va taper le nom du script dans le champ de texte et va chercher et append à un gameobject, mais je ne sais pas comment procéder, surtout que certaines fonctions de gameObject.AddComponent() sont obsolètes à partir de Unité 5.3

Voici ce que j’ai essayé de faire:

 public ssortingng scriptname; GameObject obj = null; scriptname = EditorGUILayout.TextField("Script name:", scriptname, GUILayout.MaxHeight(25)); if (GUILayout.Button("Attach script")) { //search for the script to check if it exists, using DirectoryInfo DirectoryInfo dir = new DirectoryInfo(Application.dataPath); FileInfo[] info = dir.GetFiles("*.*", SearchOption.AllDirectories); foreach (FileInfo f in info) // cycles through all the files { if(f.Name == scriptname) { //attaches to the gameobject (NOT WORKING) System.Type MyScriptType = System.Type.GetType(scriptname + ",Assembly-CSharp"); obj.AddComponent(MyScriptType); } } } 

(Bien sûr, il s’agit d’une version résumée, j’ai copié les lignes pertinentes de différentes parties du script).

Mais ça ne marche pas. Des idées?

Après une longue expérience, j’ai pu l’obtenir. Cela couvre également tous les composants de Unity. Je viens de faire une méthode d’extension pour rendre la vie plus facile.

 public static class ExtensionMethod { public static Component AddComponentExt(this GameObject obj, ssortingng scriptName) { Component cmpnt = null; for (int i = 0; i < 10; i++) { //If call is null, make another call cmpnt = _AddComponentExt(obj, scriptName, i); //Exit if we are successful if (cmpnt != null) { break; } } //If still null then let user know an exception if (cmpnt == null) { Debug.LogError("Failed to Add Component"); return null; } return cmpnt; } private static Component _AddComponentExt(GameObject obj, string className, int trials) { //Any script created by user(you) const string userMadeScript = "Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; //Any script/component that comes with Unity such as "Rigidbody" const string builtInScript = "UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; //Any script/component that comes with Unity such as "Image" const string builtInScriptUI = "UnityEngine.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"; //Any script/component that comes with Unity such as "Networking" const string builtInScriptNetwork = "UnityEngine.Networking, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"; //Any script/component that comes with Unity such as "AnalyticsTracker" const string builtInScriptAnalytics = "UnityEngine.Analytics, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; //Any script/component that comes with Unity such as "AnalyticsTracker" const string builtInScriptHoloLens = "UnityEngine.HoloLens, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; Assembly asm = null; try { //Decide if to get user script or built-in component switch (trials) { case 0: asm = Assembly.Load(userMadeScript); break; case 1: //Get UnityEngine.Component Typical component format className = "UnityEngine." + className; asm = Assembly.Load(builtInScript); break; case 2: //Get UnityEngine.Component UI format className = "UnityEngine.UI." + className; asm = Assembly.Load(builtInScriptUI); break; case 3: //Get UnityEngine.Component Video format className = "UnityEngine.Video." + className; asm = Assembly.Load(builtInScript); break; case 4: //Get UnityEngine.Component Networking format className = "UnityEngine.Networking." + className; asm = Assembly.Load(builtInScriptNetwork); break; case 5: //Get UnityEngine.Component Analytics format className = "UnityEngine.Analytics." + className; asm = Assembly.Load(builtInScriptAnalytics); break; case 6: //Get UnityEngine.Component EventSystems format className = "UnityEngine.EventSystems." + className; asm = Assembly.Load(builtInScriptUI); break; case 7: //Get UnityEngine.Component Audio format className = "UnityEngine.Audio." + className; asm = Assembly.Load(builtInScriptHoloLens); break; case 8: //Get UnityEngine.Component SpatialMapping format className = "UnityEngine.VR.WSA." + className; asm = Assembly.Load(builtInScriptHoloLens); break; case 9: //Get UnityEngine.Component AI format className = "UnityEngine.AI." + className; asm = Assembly.Load(builtInScript); break; } } catch (Exception e) { //Debug.Log("Failed to Load Assembly" + e.Message); } //Return if Assembly is null if (asm == null) { return null; } //Get type then return if it is null Type type = asm.GetType(className); if (type == null) return null; //Finally Add component since nothing is null Component cmpnt = obj.AddComponent(type); return cmpnt; } } 

Utilisation :

 gameObject.AddComponentExt("YourScriptOrComponentName"); 

Il est important de comprendre comment je l'ai fait pour pouvoir append de la prise en charge de nouveaux composants dans les futures mises à jour de Unity.

Pour tout script créé par les utilisateurs :

1. Découvrez ce qui doit être dans le ??? dans la fonction Assembly.Load .

 Assembly asm = Assembly.Load("???"); 

Vous pouvez le faire en mettant ceci dans votre script:

 Debug.Log("Info: " + this.GetType().Assembly); 

J'ai eu: Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

Nous devrions maintenant remplacer ??? avec ça.

 Assembly asm = Assembly.Load("Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); 

2. Découvrez ce qui doit être dans le ??? dans la fonction asm.GetType .

 Assembly asm = Assembly.Load("Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); Type type = asm.GetType(???); 

Dans ce cas, il s’agit simplement du nom du script que vous souhaitez append à GameObject.

Disons que votre nom de script est NathanScript :

 Assembly asm = Assembly.Load("Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); Type type = asm.GetType("NathanScript"); gameObject.AddComponent(type); 

Pour les scripts / composants intégrés à Unity non créés par les utilisateurs :

Image composants Rigidbody , Linerenderer , Image sont un exemple. Juste n'importe quel composant non créé par l'utilisateur.

1. Découvrez ce qui doit être dans le ??? dans la fonction Assembly.Load .

 Assembly asm = Assembly.Load("???"); 

Vous pouvez le faire en mettant ceci dans votre script:

 ParticleSystem pt = gameObject.AddComponent(); Debug.Log("Info11: " + pt.GetType().Assembly); 

J'ai eu: UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

Nous devrions maintenant remplacer ??? avec ça.

 Assembly asm = Assembly.Load("UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); 

2. Découvrez ce qui doit être dans le ??? dans la fonction asm.GetType .

 Assembly asm = Assembly.Load("UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); Type type = asm.GetType(???); 

Vous pouvez le faire en mettant ceci dans votre script:

 ParticleSystem pt = gameObject.AddComponent(); Debug.Log("Info: " + pt.GetType()); 

J'ai eu: UnityEngine.ParticleSystem

Rappelez-vous que ParticleSystem est utilisé comme exemple ici. Ainsi, la dernière chaîne qui ira à la fonction asm.GetType sera calculée comme asm.GetType :

 ssortingng typeSsortingng = "UnityEngine." + componentName; 

Disons que le composant que vous souhaitez append est LineRenderer :

 Assembly asm = Assembly.Load("UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); ssortingng typeSsortingng = "UnityEngine." + "LineRenderer"; Type type = asm.GetType(typeSsortingng); gameObject.AddComponent(type); 

Assembler le tout dans une méthode d'extension :

Comme vous pouvez le constater, l'ajout de scripts que vous avez créés et le script / les composants fournis avec Unity nécessitent un processus totalement différent. Vous pouvez résoudre ce problème en vérifiant si le type est null . Si le type est null , effectuez l'autre étape. Si l'autre étape est également null le script ne se ferme tout simplement pas .

Je suggérerais de faire ceci comme ceci:

 if(GUILayout.Button("Attach script")) { // check if type is contained in your assembly: Type type = typeof(MeAssemblyType).Assembly.GetTypes().FirstOrDefault(t => t.Name == scriptname); if(type != null) { // script exists in the same assembly that MeAssemblyType is obj.AddComponent(type); // add the component } else { // display some error message } } 

Bien sûr, cela échouera si vous utilisez des plugins (dépendances) qui contiennent d’autres composants, mais pour gérer cela, vous pouvez simplement vérifier les dépendances de votre assemblage:

 typeof(MeAssemblyType) // your type from Assembly-CSharp .Assembly // Assembly-CSharp assembly .GetReferencedAssemblies() // get referenced assemblies .FirstOrDefault(m => m.Assembly // from this assembly .GetTypes() // get all types .FirstOrDefault(t => t.Name == scriptname // select first one that matches the name ) ) 

Remarques :

GetReferencedAssemblies méthode GetReferencedAssemblies ne renverra que les assemblys qui ont été “utilisés” (chargés) par votre assembly. Pour être un peu clair, supposons que vous référencez ces assemblys:

  1. System.Xml,
  2. NewtonsoftJson

Et ce morceau de code:

 static void Main() { XmlDocument doc = new XmlDocument(); doc.LoadXml(); } 

Ensuite, la sortie de GetReferencedAssemblies ressemblera un peu à ça:

 >>> System.Xml, Version=, Culture=neutral, PublicKeyToken= 

Cela NewtonsoftJson que NewtonsoftJson ne sera pas chargé car il n’a pas été utilisé à l’intérieur de cet assemblage.

Meilleure suggestion:

Je vous suggère de mélanger la méthode de @Programmer answer mais de ne pas charger les assemblys car ils sont déjà chargés lorsque l’éditeur de Unity démarre avec votre projet. Utilisez GetReferencedAssemblies méthode GetReferencedAssemblies et à partir de là, vous devez appeler la méthode GetTypes pour extraire tous les types possibles de cet assemblage. (Cela sera lent mais vous garantira les résultats souhaités) Après cela, vous pourrez simplement utiliser FirstOrDefault ou simplement parcourir le Type[] vous-même pour trouver celui que vous voulez.

C’est encore possible. Utilisez ceci

 UnityEngineInternal.APIUpdaterRuntimeServices.AddComponent(GameObject go, "", ssortingng componentName); 

J’espère que cela pourra aider

Décomstackr AddComponentWindow of Unity. Apprenez comment est fait. Ne réinventez pas la roue. Vous êtes paresseux voir le lien

AddComponentAdjusted

Ensuite, appelez la fenêtre quelque chose comme ça:

  ws.winx.editor.windows.AddComponentWindow.Show(rect); ws.winx.editor.windows.AddComponentWindow.OnClose += OnCloseComponentSelectedFromPopUpMenu; ws.winx.editor.windows.AddComponentWindow.ComponentSelected += (menuPath) => ComponentSelectedFromPopUpMenu(positionData.Item1, menuPath); 

Retour de poignée (la partie la plus délicate, encore apprendre des homosexuels intelligents Unity)

  private void ComponentSelectedFromPopUpMenu(Vector2 position, ssortingng menuPath) { MonoScript monoScript; char[] kPathSepChars = new char[] { '/', '\\' }; menuPath = menuPath.Replace(" ", ""); ssortingng[] pathElements = menuPath.Split(kPathSepChars); ssortingng fileName = pathElements[pathElements.Length - 1].Replace(".cs", ""); if (pathElements[0] == "Assets") { Debug.LogWarning("Unity need to comstack new added file so can be included"); } else if (pathElements.Length == 2) { //use fileName //do something } else if (pathElements[1] == "Scripts") {//Component/Scripts/MyScript.cs ssortingng[] guids = AssetDatabase.FindAssets("t:Script " + fileName.Replace(".cs", "")); if (guids.Length > 0) { for (int i = 0; i < guids.Length; i++) { monoScript = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[i]), typeof(MonoScript)) as MonoScript; Type typet = monoScript.GetClass(); if (typet == null) continue; } else {//Component/Physics/Rigidbody //try to find by type, cos probably Unity type Type unityType = ReflectionUtility.GetType("UnityEngine." + fileName); if (unityType != null) { //do something return; } //Based on attribute [AddComponentMenu("Logic/MyComponent")] //Component/Logics/MyComponent string[] guids = AssetDatabase.FindAssets("t:Script " + fileName.Replace(".cs", "")); if (guids.Length > 0) { for (int i = 0; i < guids.Length; i++) { monoScript = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[i]), typeof(MonoScript)) as MonoScript; Type typet = monoScript.GetClass(); if (typet == null) continue; object[] addComponentMenuAttributes = typet.GetCustomAttributes(typeof(AddComponentMenu), true); if (addComponentMenuAttributes != null && addComponentMenuAttributes.Length > 0 && "Component/" + ((AddComponentMenu)addComponentMenuAtsortingbutes[0]).componentMenu == menuPath) { //do somethings } } } } }