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. Dismiss Notice

[DependencyAttributes] Atreebooster DI

Discussion in 'Assets and Asset Store' started by kubpica, Sep 21, 2020.

  1. kubpica

    kubpica

    Joined:
    Apr 2, 2018
    Posts:
    3
    Dependency management has been controversial since Unity's inception, everyone has their own way of doing it. Some use sophisticated frameworks, others believe that Unity already has IoC built into it. I decided to increase the controversy even further by creating my own dependency management tool. It works perfectly in my project, what do you think about it?

    # [DependencyAttributes] Atreebooster DI
    The Hierarchy-based Dependency Injection tool.
    Intuitivly manage dependencies of your MonoBehaviours with simple but powerfull [Attributes]:
    - [Component] - automatically finds the most likely component and injects the marked field with it.
    - [GlobalComponent] - finds the singleton or traverses scene GameObjects looking for the component of the marked field type.
    - [ChildComponent] - finds the component in children (descendants).
    - [ParentComponent] - finds the component in parents (predecessors).
    - [SiblingComponent] - finds the component in siblings.
    - [FamilyComponent] - searches for the component starting from parent down along the hierarchy.
    - [FamilyComponent(true)] - find the component in own tree (form root down along the hierarchy).
    - [OwnComponent] - looks for the component only in itself (own GameObject).
    - [ReferenceComponent] - looks for the component in GameObjects marked [ReferencePoint].
    - [Child(name/index)] - Finds the GameObject by name in children (descendants) or gets one by index.
    - [Parent(name/index)] - Finds the GameObject by name in parents (predecessors) or gets one by index.
    - [Sibling(name/index)] - Finds the GameObject by name in siblings or gets one by index.
    - [Reference(name)] - Finds closest in the hierarchy GameObject by name.
    - [Root] - Gets own root GameObject or finds one by name if specified.

    Named parameters:
    - string Of - Name of a GameObject to find and apply the attribute's algorythm to. If there are multiple GameObjects with the same name, it finds the closest one in the hierarchy.
    - int Offset - Offset in hierarchy from "Of" GameObject.
    - bool Optional - If false, new component/gameobject is created when it's not found; otherwise just warning is displayed and dependency is not injected. (false by default)
    - bool SkipItself - If true, own GameObject is skiped; otherwise included in search for the component. (false by default)

    It works with multiple scenes loaded and DDOL objects.
    It also provides MonoBehaviourSingleton allowing you to access the script from anywhere like this: SoundManager.Instance; or [GlobalComponent] SoundManager soundManager;

    # Usage
    Copy MonoBehaviourExtended.cs and MonoBehaviourSingleton.cs to your project from https://github.com/kubpica/AtreeboosterDI/tree/master/Assets/AtreeboosterDI

    Derive from MonoBehaviourExtended instead of MonoBehaviour. It provides the hierarchy based dependency injection attributes.
    If you want to use Awake() in your script, hide the method (with the 'new' keyword) and call base.Awake();

    If you want your script to be Singleton derive from MonoBehaviourSingleton<T> like this:
    ```c#
    public class SoundManager : MonoBehaviourSingleton<SoundManager> {}
    ```
    Place it anywhere in the scene and then you can access it from any script like this: SoundManager.Instance; or [GlobalComponent] SoundManager soundManager;

    # Known issues and tips
    - If you try to inject a GameObject (or Unity's built in component - non-MonoBehaviour) it should be private or marked with [NonSerialized], otherwise it may conflict with the Unity serializer and the dependency may not be injected. MonoBehaviours can be public.
    - Using the [Component] attribute is convenient but you will probably get better performance if you use the more specific ones. You still can use it, just be carefull in extreme cases.
    - If you want to use the [GlobalComponent] attribute with non-MonoBehaviourSingleton<T> components it's better to put them in one of the root gameobjects; otherwise it may be expensive.

    # Licence
    MIT but credit or review on Unity Asset Store would be nice ;)
    Do you like my work? Consider buying my game on Steam - it's not even $1 but it means a lot to me! ;) https://store.steampowered.com/app/1398130/Head_Bumper_Editcraft/

    # Github
    https://github.com/kubpica/AtreeboosterDI

    # Unity Asset Store version
    https://assetstore.unity.com/packages/tools/integration/dependencyattributes-atreebooster-di-157631
     
  2. moroc

    moroc

    Joined:
    Jan 20, 2014
    Posts:
    12
    hi, can you provide example for [ReferenceComponent]?
     
  3. kubpica

    kubpica

    Joined:
    Apr 2, 2018
    Posts:
    3
    Hi, sorry for the late reply. Here's how [ReferenceComponent] and [ReferencePoint] work:
    Code (CSharp):
    1. public class ReferenceTest : MonoBehaviourExtended
    2. {
    3.     [ReferencePoint]
    4.     public GameObject go;
    5.  
    6.     [ReferencePoint]
    7.     public GameObject go2;
    8.  
    9.     [ReferencePoint]
    10.     public GameObject go3;
    11.  
    12.     [ReferenceComponent] // It will look for the Inventory in go, go2 and go3
    13.     private Inventory inventory;
    14.  
    15.     [ParentComponent(Of = "go2")] // It will look for the Inventory in go2 and then its parents
    16.     private Inventory inventory2;
    17.  
    18.     [ChildComponent(Of = "go3", SkipItself = true)] // It will look for the Inventory in children of go3
    19.     private Inventory inventory3;
    20. }
    21. public class HierarchyTest : MonoBehaviourExtended
    22. {
    23.     [OwnComponent] // It will look for the Inventory in GameObject of the HierarchyTest (this.gameObject)
    24.     private Inventory inventory4;
    25.  
    26.     [Component] // It will look for the Inventory in all active scenes, but search will start from GameObject of HierarchyTest so it will find the Inventory that is "closest" in the Hierarchy
    27.     private Inventory inventory5;
    28.  
    29.     [ChildComponent(Of = "Managers")] // It will try to find GameObject named "Managers" in the scene, and then it will look for the Inventory in it and its children.
    30.     private Inventory inventory6;
    31. }
    [ReferencePoint] is visible only in the class it is used (so in this case it has meaning only in the ReferenceTest class), so it's probably not really that much usefull. If you want to reference a script that is in some other tree in the Hierarchy, then I usually make the script MonoBehaviourSingleton<TheScript> and I inject it with [GlobalComponent] or just [Component]. I know some people hate singletons, but if you really have only one instance of the script in a scene then why not use them? (Especially when you reference them with attribute and not with TheScript.Instance - so you can easily refactor if you decide it is no longer a singleton). And if you have more than one instance of a script then I believe you can organize the Hierarchy in a way that would allow the [Component] attribute find the correct one :)

    [Component] search order: singletons (MonoBehaviourSingleton{T}), [ReferencePoint]s, itself, children, siblings, children of siblings, parent, siblings of parent, children of siblings of parent, go to parent of parent and repeat the algorythm.

    [GlobalComponent] search order: singletons (MonoBehaviourSingleton{T}), root GameObjects, children of roots traversed in alphabetical order.

    [ReferenceComponent] searches for the component in gameobjects defined as fields in the same class and marked with [ReferencePoint].

    Maybe this tool allows for more than it should as relying dependencies on names is probably not the best practice - the main attributes i use are [GlobalComponent] for singletons and [Component] for everything else (alternatively [ChildComponent] or [ParentComponent] if i want to be more specific and prevent it from searching the whole scene in case i forget to attach the component).
     
    Last edited: Feb 6, 2023