Search Unity

Editor driven dependency injection

Discussion in 'Scripting' started by Wenzil, Nov 13, 2013.

  1. Wenzil

    Wenzil

    Joined:
    Apr 3, 2013
    Posts:
    19
    Hello everyone,

    I've come across an idea very much related to serialization and dependency injection that would make writing reusable code in Unity much simpler and fun. There is already a form of controlled dependency injection in Unity: a GameObject expects MonoBehaviour derived script implementations that you inject using one of the following methods:
    • Select a game object, then click 'AddComponent' in the inspector, and select the script you want to inject
    • Select a game object, right-click an already injected script in the inspector, click 'Copy Component', right click anywhere in the inspector of another game object, click 'Paste Component As New' to inject a copy of the script
    • Simply drag-and-drop a script from your assets folder to the game object you want to inject the script in
    Forget about game objects and components for a minute.

    What I'm proposing is to extend that functionality to the scripts themselves. The idea is that you would write an abstract class or interface, and then any script which specifically exposes a field of that abstract type could be injected with some concrete implementation. The injection could be done in a way very similar to how you inject scripts in game objects today.

    Instead of drag-and-dropping a monobehavior in a game object, you would drag-and-drop any script in a field specifically exposed for dependency injection. And just like Unity today checks that the scripts you inject in game objects inherit from MonoBehavior, here it would need to check that the injected script inherits the abstract type of the field. Exposing the field could just be a matter of making it public (just like serialization) or marking it with a hypothetical [Injectable] attribute.

    Now, such functionality would probably require (and be based on) something like Serialization of Polymorphic Data Types. So I urge you guys to go vote for it!

    Any thoughts?
    Thanks!
     
    Last edited: Dec 22, 2013
  2. Wenzil

    Wenzil

    Joined:
    Apr 3, 2013
    Posts:
    19
    I've just come across a Gamasutra article which touches on this subject and it reignited my enthusiasm for a feature like this. I've made my original post more consise and here's a proof of concept demonstration so you get the idea.

    Say we have an interface for things that move and an interface for things that shoot.
    Code (csharp):
    1.  
    2. public interface IMovement
    3. {
    4.     void Move(GameObject go);
    5. }
    6.  
    7. public interface IWeapon
    8. {
    9.     void Shoot();
    10. }
    11.  
    And we have a Ship behavior which requires a mechanism to move and a mechanism to shoot.
    Code (csharp):
    1.  
    2. public class Ship : MonoBehaviour
    3. {
    4.     // abstract fields to be injected in the editor
    5.     public IMovement movementMechanism;
    6.     public IWeapon weaponMechanism;
    7.    
    8.     void Update()
    9.     {
    10.         movementMechanism.Move(gameObject);
    11.         weaponMechanism.Shoot();
    12.     }
    13. }
    14.  
    Here are two movement implementations that we may want for our ship.
    Code (csharp):
    1.  
    2. public class LinearMovement : IMovement
    3. {
    4.     // this variables is tweakable for every injected instance in the editor
    5.     public float speed = 1;
    6.  
    7.     public void Move(GameObject go)
    8.     {
    9.         go.transform.Translate(Time.deltaTime * speed, 0f, 0f);
    10.     }
    11. }
    12.  
    Code (csharp):
    1.  
    2. public class SenoidalMovement : IMovement
    3. {
    4.     // these variables are tweakable for every injected instance in the editor
    5.     public float amplitude = 1f;
    6.     public float frequency = 2f;
    7.    
    8.     public void Move(GameObject go)
    9.     {
    10.         float yMovement = amplitude * (Mathf.Sin(2 * Mathf.PI * frequency * Time.time) - Mathf.Sin(2 * Mathf.PI * frequency * (Time.time - Time.deltaTime)));
    11.         go.transform.Translate(Time.deltaTime * 10f, yMovement, 0f);
    12.     }
    13. }
    14.  
    Here's a weapon implementation we may want for our ship.
    Code (csharp):
    1.  
    2. public class LaserCannon : IWeapon
    3. {
    4.     // these variables are tweakable for every injected instance in the editor
    5.     public float fireDelay = 1;
    6.     public GameObject owner;
    7.     public GameObject projectilePrefab;
    8.  
    9.     private float timeSinceLastShoot;
    10.    
    11.     public void Shoot()
    12.     {
    13.         if (Time.time > fireDelay + timeSinceLastShoot) {
    14.             GameObject.Instantiate(projectilePrefab, owner.transform.position, Quaternion.identity);
    15.             timeSinceLastShoot = Time.time;
    16.         }
    17.     }
    18. }
    19.  
    It would be GREAT if we could plug in any movement or weapon mechanism we like for our ship by drag-and-dropping an implementation from the assets folder into the ship monobehavior. After it is injected, then we can tweak its serializable properties. Here's how I imagine it would look like in the editor.

    $workflow.png

    Of course, if the injected script also had an injectable field, we could keep injecting deeper until we hit a script with no injectable field, or simply stop injecting at any point so that the injectable field's value stays null. But let's get the basics down first!

    What do you guys think? I feel like this would open so many possibilities for coding things like Behavior Trees, Steering Behaviors... and so many other things!
     
  3. Lucas-Meijer

    Lucas-Meijer

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    175
    You can get very close to this by making IWeapon a class that derives from ScriptableObject.
    Unfortunatttely implementing poly morphism for non scriptableobjects in the serialization system is very hard. (I know, I tried to implement it a few times :)

    Bye, L
     
  4. Lucas-Meijer

    Lucas-Meijer

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    175
    I should add that in the next unity release that will come down the pipe at some point, you'll find a new feature called serialization callbacks, that you can use to implement features like this into the serialization system yourself.
     
  5. Wenzil

    Wenzil

    Joined:
    Apr 3, 2013
    Posts:
    19
    Thanks for the reply, that was fast!

    I think that the two approaches you mention have some serious drawbacks.

    Maybe I'm doing something wrong but for me, making IWeapon a ScriptableObject doesn't make it possible to drag-and-drop IWeapon derived scripts (from the assets folder) into a IWeapon field of another script (in the inspector). If I make IWeapon a MonoBehavior, then create a prefab and add a IWeapon derived monobehavior in it, and then drag-and-drop that prefab in the IWeapon field of another script, THEN it works. But this is really cumbersome and unusable in most use cases in my opinion.

    For instance, say we have a BehaviorTree monobehavior which has a IBehaviorNode root, then that root may be a Sequence which is just an IBehaviorNode containing an array of IBehaviorNode children, and so on. In a design like this, each IBehaviorNode in the tree would have to be wrapped by its own prefab in the assets folder. An Attack behavior could have a float 'damage' serializable property, but to edit it you'd first have to find and select the exact wrapper prefab. Given how many AI behaviors there can be in a game, things would get messy real quick. Especially since injecting a prefab in a script doesn't clone the prefab and its components, but instead simply injects the reference. So the AI designer would have to be careful not to use the same behavior node in more than one behavior tree (so as to not share internal behavior state).

    So that seems like a bad design then! But with dependency injection as described in my original post, all those problems are gone. You can edit in-place every behavior node in the behavior tree directly in the BehaviorTree inspector. Editing a behavior tree in the inspector is not ideal but it was my initial goal when I was trying to design this. It certainly would be better than having the tree split across dozens of prefabs.

    The serialization callbacks solution is also insufficient in most use cases such as the BehaviorTree example just because of the sheer number of IBehaviorNodes that you would have to write custom serialization code for.

    Polymorphic serialization is probably challenging indeed, but I think it's definitely worth it. I may be able to help by pointing you toward this framework which achieves it nicely :) http://simple.sourceforge.net

    Thanks for your time!
     
  6. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    Would be nice ;)

    But until then for what you are trying to achieve MonoBehaviour is king of the unity system in regards to inspector injection/serialization and interfacing is best achieved by having a base class MonoBehaviour acting as a "interface" and then derived classes dont have to be prefabs they are just added as components. Your main class (ship in your case ) would then sniff for these components via their base type and so you handle them like they would a interface.
     
  7. Wenzil

    Wenzil

    Joined:
    Apr 3, 2013
    Posts:
    19
    @shaderbytes

    I see what you're saying. However this way of doing things just feels less "designer friendly" to me, because the designer needs to know that the Ship component will sniff appropriate IWeapon and IMovement components. This information is buried in the code.

    Another issue is that it becomes trickier to structure things. For example if Ship could be injected with multiple weapons where the ordering matters (to indicate upgrade progression perhaps). Then, naively sniffing the IWeapon components with something like GetComponents<IWeapon>() would return the weapons in some unknown order I'd think.

    Not to mention that being forced to inherit MonoBehaviour all the time is a bit irritating in itself. These issues would all be solved if serialization of polymorphic types were to be implemented. :)
     
    Last edited: Dec 22, 2013
  8. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    What I think is, it's exactly how I'm working. But I don't use drag'n'drop... Silly thing is too slow.
    I got a "+" sign on any [CreateDerived] flagged properties. Pressing the "+" popup a list of the class that derive from that base type.
    You choose it, and it creates an instance of that type and it set it in that property.

    $GkI1GLT.png

    I basically got an unlimited number of depth with objects within objects.

    I got it working for lists too.

    $1t5WCM3.png

    However... I don't think I support Interfaces yet. Just abstract and normal classes. Shouldn't be too hard to make it work with interfaces too. (As long as Unity serialized interfaced fields.)
     
  9. Wenzil

    Wenzil

    Joined:
    Apr 3, 2013
    Posts:
    19
    Yes, this is pretty much it! I'm really curious how you got it to work. Are your base abstract classes ScriptableObjects? How do you expose their serialized properties in the inspector? I assume you use Reflection to generate the list of derived classes? Do you have to duplicate code in order for it to work in other scripts, or does simply flagging fields with [CreateDerived] and the class with [Serializable] do the job?

    I'm rusty on editor scripting. :)

    PS I still think this feature should be officially integrated in Unity!
     
  10. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Sadly, the hard part was; I basically rewrote the Inspector. However, it also means it allowed me to integrates tons of features I wanted, such as Property exposition.

    Here's a list of the attributes I built-in it;


    - AllowSceneObject; Object field allow in-scene picking
    - Bypass; Allow Unity native class to be handled by the Inspector the same way as my own script. Unlimited depth within their own type.
    - CreateDerived; Object field is transformed into a instance creator/destructor for derived type.
    - Descriptor; Change the name of the property, and all tooltip display. Also support icons.
    - DisplayAsParent; Nested object are displayed as being part of the parent structure.
    - Expandable; Make any class expandable in the inspector... Even ScriptableObject or MonoBehaviour, from object picker or create derived field.
    - FieldEditor; Allow to make a specific type's field display a specific way. Similar to PropertyDrawer, but support GUILayout.
    - Help; Runtime display of an help box.
    - HideFields; Hide all the fields of a class. Because I'm lazy.
    - Inspect; Expose a property. Pass parameters allow to hide or show the property based on some condition.
    - MaskedEnum; Turn an Enum field into a Bitfield (Mask) field.
    - ReadOnly; Make a field uneditable. Useful to display internal values without fear of someone messing them up.
    - RestrictProperty; Turn any field type into a list fed by a bound method. Useful when you want to restrict a value to a specific list of choice.
    - RuntimeResolve; The field display is based on the current type of the object, instead of the type of the field holding it.

    Or other nice feature like sortable list;

    $MsfXesV.png

    I can also inspect object that derive from System.Object, unlike the standard Inspector.
    Or I can expose method in the form of a button.

    And the ability to wrap the Inspector in other EditorWindow;

    $SQGoWoo.png

    This is a bundle editor I made. The right side is a "ExternalEditor" that allow me to draw the same as the Inspector would do, just about everywhere. You pass what is the selected item and a region, and it draws itself.

    An example of one implementation of the "CreateDerived";

    Code (csharp):
    1.  
    2. [SerializeField]
    3. private CameraFieldOfView fieldOfView;
    4.  
    5. [Inspect]
    6. [CreateDerived]
    7. public CameraFieldOfView FieldOfView
    8. {
    9.     get { return fieldOfView; }
    10.     set { fieldOfView = value; }
    11. }
    12.  
    In this case, the Inspect attribute means my inspector is exposing that property. It can receives a collection of parameters to hide or show that property depending on some condition.

    Example;

    Code (csharp):
    1.  
    2. [Inspect("Type", PathType.Linear)]
    3. public int Precision
    4.  
    This property is displayed only when the other property named "Type" has its value as "PathType.Linear".

    CreateDerive is enough to change the field from a "Object Picker" field to a "+" icon.

    At first, I did use ScriptableObject. However, they are not working for Prefab or duplication of the GameObject. In the end, I created

    Code (csharp):
    1.  
    2. public abstract class ComponentMonoBehaviour : MonoBehaviour
    3.  
    Which is an automatically hidden MonoBehaviour. The Inspector adds it to the GameObject and flag it to be invisible on its own, it can only be seen as being part of another object. It also managed the destruction of sub-objects in the event it get deleted. So I can have unlimited nesting of those "Component".

    Basically, that's the only two steps needed; an attribute and having your class derive from ComponentMonoBehaviour. Everything else is handled by the custom Inspector.
     
    Last edited: Dec 23, 2013
  11. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    Thats quite a system you have there ;) looking at your sample code :

    Code (csharp):
    1. [SerializeField]
    2.  
    3. private CameraFieldOfView fieldOfView;
    4.  
    5.  
    6.  
    7. [Inspect]
    8.  
    9. [CreateDerived]
    10.  
    11. public CameraFieldOfView FieldOfView
    12.  
    13. {
    14.  
    15.     get { return fieldOfView; }
    16.  
    17.     set { fieldOfView = value; }
    18.  
    19. }
    Field of view would normally be a float value. In your example its typed to a custom Type "CameraFieldOfView ", Do you have a class "CameraFieldOfView " with just one property in it? Sorry if its a dumb question im just trying to grasp what you have done here. I have never used custom attributes myself yet
     
    Last edited: Dec 23, 2013
  12. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Using this camera system as example was maybe not a good idea. It is, from my experience, the most powerful game camera system in existence, but it's not the most straight forward one. I didn't design it, someone else did in another video game engine, and I just ported it to Unity.

    I actually have a few class that derive from that base class (CameraFieldOfView).

    I have one that only have a fixed value. (CameraFixedFieldOfView)
    I have one that modify the field of view based on the speed of a target. (CameraVelocityFieldOfView)
    I have one that follow a curves, for when you want to add some effect in a scripted cinematic. (CameraCurvedFieldOfView)
    etc.

    Even the "Fixed" version is useful, because the system support full transition between camera "context".

    This base class exist so that anybody could create a "Field of View Modifier" by deriving from it.
    It's the same way with Positionners, Orienters, Dampers, Shakers and so on.

    When someone creates a new camera, he only needs to pick the pieces he needs. Kinda like a collection of lego blocks.

    When I press that "+" button, I get:

    $7wn8j9A.png

    In this case, I pressed "+" on a CameraPositionner field. This panel listed me all the non-abstract class that derive from this base class. When I select one, it creates an instance of that type and assign it to the field I clicked.

    $BSXMUvN.png
     
    Last edited: Dec 23, 2013
  13. shaderbytes

    shaderbytes

    Joined:
    Nov 11, 2010
    Posts:
    900
    Ah ok thanks I see it has purpose being a complex object in that way.

    I want to learn to use custom attributes, a quick search gave me this page :

    http://msdn.microsoft.com/en-us/library/aa288454(VS.71).aspx

    sample from there


    Code (csharp):
    1. using System;
    2. [AttributeUsage(AttributeTargets.All)]
    3. public class HelpAttribute : System.Attribute
    4. {
    5.    public readonly string Url;
    6.  
    7.    public string Topic               // Topic is a named parameter
    8.    {
    9.       get
    10.       {
    11.          return topic;
    12.       }
    13.       set
    14.       {
    15.  
    16.         topic = value;
    17.       }
    18.    }
    19.  
    20.    public HelpAttribute(string url)  // url is a positional parameter
    21.    {
    22.       this.Url = url;
    23.    }
    24.  
    25.    private string topic;
    26. }
    Is this kind of how you did it?

    I have not played with the new editor features in unity like property drawers yet. From the few times I have briefed over the info concerning them I did see they had some or other means of supporting custom attibutes..

    http://docs.unity3d.com/Documentation/ScriptReference/PropertyAttribute.html

    Not sure if this is the same kind of attibutes as in the previous link
     
  14. Wenzil

    Wenzil

    Joined:
    Apr 3, 2013
    Posts:
    19
    @LightStriker

    This is incredibly cool. If you haven't already, you should definitely think about publishing your custom inspector on the Assets Store. If I had money, I would buy it in a heartbeat! The functionality of your [CreateDerived] attribute does exactly what I'm looking for.

    That said, if Unity supported serialization of interfaced fields it would be even better. Forcing injected scripts to derive from MonoBehaviour must add some overhead somewhere, right? Plus it's just nicer to not depend on them :)

    With your inspector, that's just nitpicking, but with the default inspector, it would allow dependency injection without a full inspector rewrite!
     
    Last edited: Dec 23, 2013
  15. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Yes. That's pretty much how you create a new attribute, and how all mines are made.

    I stayed away from PropertyDrawer because they were too limited for what I had in mind.

    An attributes is a "tag" that you place on top of a definition (class, field, method, property, etc). Once the code is "compiled", you can retrieve the definition of pretty much anything and see which attributes were place on them. It's called "Reflection", or if you want, a way to see how the code was written while the code is running.

    http://msdn.microsoft.com/en-us/library/f7ykdhsy(v=vs.110).aspx

    It's insanely powerful. It's not a Unity feature, by the way. Any CLR/.NET language support reflection.

    There's almost no overhead when you use MonoBehaviour versus... something deriving from System.Object. It does have an overhead in memory, but if I remember some older stuff I've seen, we are talking about a few bytes of memory adressing. As for CPU overhead, as long as you don't implement any Update method, there's no difference.

    Totally forget to check, but I'm pretty sure I support interfaces as field definition, not just classes. I'll check that tomorrow.
     
    Last edited: Dec 23, 2013
  16. i_am_kisly

    i_am_kisly

    Joined:
    Feb 16, 2015
    Posts:
    22
    how get this selectable list or table ? As in the example of LightStriker.
    Screenshot_20170326_130324.png