View Controller with Zenject

View Controllers

Firstly, if you do not know what Zenject is, it’s a light-weight Dependency Injection library that is written for Unity. You can read more about this here.

Something that I feel would go nicely with Zenject is a View Controller system that allows you to create prefabs that will never need a reference to anything, ever again. Below, I’ll talk about the difference between the View and Controller in this system.

View

A view is a MonoBehaviour that sits on the root GameObject that holds information about what’s in that Prefab. This could be a button that a user can press, a label you want to change, or any other useful script that you want to take advantage of.

Note: A view should have no functional code and should act as a dumb class.

Here is a sample of one of my views. You’ll notice the only code here are SerializeField’s and public properties that will be used by the controller.

using UnityEngine;
using UnityEngine.UI;

public class StartGameView : View
{
    [SerializeField]
    private Button m_startGameButton = null;

    [SerializeField]
    private Text m_highScore = null;

    public Button StartGameButton
    {
        get { return m_startGameButton; }
    }

    public Text HighScore
    {
        get { return m_highScore; }
    }
}

As you can see here, this just stores references to the MonoBehaviours in the Prefab.

Controller

A controller has two possible functionalities. One is to act as a manager, and the other that acts as a View-Controller, or another way is, a manager for that view.

Controller as a Manager

using UnityEngine;
using Zenject;

public class AudioManager : Controller
{
    [Inject("Music")]
    private AudioSource m_musicSource = null;
    
    [InjectOptional("Sfx")]
    private AudioSource m_sfxSource = null;

    public override void Initialise()
    {
        // Called by Zenject.
    }

    public override void OnDestroy()
    {
        // Called by Zenject.
    }

    public void PlayAudio(AudioData audio)
    {
        if (audio.Type == AudioType.Music)
        {
            PlayAudio(m_musicSource, audio.Clip);
        }
        else
        {
            PlayAudio(m_sfxSource, audio.Clip);
        }
    }

    private static void PlayAudio(AudioSource source, AudioClip clip)
    {
        if (source == null)
        {
            return;
        }

        if (source.clip == clip && source.isPlaying)
        {
            return;
        }

        source.Stop();
        source.clip = clip;
        source.Play();
    }
}

Above, you can see we get calls from Zenject, these are because Controller is an interface that inherits IInitializable and IDisposable. The base controller does some code behind the scenes and calls abstract methods “Initialise” and “OnDestroy”.

Controller as a View-Controller

using Zenject;

public class StartGameController : Controller<StartGameView>
{
    #region Fields

    [Inject]
    private GameManager m_gameManager = null;

    #endregion

    public override void Initialise()
    {
        // #JD 20/11/2015: Show the view.
        View.Show();

        // #JD 25/11/2015: Set the high score text.
        View.HighScore.text = m_gameManager.HighScore.ToString();

        // #JD 20/11/2015: Add View listeners.
        View.StartGameButton.onClick.AddListener(StartGameClicked);
    }

    public override void OnDestroy()
    {
        // #JD 20/11/2015: Hide the view.
        View.Hide();

        // #JD 20/11/2015: Remove View listeners.
        View.StartGameButton.onClick.RemoveListener(StartGameClicked);
    }

    #region Button Listeners

    private void StartGameClicked()
    {
        // Do some code when Start Game is clicked.
    }

    #endregion
}

As you can see above, this controller takes a View type as it’s generic type, this gives us access to the View at runtime as this is injected into the controller by Zenject. Now this controller can hook up the buttons and change the view’s look based on any model you inject into this class.

This helps keep prefabs separated and then the controller can have any other item that it requires injected into it. No more requiring references to other prefabs or objects in the scene.

Code Available

The code is now available on GitHub here.

Note: The code is written to work in a compiled DLL, but you are able to copy the ViewController and Pooling folders into Unity, along with Zenject 4 which is available here.

jamjardavies / April 12, 2016 / Unity3D

Comments

  1. SoldierFox - March 8, 2017 @ 7:48 am

    Hi JAMJARDAVIES,
    I’m have a stupid question.
    in your sample project, how unity know StartGameController is a start scene.?
    Can you show me where is StartGameController call? I can’t find it :(
    Thanks….

    • SoldierFox - March 8, 2017 @ 7:50 am

      I mean Start Game View Prefeb …

    • SoldierFox - March 8, 2017 @ 7:56 am

      i got it, in func Initialise() you set “View.Hide();” for GamePlay and GameOver, “View.Show();” for StartGame?
      So everything is Initialise in the first time game start? it’s have memory problem when I have a large resource?
      Sorry I’m a newbie unity….
      Thank you very much…

    • SoldierFox - March 9, 2017 @ 11:45 am

      I mistake, please forget my comment.
      Thanks….

  2. jk314 - February 1, 2017 @ 12:59 am

    Another question. How do you bind a view and controller of an object that exists in the hierarchy? All the objects for Blasteroids are created at runtime I see. Thanks.

    • jamjardavies - February 3, 2017 @ 9:46 am

      If you look into the BindViewController code, you can change it to be an instance instead of prefab.

  3. jk314 - January 30, 2017 @ 5:18 am

    Hi,

    How is it possible to bind 2 objects with the same View and Controller code?

    For example:

    Container.BindViewController(settings.Button0);
    Container.BindViewController(settings.Button1);

    Thanks

    • jk314 - January 30, 2017 @ 5:20 am

      Comments seem to be stripping the code I entered – I will see if it does it correctly this time…

      “Container.BindViewController (settings.Button0);”
      “Container.BindViewController (settings.Button1);”

      If not, feel free to delete these comments.

    • jamjardavies - January 30, 2017 @ 9:17 am

      So you would like to have a different instance of a View Controller for different prefabs?

      I’m in the process of updating the code to be more MVC style, and this will allow transient view controllers, meaning each time you resolve the controller, it’ll create a new instance. This will however, only work with 1 prefab in the bind stage.

      What you can do is use the Controller.TransientFactory to create them at runtime (you have to bind this first) and this will auto-create the prefab you added at bind stage.

      I’ll try and get a new version uploaded soon.

  4. Ardrian - June 27, 2016 @ 11:00 pm

    Hey, great example code here, and explanatory article. Just wondering if you were going to be posting your code to Github. I’d love to fork from it.

    Otherwise I could put up a repo now giving full credit to you for the concept along with links to this blog post. Let me know! (Would def rather go with the first option!)

    • jamjardavies - June 28, 2016 @ 7:08 am

      I’ve just updated the code to work with the new Zenject so I’m just testing now, hopefully have this up very soon!

      • Ardrian - June 28, 2016 @ 11:53 am

        Awesome. Looking forward to it. What’s your github username? Might be easier for me to follow you there. (I don’t seem to be getting notifications of comments on this board)

      • jamjardavies - June 28, 2016 @ 3:35 pm

        Odd, maybe I missed a setting.

        My GitHub is the same as here, jamjardavies.

        I’ll look at updating the demo I made to work with the updated View Controller code with Zenject 4 and create a repo for it.

      • jamjardavies - June 28, 2016 @ 5:11 pm

        The code is now available here: https://github.com/Jamjardavies/ViewController/tree/develop

        Please note: This is created to work with a DLL along with Zenject 4. You can copy the code into Unity and it should work, as long as you have Zenject 4 too.

        Let me know if you have any issues!

      • Ardrian - June 29, 2016 @ 5:41 pm

        Hey, checked out your project and starred. Nice! Thank’s for putting that up.

        I had my own simple 3 class framework for doing MVC with Zenject, yours is more elegant though.

        I’m currently working on a nice way to handle transitions in a more generic manner. (So a view controller having “TransitionStarted” “TransitionEnded” virtual methods etc. I’ll ping you on github if I put something up there.

      • jamjardavies - June 30, 2016 @ 1:53 pm

        Funny you say that, I’ve been meaning to work on transitions next, infact, I currently have a SceneController which contains a signal for changing the scenes.

        Would be interesting to see your implementation of this!

  5. Rohit Bhosle - April 24, 2016 @ 1:55 am

    Hi, The idea looks awesome, and would streamline implementing the MVC further in unity. Now Excitedly waiting for your Code Share. Have you decided any date? by when you plan to share a example project??

    • jamjardavies - April 24, 2016 @ 8:26 pm

      I’m hoping to get a version online tonight. I will reply when I’ve got this uploaded :)

      • jamjardavies - April 24, 2016 @ 8:41 pm

        I have an example project available here, with the View Controller code in, however it’s not the latest, which I will try and share later.

        You can download Blasteroids (my Asteroids clone) here: http://jamjardavies.co.uk/Downloads/Blasteroids.zip

      • Rohit Bhosle - April 25, 2016 @ 10:53 am

        Yes, thanks, will have a look through it. There are quite a few other users, in the zenject google group that are also awaiting the code share :)
        Looking forward to it

Comments are closed.