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

benefits of DI in Unity?

Discussion in 'Scripting' started by Gohan1, Mar 3, 2016.

  1. Gohan1

    Gohan1

    Joined:
    Nov 20, 2015
    Posts:
    28
    I recently heard of DEPENDENCY INJECTION(DI) and I learned that when you apply it to your project SOLID principles will just come naturally. what problems in unity are solved by using DI. specifically it's component model architecture when it comes to large projects. or what problems have you or your team encountered in unity that made you decide to use DI and what happened after you used it? do you recommend it for someone like me who is in the process of making a medium to large project that requires webservices, cloud database?
     
  2. bajeo88

    bajeo88

    Joined:
    Jul 4, 2012
    Posts:
    64
    Used it in a project about 3 1/2 years ago as the project manager was a big fan of DI.

    It did help on our medium sized project as you can outline the entire structure in code injecting mock classes in areas which are not yet developed. Other huge advantages for us were the ability to completely remove classes/methods by restoring them to their initial mocked responses which actually helped us track down a few tricky bugs quicker than we should have.

    Down sides for us, we were on a very tight schedule and so after a while the 'mock' classes were completely ignored or not even created for some systems making it pretty much unused.

    Our project was entirely based inside unity so we could cope without DI quite well, I can imagine if your using web services etc... DI will be more useful for you. Someone else may have other opinions on DI, for me it does have its uses but if you are seeing the project as something long term which you will update lots it will be useful.

    Hope this helps to some extent.
     
  3. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    I would definitely recommend you using Dependency Injection pattern for external services you integrate in your project.

    For example, imagine using Photon Networking for your games network solution. If you start making your game from ground up with Photon, you will eventually wound up with hundreds of Photon class references within your classes. That is bad, because if you decide to switch to another service, or Photon gets shut down, you will end up with a broken project.

    Thats why you declare an INetworkingPlugin interface. This is what I use for my project:
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4.  
    5. public interface INetworkingPlugin
    6. {
    7.     bool OfflineMode {set; get;}
    8.     bool IsDisconnected { get; }
    9.     bool IsConnected { get; }
    10.     bool IsEligibleToSearchForOpponent { get; }
    11.     bool IsInRoom { get; }
    12.     bool IsMasterClient { get; }
    13.  
    14.     event Action OnDisconnect;
    15.     event Action OnJoinedLobbyEvent;
    16.     event Action OnConnectedToMasterEvent;
    17.     event Action OnCreatedRoomEvent;
    18.     event Action OnJoinedRoomEvent;
    19.     event Action OnJoinRoomFailedEvent;
    20.     event Action OnJoinRandomRoomFailedEvent;
    21.  
    22.     event Action<object> OnPlayerConnectedEvent;
    23.  
    24.     event Action OnFriendsFoundEvent;
    25.  
    26.     void Connect(string appVersion);
    27.     void Disconnect();
    28.     void JoinRandomRoom();
    29.     void CreateRoom(string roomName);
    30.     void LeaveRoom();
    31. }
    Now, you just create a PhotonNetworkPlugin class that implements this interface:
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4.  
    5. public class PhotonNetworkingPlugin : MonoBehaviour, INetworkingPlugin
    6. {
    7.     public event Action OnDisconnect;
    8.     public event Action OnJoinedLobbyEvent;
    9.     public event Action OnConnectedToMasterEvent;
    10.     public event Action OnCreatedRoomEvent;
    11.     public event Action OnJoinedRoomEvent;
    12.     public event Action OnJoinRoomFailedEvent;
    13.     public event Action OnJoinRandomRoomFailedEvent;
    14.  
    15.     public event Action<object> OnPlayerConnectedEvent;
    16.  
    17.     public event Action OnMatchFoundEvent;
    18.     public event Action OnFriendsFoundEvent;
    19.  
    20.     public bool OfflineMode
    21.     {
    22.         set { PhotonNetwork.offlineMode = value; }
    23.         get { return PhotonNetwork.offlineMode; }
    24.     }
    25.  
    26.     public bool IsDisconnected
    27.     {
    28.         get { return PhotonNetwork.connectionState == ConnectionState.Disconnected; }
    29.     }
    30.  
    31.     public bool IsConnected
    32.     {
    33.         get { return PhotonNetwork.connected; }
    34.     }
    35.  
    36.     public bool IsEligibleToSearchForOpponent
    37.     {
    38.         get { return PhotonNetwork.connectedAndReady; }
    39.     }
    40.  
    41.     public bool IsInRoom
    42.     {
    43.         get { return PhotonNetwork.room != null; }
    44.     }
    45.     public bool IsMasterClient
    46.     {
    47.         get { return PhotonNetwork.isMasterClient; }
    48.     }
    49.  
    50.     void Start()
    51.     {
    52.  
    53.     }
    54.  
    55.     void INetworkingPlugin.Connect(string appVersion)
    56.     {
    57.         PhotonNetwork.ConnectUsingSettings(appVersion);
    58.     }
    59.  
    60.     void INetworkingPlugin.Disconnect()
    61.     {
    62.         PhotonNetwork.Disconnect();
    63.     }
    64.  
    65.     void INetworkingPlugin.JoinRandomRoom()
    66.     {
    67.         PhotonNetwork.JoinRandomRoom();
    68.     }
    69.  
    70.     void INetworkingPlugin.CreateRoom(string roomName)
    71.     {
    72.         PhotonNetwork.JoinOrCreateRoom(roomName, null, null);
    73.     }
    74.  
    75.     void INetworkingPlugin.LeaveRoom()
    76.     {
    77.         PhotonNetwork.LeaveRoom();
    78.     }
    79.  
    80.     #region IPunCallbacks
    81.  
    82.     void OnConnectedToPhoton()
    83.     {
    84.     }
    85.  
    86.     void OnLeftRoom()
    87.     {
    88.     }
    89.  
    90.     void OnMasterClientSwitched(PhotonPlayer newMasterClient)
    91.     {
    92.     }
    93.  
    94.     void OnPhotonCreateRoomFailed(object[] codeAndMsg)
    95.     {
    96.     }
    97.  
    98.     void OnPhotonJoinRoomFailed(object[] codeAndMsg)
    99.     {
    100.         if (OnJoinRoomFailedEvent != null)
    101.             OnJoinRoomFailedEvent();
    102.     }
    103.  
    104.     void OnCreatedRoom()
    105.     {
    106.         if (OnCreatedRoomEvent != null)
    107.             OnCreatedRoomEvent();
    108.     }
    109.  
    110.     void OnJoinedLobby()
    111.     {
    112.         if (OnJoinedLobbyEvent != null)
    113.             OnJoinedLobbyEvent();
    114.     }
    115.  
    116.     void OnLeftLobby()
    117.     {
    118.     }
    119.  
    120.     void OnFailedToConnectToPhoton(DisconnectCause cause)
    121.     {
    122.     }
    123.  
    124.     void OnDisconnectedFromPhoton()
    125.     {
    126.         if (OnDisconnect != null)
    127.             OnDisconnect();
    128.     }
    129.  
    130.     void OnConnectionFail(DisconnectCause cause)
    131.     {
    132.     }
    133.  
    134.     void OnPhotonInstantiate(PhotonMessageInfo info)
    135.     {
    136.     }
    137.  
    138.     void OnReceivedRoomListUpdate()
    139.     {
    140.     }
    141.  
    142.     void OnJoinedRoom()
    143.     {
    144.         if (OnJoinedRoomEvent != null)
    145.             OnJoinedRoomEvent();
    146.     }
    147.  
    148.     void OnPhotonPlayerConnected(PhotonPlayer newPlayer)
    149.     {
    150.         //TODO: ovdje convertati u neki generic player class
    151.         if (OnPlayerConnectedEvent != null)
    152.             OnPlayerConnectedEvent(newPlayer);
    153.     }
    154.  
    155.     void OnPhotonPlayerDisconnected(PhotonPlayer otherPlayer)
    156.     {
    157.     }
    158.  
    159.     void OnPhotonRandomJoinFailed(object[] codeAndMsg)
    160.     {
    161.         if (OnJoinRandomRoomFailedEvent != null)
    162.             OnJoinRandomRoomFailedEvent();
    163.     }
    164.  
    165.     void OnConnectedToMaster()
    166.     {
    167.         if (OnConnectedToMasterEvent != null)
    168.             OnConnectedToMasterEvent();
    169.     }
    170.  
    171.     void OnPhotonMaxCccuReached()
    172.     {
    173.     }
    174.  
    175.     void OnPhotonCustomRoomPropertiesChanged(ExitGames.Client.Photon.Hashtable propertiesThatChanged)
    176.     {
    177.     }
    178.  
    179.     void OnPhotonPlayerPropertiesChanged(object[] playerAndUpdatedProps)
    180.     {
    181.     }
    182.  
    183.     void OnUpdatedFriendList()
    184.     {
    185. //        foreach (FriendInfo info in PhotonNetwork.Friends)
    186. //        {
    187. //            Debug.Log("Found Friend:"+info.Name+" Online:"+info.IsOnline+" InRoOm:"+info.IsInRoom);
    188. //        }
    189. //      
    190.         if (OnFriendsFoundEvent != null)
    191.             OnFriendsFoundEvent ();
    192.     }
    193.  
    194.     void OnCustomAuthenticationFailed(string debugMessage)
    195.     {
    196.         Debug.Log("Custom authentication failed:"+debugMessage);
    197.     }
    198.  
    199.     /// <summary>
    200.     /// Called by PUN when the response to a WebRPC is available. See PhotonNetwork.WebRPC.
    201.     /// </summary>
    202.     /// <remarks>
    203.     /// Important: The response.ReturnCode is 0 if Photon was able to reach your web-service.
    204.     /// The content of the response is what your web-service sent. You can create a WebResponse instance from it.
    205.     /// Example: WebRpcResponse webResponse = new WebRpcResponse(operationResponse);
    206.     ///
    207.     /// Please note: Class OperationResponse is in a namespace which needs to be "used":
    208.     /// using ExitGames.Client.Photon;  // includes OperationResponse (and other classes)
    209.     ///
    210.     /// The OperationResponse.ReturnCode by Photon is:<pre>
    211.     ///  0 for "OK"
    212.     /// -3 for "Web-Service not configured" (see Dashboard / WebHooks)
    213.     /// -5 for "Web-Service does now have RPC path/name" (at least for Azure)</pre>
    214.     /// </remarks>
    215.     void OnWebRpcResponse(ExitGames.Client.Photon.OperationResponse response)
    216.     {
    217.     }
    218.  
    219.     void OnOwnershipRequest(object[] viewAndPlayer)
    220.     {
    221.     }
    222.  
    223.     #endregion
    224. }
    And in your networking code, you just use:
    Code (CSharp):
    1. public INetworkingPlugin Net;
    2.  
    3. void Start()
    4. {
    5.      Net = gameObject.AddComponent<PhotonNetworkingPlugin>();
    6.  
    7.      Net.OnCreatedRoomEvent += HandleOnCreatedRoomEvent;
    8.      Net.OnJoinedRoomEvent += HandleOnJoinedRoomEvent;
    9.      Net.OnJoinRandomRoomFailedEvent += HandleOnJoinRandomRoomFailedEvent;
    10.      Net.OnPlayerConnectedEvent += HandleOnPlayerConnectedEvent;
    11.  
    12.      Net.Connect("0.1");
    13. }
    This will now enable you to implement other services, maybe like DarkRiftPlugin or ForgeNetworkPlugin and easily swap out on the fly if you don't want to use one. You can also provide a mock class that will act as a real one in order to test out features. Pretty cool, huh?

    Hope this wall of text helped in understanding the DI use better!
     
    CrazyD0G and Gohan1 like this.
  4. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    DI style approaches only suit experienced developers though, and even then you'd have to document it and it's only useful for teams. There's no practical purpose to it for small teams.
     
    Kiwasi and bajeo88 like this.
  5. Gohan1

    Gohan1

    Joined:
    Nov 20, 2015
    Posts:
    28
    thanks @bajeo88 so it helped you but I took time.

    @Fajlworks thank you for the example. Your code is so clean and lean; I can see that when I want to switch to another service I could just make another implementation INetworkingPlugin without really changing the consumer. I could not imagine the pain if I were to concretely connect the consumer to photon and decide to change or it dies which puts me in a dilemma because @hippocoder mentioned that
    which contradicts to this very example even for a small team.
     
  6. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Like every design pattern, it has its uses where it shines and where it is probably overkill, regardless of team size. I also use the same approach for backend; I have a IBackendPlugin which requires methods like Login, Register, Query, etc. So for evaluating purposes, I created KiiPlugin, GamedoniaPlugin and PlayfabPlugin to test features out before deciding, without breaking the project with dependencies.

    Other purposes of DI, what I can think of, could be for custom game modes, or adding behaviour; like having MoveController listen to IHandleInput.
    Code (CSharp):
    1. public interface IHandleInput
    2. {
    3.      void ProcessInput(Transform obj);
    4. }
    5.  
    6. public class KeyboardInput : MonoBehaviour, IHandleInput
    7. {
    8.      public float moveSpeed = 1f;
    9.      public float turnSpeed = 2f;
    10.  
    11.      void IHandleInput.ProcessInput(Transform obj)
    12.      {
    13.           if(Input.GetKey(KeyCode.UpArrow))
    14.             obj.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
    15.        
    16.         if(Input.GetKey(KeyCode.DownArrow))
    17.             obj.Translate(-Vector3.forward * moveSpeed * Time.deltaTime);
    18.        
    19.         if(Input.GetKey(KeyCode.LeftArrow))
    20.             obj.Rotate(Vector3.up, -turnSpeed * Time.deltaTime);
    21.        
    22.         if(Input.GetKey(KeyCode.RightArrow))
    23.             obj.Rotate(Vector3.up, turnSpeed * Time.deltaTime);
    24.      }
    25. }
    26.  
    27. public class BasicAI : MonoBehaviour, IHandleInput
    28. {
    29.      public Target followTarget
    30.      public float moveSpeed = 1f;
    31.  
    32.      void IHandleInput.ProcessInput(Transform obj)
    33.      {
    34.            obj.LookAt (followTarget);
    35.            obj.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
    36.      }
    37. }
    Now you can reuse MoveController component for both your player and for AI, with each component doing its own logic. BasicAI can be use for testing purposes as a mock component until you design an elaborate AI that will process logic inside a coroutine and then report back to ProcessInput. But, then again, there are other ways of solving problems. It just depends which one you're most comfortable with :)
     
  7. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I might not be as much experienced as others (only 3-4 years in programming, 2 of them in C++, 2 in C#, Java, JavaScript etc) to already know all the downsides, but here's my personal point of view:

    I use it for stuff that is more likely to change over time (It doesn't necessarily need to be external plugins/framworks) or for which i need to test different implementations (for convenience vs performance and other stuff), just like @Fajlworks has pointed out. Or even really trivial mock classes that represent behaviour that is actually more complex to implement but is currently not on my current time schedule (I don't really have a fixed one in my personal projects, but let's ignore that :D).

    I also like it due to the fact that it really contributes to achieve lower coupling and allows for huge yet quick changes in projects.

    A really small and trivial example (not quite Unity related): We recently had a small university project (java based) that we had worked on in a small team. Sometimes everyone just worked at home, sometimes in university. Not everyone wanted to install MySQL on his personal machine and access (via VPN) to the personal MySQL instance on the University server wasn't possible all the time.

    So we've written an implementation that offered some kind of in-memory database, nothing complex, just some deterministicly generated records, allowing us to test the features we've implemented that required a 'backend'. It also allowed us to test different approaches like pure JDBC or the more convenient JPA and some funny stuff like persistent file based storage systems. So we could easily make use of another implementation/technology with a small change in the whole program and everything kept working, but with a completely different backend.

    Another example, more Unity related: For quite some time I've been working on some network system with optimizations for my needs for several hundreds to thousands of synchronizations in order to reduce bandwith and such. Well, I soon noticed it's gonna be really complex so it's the most easy part if you do not rely on specific implementations but rather on a general interface that defines the communication schemes and throw in dummy classes that behave as if the system already worked. Everything controlled from the outside. It's just too easy to exchange a part of the system.

    I still combine it with some kind of manager/factory class, it's also (imo extremely) useful in combination with MVC/MVP and observer patterns, probably some others too.
     
    Last edited: Mar 4, 2016
  8. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    I'm not sure that I agree that DI is useful only for non-small teams. DI is a useful concept regardless of size because it forces developers to expose dependencies publicly. This naturally lends itself to favoring small, modular class design. A bane of projects is the junk that can accumulate. When you have dependencies hidden deep inside of large classes, it can be difficult to grasp the exact magnitude of any given change.

    DI requires that we expose all external dependencies when building a class. Done right, there are no (or few) globally accessible singletons or static methods. So I don't have to worry about some developer sitting in the middle of an Update method and thinking, "I'll just add a dependency to some really expensive singleton here to get the data that I need!" Instead, that developer will have to go through the whole overhead of declaring his dependency to the appropriate interface or class (we allow concrete depedencies for some things), and making sure that his class is properly instantiated via the DI container.

    Exposing dependencies to be filled in by an external source is also great for testing. Testing is something that hasn't quite permeated the Unity culture yet, due to the difficulty of testing MonoBehaviours. But it should.

    Making use of a DI container isn't easy, though. And, like any tool, it can be used inappropriately. Most developers new to DI containers use them solely to replace their singleton implementations. It takes time for the mindset of small, modular classes to take root. Once that happens, though, things get better, and using DI becomes fast and awesome.
     
  9. Gohan1

    Gohan1

    Joined:
    Nov 20, 2015
    Posts:
    28
    thank you so much everyone. I learned a lot from you guys and it helped me decide to invest in learning DI since most of you got positive results with it in unity. I sure hope this will become another step for me to become a unity programmer.
     
  10. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Well OK it can be used for a small team, if that team is unsure of what is happening. Generally from my experience, I'm pretty sure I know what is happening with the code base, so I haven't any need for it in a Unity game development context. I do respect the comments made though. Just echoing personal thoughts on it.

    Not really, no. It'll add confusion at this point... not sure what it actually has to do with Unity. It's a design pattern and you will end up using design patterns regardless if it's in Unity or something else.

    To get good at Unity, you need solid C# skills and a clear understanding of the Unity API.
     
  11. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    I concur. Inversion of Control and Dependency Injection containers are very advanced topics which can wait until later in an engineer's career.
     
    MV10 likes this.
  12. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    Yes, this... Basically if someone is asking why they should use it, they probably shouldn't be using it in a real project yet. I found it extremely handy in one-man-band projects (complex true-REST web apps, mainly -- not the half-assed MVC stuff but full-on Roy Fielding REST) but I would think you'd need an extremely complicated Unity project to really benefit from the added design-overhead.

    And it's definitely something you decide to use day one, you won't be throwing DI into the mix on anything already in progress, that's a refactoring disaster waiting to happen.