Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Get behavior subclasses to register themselves for selection with custom inspector

Discussion in 'Scripting' started by yuzu_drink, Jan 20, 2012.

  1. yuzu_drink

    yuzu_drink

    Joined:
    Jan 15, 2012
    Posts:
    14
    Hello,

    I am trying to create an enemy spawner that I can place instances of throughout the world. I need to be able to select per-instance what kind of enemy representation I want (easy, use a prefab) and also which logical behavior those enemies use (not so easy).

    I could do this by having a simple public string variable which I type in to each spawner, but that creates the burden of typing the class name correctly each and every time I make a spawner.

    I could also manage (by hand) an enum of the classes available to choose, and select from that list. This makes adding new logic behaviors more difficult, but not quite so tedious (or error prone) as simply typing in the names each time; though it does pose the small issue of where is best to store this enum (in the base class? in a separate Enum class?)...

    Ideally, what I think I'd like to do is have a static List<string> into which each class adds a string of its class name, and then use a custom popup editor to select which classname to use. Then when I create a spawner in the level, I can simply select from a popup menu which subclass of BaseEnemyBehavior I want to use.

    I tried to do the following:
    Code (csharp):
    1. public class BasicEnemyBehavior : MonoBehaviour {
    2.    
    3.     public static List<string> enemyBehaviors;
    4.    
    5.     static BasicEnemyBehavior() {
    6.         enemyBehaviors = new List<string>();
    7.         enemyBehaviors.Add("BasicEnemyBehavior");
    8.     }
    9. }
    and then each subclass has a similar static constructor which adds its own class name to the list.

    However this doesn't appear to work, as I only get the base class when I access the static variable.

    Are static variables not persistent across child classes? Or am I missing something about how static constructors work?

    Is there another, painfully obvious way to accomplish this task that I'm simply missing?

    Thanks for any help you can offer.
     
    Last edited: Jan 21, 2012
  2. Alan47

    Alan47

    Joined:
    Mar 5, 2011
    Posts:
    163
    Hello,

    I've found this page about C# programming which might be of interest for you: Static constructors

    I am not entirely sure how exactly static constructors are handled in Mono and especially in Unity, but I think what "breaks" your idea is the following attribute:

    In other words: as long as you do not actively *use* a class, it won't get loaded by the classloader. And static constructors are evaluated at the time their class is loaded. And since you can't call a static constructor manually (as stated on the website), there is no way I can think of to force the static constructors to be evaluated - EXCEPT if you would have a reference to the class you want to use, then you could call any (dummy) method on it which would force it to be loaded, which would trigger its static constructors as a "side effect". However, as you want your subclasses to register automatically, that method is not applicable.

    A possible solution I can think of (although it is pretty awkward I must admit) is to manually load every class file within a certain folder on your machine, so when you add a new behaviour, you would place that class file within the specified directory and it will get loaded when you create your list of available behaviours. I know how this is done in Java, but unfortunately I have no clue how C# handles this. If you really want to get your hands dirty and do this, there's certainly somebody around here who can give you a hint on how to do it. A word of warning, though: manually messing with the classloader usually doesn't result in anything good if you don't know EXACTLY what you're doing.

    I would refrain from this method and simply go with a less sophisticated version in which you manually register any existing behaviour in a list. That way, you have to manually update the list every time you add a behaviour, which is inconvenient, but perhaps the best solution in this special case.

    I hope this helps.


    Greets,



    Alan
     
  3. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,039
    Seems to work for me (as long as an instance of the class exists somewhere in the scene, static constructor won't get triggered if the class is never loaded) ... that said it seems like a very strange way of addressing this requirement...

    Code (csharp):
    1.  
    2. public class ParentClass : MonoBehaviour {
    3.    
    4.     public static List<string> list;
    5.    
    6.     static ParentClass(){
    7.         list = new List<string>();
    8.         list.Add("ParentBehavior");
    9.     }
    10. }
    11.  
    12.  
    13. public class ChildClass : ParentClass {
    14.     static ChildClass() {
    15.         list.Add("ChildBehavior");
    16.     }
    17.  
    18.     void Start(){
    19.         foreach(string s in ParentClass.list) Debug.Log("Behaviour: " + s);
    20.     }
    21. }
    22.  
    Output:
    Behaviour: ParentBehaviour
    Behaviour: ChildBehaviour
     
    Last edited: Jan 20, 2012
  4. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,039
    Proscratinating... given you want a list of class names as strings ... how about this:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. public class SpawnerClass : MonoBehaviour {
    7.    
    8.     static List <string> behaviours;
    9.    
    10.     static SpawnerClass() {
    11.         behaviours = new List<string>();
    12.        
    13.         foreach(System.Type o in typeof(BehaviourClass).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(BehaviourClass)))) {
    14.             behaviours.Add(o.Name);
    15.         }
    16.     }
    17.    
    18.     void Start() {
    19.         foreach (string behaviour in behaviours) Debug.Log(behaviour);
    20.     }
    21. }
    22.  
    23. public abstract class BehaviourClass : MonoBehaviour {
    24.    
    25. }
    26.  
    27. public class BehaviourOne : BehaviourClass {
    28.    
    29. }
    30.  
    31.  
    32. public class BehaviourTwo : BehaviourClass {
    33.    
    34. }
    35.  
    Output:
    BehaviourOne
    BehaviourTwo
     
    Last edited: Jan 20, 2012
  5. yuzu_drink

    yuzu_drink

    Joined:
    Jan 15, 2012
    Posts:
    14
    JohnnyA, thanks for putting that together! I'll try it once I get home this evening.

    The problem I was running into with the way I was doing it is that, as Alan47 stated quite well, the static constructors only get called if you create at least one instance of the class (or in some other way already reference the class).

    But if I can look at the assembly information like that, it could be just the solution I was hoping for! :)

    Thanks so much, and if this doesn't work, I'll just go back to enumerating all the classes by hand. I sometimes get too distracted by trying to do something elegantly and forget that, at the end of the day, I'm just trying to write a fun game. XD

    -- edit -----
    Okay, maybe "elegant" isn't the best word to describe mucking about in the assembly structure, but I meant something that takes care of registering subclasses for me so that I don't have to risk mistyping something. Easy to find and correct, yes, but still kind of irritating if you don't ACTUALLY have to do it. Maybe "robust" is a better word for it? :p
     
    Last edited: Jan 20, 2012
  6. yuzu_drink

    yuzu_drink

    Joined:
    Jan 15, 2012
    Posts:
    14
    Just following up, that solution worked like a charm! Thanks again. :)