Am I doing Dependency Injection Correctly?

I am having to unit test my project for my software engineering class and I am somewhat confused on if I am doing DI (Dependency Injection) correctly. I am very new to this whole process and it’s purely a learning experience for me. I have been told that it is far more difficult to test in games, so any advice will be greatly appreciated.

I have a InputController that requires a dependency on CPIM (CrossPlatformInputManager). I have set up an interface that eliminates the classes dependency on CPIM which if we do not provide an interface it sets up the interface with a class called MyCrossPlatformInputManager.

I want to know if I am doing DI correctly before writing interfaces for the rest of my scirpts and setting them up to be more testable?

Here is the scripts in question:

The playerInputController Class

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class playerInputController : MonoBehaviour {

 

   public IplayerInputController inputInterface;
 
   public playerInputController(IplayerInputController inputInterface)
    {
        this.inputInterface = inputInterface;
    }
    //this is here so that the script will run properly outside of testing.
     public playerInputController() : base()
     {
        if (inputInterface == null)
        {
           inputInterface = new MyCrossPlatformInputManager();
        }
     }


    //variables for Crossplatforminput
    public float Lookh { get; set; }
    public float Lookv { get; set; }
    public float Moveh { get; set; }
    public float Movev { get; set; }
    public float f { get; set; }
    playerController sendInput;
  
   

  
    // Use this for initialization
    void Start () {
        anim = GetComponent<Animator>();
        sendInput = GetComponent<playerController>();
         
    }

    public void getCurrentInput()
    {
        //left joystick or wasd
        Moveh = inputInterface.GetAxisRaw("Horizontal");
        Movev = inputInterface.GetAxisRaw("Vertical");
        //right joystick or arrow keys
        Lookh = inputInterface.GetAxisRaw("HorizontalLook");
        Lookv = inputInterface.GetAxisRaw("VerticalLook");
        //space or right trigger
        f = inputInterface.GetAxisRaw("Fire1");

    }

 

    // Update is called once per frame
    void FixedUpdate () {

        //calls a method that gets our current input
        getCurrentInput();


        //send our inputs to the playercontroller
        if (Mathf.Abs(Moveh) != 0 || Mathf.Abs(Movev) != 0)  sendInput.updatePosition(Moveh, Movev);
      
      
        //update player direction
        if (Mathf.Abs(Lookh) != 0 || Mathf.Abs(Lookv) != 0) sendInput.updateDirection(Lookh, Lookv);

        //gets if we are firing
        if (Mathf.Abs(f) != 0) sendInput.fire();     
     
        if (inputInterface.GetAxisRaw("Grenade") != 0) UsePowerUp(2);
        if (inputInterface.GetAxisRaw("Armor") != 0) UsePowerUp(0);
        if (inputInterface.GetAxisRaw("SpeedBoost") != 0) UsePowerUp(1);
        if (inputInterface.GetAxisRaw("Nuke") != 0) UsePowerUp(3);

    }



    void UsePowerUp(int i)
    {   
        sendInput.UsePowerUp(i);
    }


}

The IPlayerInputController Class

public interface IplayerInputController {

    float GetAxisRaw(string axisName);

}

The MyCrossPlatformInputController class

using UnityStandardAssets.CrossPlatformInput;
public class MyCrossPlatformInputManager : IplayerInputController
{
    public float GetAxisRaw(string axisName)
    {
        return CrossPlatformInputManager.GetAxisRaw(axisName);
    }
}

In a normal class that would work, but it won’t work for a MonoBehaviour, because you’re not supposed to ever call or change the constructor for a MonoBehavior. The reasons why have to do with serialization and are complicated, but if you’re interested in why:

Being unable to use constructors for MonoBehaviours makes DI a little awkward in Unity. I use Zenject and really like it. It’s a free DI Framework designed for Unity:

If you don’t feel like using a whole framework, you could get around it by having a public void Setup(IplayerInputController) method on there that you have to call from somewhere.

playerInputController is a MonoBehaviour and as such its not allowed to have constructors. to do traditional Injection, write your controller as a serializable custom class you can then do test cases that inject normally. You can then write a Monobehaviour that encapsulates that Controller and injects its data in OnEnable and calls its functions in Update (quick note FixedUpdate is not fully reliable for for taking input as Unity’s input flags reset on a per-frame basis and FixedUpdate can be called 0 to multiple times per frame).

To do injection with MonoBehaviours means you want to set its Serilizable Data (either in inspector or code) and the Monobehaviour only works on that data in Start() or OnEnable(), preferbly OnEnable(). doing so gives time for any code thats instantiating the Monobehaviour and wants to inject the data. You can’t use Awake() since it runs immeadiately during the Instantiate call, before your code would have a chance to inject.

You can also hold a private static MyCrossPlatformInputManager so that any controllers that haven’t been given an input controller and quietly default to that.

public class playerInputController : MonoBehaviour
{
private static IplayerInputController Controller_default = new MyCrossPlatformInputManager();
private IplayerInputController m_inputInterface;

public IplayerInputController InputInterface
{
    get { return (m_inputInterface==null)?Controller_default:m_inputInterface; }
    set { m_inputInterface = value; }
}

// ... the rest of the code
}

if you made your IplayerInputController an abstract ScriptableObject asset that would expose a field in the inspector. then you can even specify different types of InputControllers and drop them in the field in the inspector. and as far as Monobehaviours are concerned, setting the fields in the Inspector is practically the same as Dependency Injection. It affords you the mindset that you can make your code really simple (where it doesn’t try to find its data), handling only fail cases (like the injected data is null). and you put all the context data that the class needs exposed in the inspector.

With this mindset, you assume that the Scene is whats actually knows the context of how the class needs to be used. and is responsible for injecting data into your classes (i.e. you setting the fields in the inspector). take this class for example

public class OnUpdateDispatcher : MonoBehaviour
{
    [System.Serializable]public class GameObjectEvent:UnityEngine.Events.UnityEvent<GameObject>{}
   
    public GameObjectEvent OnUpdate = new GameObjectEvent();

    private void Update()
    {
        OnUpdate.Invoke(gameObject);
    }
}

This class is stupidly simple but the context of how this class is used is determined by the inspector. which unlocks a lot of power to your level designers, as it gives them the power to update several objects per frame by piggybacking off of Unity’s EventSystem. You’ll be surprised how much you suddenly can do with just these dozen lines of code (and its simple implementation makes its extremely stable code).

I also have an InputDispatcher version that maps input calls towards certain objects (this dispatcher is a bit more complex since it works with my custom InputManager, but it still fully exploits Injections). And I can easily disable this component and add in an AIDispatcher and suddenly and effortlessly, a character switches control from a player to an AI.

Thank you for the replies, I really do appreciate it. I will definitely be researching into Unity’s event system, Serializable classes, and Scriptable objects.