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

Creating Editor for plain C# Class

Discussion in 'Scripting' started by RIw, Apr 29, 2018.

  1. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    Hello, I want to ask you if it is possible to create an Editor for plain C# class like we do for the ones deriving from MonoBehaviour class?
    I want to change some properties of that class without accessing the C# Code.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    You can use PropertyDrawer's to create editors that target none MonoBehaviour classes, and fields that have been tagged with special attributes:
    https://docs.unity3d.com/ScriptReference/PropertyDrawer.html

    Note, the context of them are very different then the classes that inherit from Editor and target MonoBehaviour.

    They are heavily recycled and the state between calls to OnGUI can not be trusted. Since if you have 2+ fields that use the PropertyDrawer that are being rendered on screen, they just use the same PropertyDrawer over and over. So you'll have calls to OnGUI for both. And if you attempt to modify state, you'll have a lot of overlap.
     
  3. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct But it won't work when I'll just click on the class file in the Unity Project window. It won't show me the Inspector.
    It only work when I attach this plain class to some MonoBehaviour :(
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    The class file is just a class. Why would there be an editor?

    An editor would be for an object, an instance of the class.

    If you want objects in your assets folder, you can inherit from ScriptableObject, make instances of your custom type, and then that object in your assets folder can have an editor.

    ...

    Otherwise... what would the editor of a class be? A notepad?
     
  5. rakkarage

    rakkarage

    Joined:
    Feb 3, 2014
    Posts:
    683
  6. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct I want Editor for just a class file to change value of some properties without hardcoding it.
    I know about ScriptableObject's but they don't fit my needs.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    This doesn't logically follow with what a class is.

    A class is just a blueprint for an object.

    Changing some properties IS hardcoding it.

    You have to get an instance of the class to then modify the properties of to have an independent object from the class that isn't hard-coded.

    ...

    It's just like MonoBehaviour/components. You can't modify the class itself except the actual hard-coded text. If you select it you just see the code for it.

    You have to attach it to a GameObject (create an instance of it) to modify its properties (in a none hardcoded manner).

    This goes for ANY class.

    If you have:
    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.  
    5.     public string Name;
    6.  
    7. }
    And you were to select the class itself in the editor. And you changed 'Name' to be "Blargh". What do you expect to happen?

    Because I know what to expect to happen. I expect the hard-coded default value of 'Name' to change to "Blargh". And then any instance of Foo will have that as their default 'Name' value.

    Which you can get just by editing the code to be:
    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.  
    5.     public string Name = "Blargh";
    6.  
    7. }
    8.  
     
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Now if you did this:
    Code (csharp):
    1.  
    2. [CreateAssetMenu(fileName = "FooAsset", menuName = "Custom/Foo")]
    3. public class Foo : ScriptableObject
    4. {
    5.  
    6.     public string Name;
    7.  
    8. }
    9.  
    And then in your 'Project' view you right clicked and selected "Create->Custom->Foo", you'd get an asset named 'FooAsset' that is of type 'Foo' and has an inspector to modify 'Name'.

    And then with this you can create a custom editor the same way you would for MonoBehaviour using the 'CustomEditor' attribute on an 'Editor' script.
     
  9. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct Maybe I will try to explain why i need such solution, then it will be easier to understand it.
    I have some implementations of my own ISpell Interface which is designed for Spells in my game.
    Many spells are pretty much unique, they have their own factors, I want them to dispose some fields in the Inspector to for example drag GameObject that the Spell is using, or change some factor's value without opening Code Editor.
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Yes.

    But a class is not an object, it is a blueprint for an object.

    You don't have an instance of the class to modify the properties of.

    My point is you are fundamentally not understanding how classes work.
     
  11. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,200
    @RIw, are you talking about the editor for the script asset?

    Ie. this one:
    upload_2018-5-2_18-0-9.png


    In that case, yes, you can edit it, but it's only relevant for MonoBehaviours and ScriptableObjects. Those fields are editor settings to provide some default values when you create a new instance of the MB or SO, and are not relevant for non-asset types (like plain classes).
     

    Attached Files:

  12. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct My point is that you dont understand what I've written earlier.
     
  13. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @Baste Yeah thanks, I know about building Editor for ScriptableObjects and MonoBehaviours, but as I mentioned earlier. I'm now not targeting them.
    Well I'll just have to change a little bit my plan, and provide the data to those classes in a different way.
     
  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Well you didn't do a very good job at describing it.

    You want to have an editor for what then?

    Not for the class to modify defaults like @Baste said... but not for an instance? What then? If your planned editor doesn't modify the class itself, the defaults, or an instance... what could it be modifying?
     
  15. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct Think about the following example:

    public class SomeFunClass : IKillMe
    {
    public GameObject Prefab;
    void KillMe()
    {}
    }

    And all I want to do is to click on the SomeFunClass in Unity, and Drag the 'Prefab' instead of hardcoding loading it from the Resources. Same thing for other data types.
     
  16. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    And that requires an instance of it.

    If there is no instance, what would be storing a reference to the prefab? There's nothing to store it!

    Classes aren't objects (well if we want to get pedantic, there is a reflected object that represents a class, but this is not what we're talking about). Classes are blue prints for objects. Classes define what functions and properties exist for objects of that class type.

    Objects are instances of that class. Once you have an instance, you can set values into the properties.

    Classes can also have default values, which are defined in the code by setting = to the various fields. Unity also supports defining defaults through the editor for MonoBehaviour and ScriptableObject. But this is an editor only thing, and is for 'defaults'... which you said you don't want.

    So... no object, then nothing to assign the Prefab to.

    If you assigned it to the class. What then? You can create multiple instances of that class. Do they ALL get that reference? Wouldn't that be a 'default'?
     
    Last edited: May 2, 2018
  17. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct Yeah I want to make those properties default obviously, as I said many times earlier. I just want to expose to the Editor the values that I normally hardcoded in the class so instead of
    Code (CSharp):
    1. public float someFloat = 5;
    I could do that in Editor, without opening Visual Studio.

    So each instance of this class would have the value of someFloat as I have set in the Editor.
    You can think of it as some kind of 'Factory' for the instances of this class.
     
  18. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So you DO want what @Baste showed....

    :smh:

    There is no built in way from Unity to do this.

    Though you could probably hack together something to do it using the UnityEditor API. It ain't going to be trivial though. You'll need some sort of container to hold references to the default values (maybe store said information in the meta file for the class?). And of course it will be editor time only.

    Well you could come up with a runtime one. But that would require also instantiating instances of your class through some factory that resolved these references. And you'd have to store the default values into some other resource (so not the meta file).

    In the end... yeah, not something trivial that could really be explained here.

    And not directly supported by Unity.
     
  19. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    For what purposes do you need this?

    Why isn't ScriptableObject an option for you? You can create assets that represent instances of some custom data container which represent specific configurations. They're super powerful, and you're not restricted to just one single default configuration.
     
  20. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct I'm working with a couple of friends on some game. I created an interface for implementing Spells for this game. The Spells are not any type of MonoBehaviour or something, but each one of them has unique set of properties, so I wanted to expose some variables I'm using in the Spell implementations to allow my teammates adjust some factors without coding (just replacing variable's value but still).

    But yeah, your last message made me sure that Unity is not supporting it, so I will have to write some Singleton class for Storage and query it from Spell implementations, or I will need to figure out some other, maybe smarter solution.
     
  21. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    I think a ScriptableObject would do this great.

    Code (csharp):
    1.  
    2. [CreateAssetMenu(fileName = "Spell", menuName = "Custom/Spell")]
    3. public class SpellConfiguration : ScriptableObject
    4. {
    5.  
    6.     public GameObject FXPrefab;
    7.     public float Power;
    8.     public float AOERange;
    9.     //so on so forth
    10.  
    11. }
    12.  
    These aren't monobehaviours.

    But with it you can create assets in your assets folder and configure them.

    For example here is how we set up our weapons. And for them custom MovementSettings and animation sets for each weapon:
    Code (csharp):
    1.  
    2.     [CreateAssetMenu(fileName = "PlayerMovementSettings", menuName = "Prototype Mansion/PlayerMovementSettings")]
    3.     public class PlayerMovementSettings : ScriptableObject
    4.     {
    5.  
    6.         #region Fields
    7.  
    8.         [SerializeField()]
    9.         public float Speed = 1f;
    10.         [SerializeField()]
    11.         public float RunSpeed = 2f;
    12.         [SerializeField()]
    13.         [UnityEngine.Serialization.FormerlySerializedAs("TurnSlerpSpeed")]
    14.         [Tooltip("Speed of turning while walking about.")]
    15.         public float TurnSpeed = 180f;
    16.         [SerializeField]
    17.         [Tooltip("Speed of turning while walking about in tank controls mode.")]
    18.         public float TankControlTurnSpeed = 180f;
    19.         [SerializeField()]
    20.         [Tooltip("Speed at which the player auto rotates towards target.")]
    21.         public float LockOnTurnSpeed = 180f;
    22.         [SerializeField()]
    23.         [Range(0f, 1f)]
    24.         public float InjuredSpeedDamper = 0.75f;
    25.         [SerializeField()]
    26.         [Range(0f, 1f)]
    27.         public float CriticalSpeedDamper = 0.75f;
    28.         [SerializeField]
    29.         public float QuickTurnDuration = 0.1f;
    30.  
    31.         [SerializeField]
    32.         [Range(0f, 1f)]
    33.         private float _grappleReleaseOdds = 0.1f;
    34.  
    35.         [SerializeField]
    36.         public float ImmunityDuration = 1f;
    37.  
    38.         [SerializeField]
    39.         [Range(0f, 1f)]
    40.         [Tooltip("If targeting an enemy, a damper to be applied to the speed.")]
    41.         private float _enemyTargetedSpeedDamper = 1f;
    42.  
    43.         [SerializeField]
    44.         [Range(0f, 1f)]
    45.         [Tooltip("While performing an attack, a dampe to be applied to the speed.")]
    46.         private float _attackingSpeedDamper = 1f;
    47.  
    48.         [SerializeField]
    49.         [Tooltip("If a weapon makes a successful strike, the player can be stunned for a short duration. Only checked by melee weapon controller.")]
    50.         public float SuccessfulStrikeFreezeDuration = 0f;
    51.  
    52.         #endregion
    53.  
    54.         #region Properties
    55.  
    56.         public float GrappleReleaseOdds
    57.         {
    58.             get { return _grappleReleaseOdds; }
    59.             set { _grappleReleaseOdds = Mathf.Clamp01(value); }
    60.         }
    61.  
    62.         public float EnemyTargetedSpeedDamper
    63.         {
    64.             get { return _enemyTargetedSpeedDamper; }
    65.             set { _enemyTargetedSpeedDamper = Mathf.Clamp01(value); }
    66.         }
    67.  
    68.         public float AttackingSpeedDamper
    69.         {
    70.             get { return _attackingSpeedDamper; }
    71.             set { _attackingSpeedDamper = Mathf.Clamp01(value); }
    72.         }
    73.        
    74.         #endregion
    75.  
    76.     }
    77.  
    And the resulting item:

    MovementSettings.png
     
  22. RIw

    RIw

    Joined:
    Jan 2, 2015
    Posts:
    90
    @lordofduct Yeah, I thought about ScriptableObjects too but I think that the Factors are going to be "Upgradeable" for Players so maybe some Singleton Storage Class that is based on PlayerPrefs or Custom Serialization would be better.
    Anyway thanks for sharing ;)
     
  23. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Not sure why ScriptableObjects would not support 'upgradeable'.

    I would definitely suggest foregoing PlayerPrefs!

    Hope you figure out what you want, have fun.
     
    RIw likes this.