Some other Unity dev thoughts

Pseudo MVC, separation of logic + view models and more

A common topic when you chat with developers is separating the view / presentation layer of your application from the underlying ‘business’ logic that runs under the hood.

You can see this with Web applications, (many of not most) mobile apps among others. Common acronyms that are thrown around on the subject matter include MVC (model-view-controller), MVP (model-view-presenter) among others.

Unity doesn’t necessarily lend itself easily to this kind of system. Typically you have a bunch of C# components (MonoBehaviours) that in turn ‘do stuff’, whether it’s run game logic, control your character, handle input, and update the UI.

It’s only once your project becomes more complicated that you start to realize that you need to begin to separate these components into separate pieces in order to make the code more readable and save yourself a ton of headaches in the future.

When starting out in Unity, it’s common for a player character script (as one example) to start out as a single ‘CharacterSystem.cs’ script that holds literally ‘everything’ – movement, input, weapon handling, damage, you name it – all in the one script.

Eventually this script is a thousand lines long (or longer!) and rapidly becomes an unreadable mess that is difficult to debug and manage. This problem becomes even more complicated when you factor in multiple developers working on the same project / codebase and … well you can see where I’m going with this.

Everyone has been there, I definitely know I have.

Now, there ARE existing MVC / MVP type of systems out there – in fact the new UI Toolkit system that Unity has started using is an extreme version of this kind of a system – it uses an XML style layout system for the actual UI, a javascript-style interaction layer, a hackified CSS for presentation / styling and more.

Honestly, I find it terrifying. Something I’m always for is the KISS principle – Keep It Simple, Stupid.

Also, UI Toolkit isn’t ready for prime time when it comes to runtime UI (versus building custom UI for editor windows etc).

So what does this mean for this project?

UI Managers, Actions and Events

Something I developed a long time ago is a fairly simple UI Manager. I use it for everything I build in Unity. At it’s core, it manages a list of Panels and has an interface to ShowPanel() and HidePanel().

Like most of the common services that I’ve created, it uses the ServiceLocator pattern that I described in a previous dev blog, and is registered like so:

public class UIManager : MonoBehaviour, IUIManager
{
    public static UIManager Instance
    {
        get { return ServiceLocator.Get<UIManager>(); }
    }
}

On startup, the ServiceManager registers the service and then it can be accessed from any of the scripts in the game:

var go = gameObject;

// generic UI system, initialize first so that we can display stuff on the screen
var uiService = go.GetOrAddComponent<UIManager>();
ServiceLocator.Add(uiService);

In fact, it’s the FIRST service that I register, since many of the other services rely on it to display error messages, dialogs and whatnot during the startup process of the game.

The attentive out there may also see the GetOrAddComponent() method that I use to handle grabbing the reference to the UI Manager. This is a standard C# Extension method that I have (one of many) that is a helper to prevent code like this:

var blah = gameObject.GetComponent<MyBlah>();
if(blah != null)
{
    DoSomething();
}

Instead I can simply write gameObject.GetOrAddComponent(); and I am guaranteed to get a reference to the component. If it exists, it returns the existing component, if not, it automatically adds the component.

I’ll go through the extension methods that I’m using and how they can be super useful in a follow up blog!

Now that I’ve registered the UI Manager, I can reference it from elsewhere in the game like so:

UIManager.Instance.ShowPanel(“MyPanel”);

How does it know what a panel is? Essentially a panel is a game object that has a custom component on it called ‘PanelInfo’. This component registers itself with the UIManager (in the Start() method) and adds itself to a list of panels that the UI Manager maintains.

There’s a second component that gets added to any button in the UI called “MenuAction()’ that has a couple of options – the simplest is that it can either show or hide a panel, or it can call a custom method on a specific game object.

Using these two components (PanelInfo & MenuAction), I can craft an entire UI without any code that shows / hides other panels very quickly.

So that’s one piece of the puzzle – handling the UI structure and controls.

The rest of the MVP (Model→View→Presenter) or MVC (Model→View→Controller) puzzle is something that I’ve been iterating on for a while, and I think I’ve finally found a clean solution that I like.

When working with Photon (the networking library that I’m using), there are a few ways to interact with it. The most common is to replace the typical ‘MonoBehaviour’ class with a custom variation called MonoBehaviourPunCallbacks

So instead of creating a Unity component like so:

MyComponent : MonoBehaviour

You create one like this:

MyComponent : MonoBehaviourPunCallbacks

This automatically gets you a bunch of callbacks that Photon will automatically call whenever something happens – OnJoinedRoom for example when a player joins a room, and so on.

In order to keep the UI implementation details separate from the lower-level network logic, I’ve split the code into 3 layers:

  • the ‘Service’ layer – this is a MonoBehaviourPunCallback script that directly interacts with Photon

  • a ‘View’ layer – which controls front-end logic, updates the UI, decides what screens to display

  • a ‘Presentation’ layer – for individual UI elements that need to be updated with specific information, for example player information on a prefab.

The terminology probably isn’t correct in ‘real’ software engineering terms, but….it’s my codebase and I’ll do what I want ;P

How does the code interact with the different layers? This is where Actions and Events come in!

Actions & Events

There are a number of ways to create events in Unity. The ‘official’ way that you’ll see frequently in documentation and examples is to use the UnityEvent class. This can be useful to wire up events in a scene via drag and drop.

The default Button class in Unity provides a number of different events that can be wired up for example:

If you click the ‘+’ button, it will let you drag and drop another game object into the field and choose which method / function you want to call when the button is pressed.

All fine and dandy, except…I don’t want to have to manually wire up things like this. Something I mentioned in a previous devblog is a rule that I use almost everywhere:

Systems should automatically configure themselves whenever possible

Also: UnityEvent callbacks don’t really work as nice for POCO (plain-old-c#-object) scripts (at least not for me).

Another typical way of handling events is to use C# delegates, which are great, except they have a bit of a clunky syntax that I can literally never remember (I’m going to have to google them to even find an example). Now when I think of c# delegates, I’m really thinking of delegates AND events, because you typically don’t use delegates without the event itself.

Here’s a great example from this tutorial that explains them infinitely better than I will. Here’s another great tutorial that covers actions & delegates as well.

public delegate void NewspaperCompany();
public static event NewspaperCompany newspaperSubscription;

What this code is saying is that there is a ‘delegate’ called ‘NewspaperCompany’ that has an event called newspaperSubscription that can be fired. IF the above delegate and event where in a class called MyNewspaper, then that class can call / fire the event at any point like so:

newspaperSubscription?.Invoke();

The ?.Invoke() syntax might be confusing to some folks.

The TLDR is that if you fire an event that doesn’t have ANY subscribers / listeners, then you’ll get an exception.

Another way of writing the above is like so:

if(newspaperSubscription != null)
{
     newspaperSubscription();
}

The ? is what’s called a null-ternary operator in C# (I have no idea who comes up with these names).

The simple way to think about it is that this line:

newspaperSubscription?.Invoke();

is basically saying ‘I want to fire the newspaperSubscription event, but ONLY if it isn’t null.

From left to right:

newspaperSubscription ← the event I want to call
? ← checking if it’s not null
.Invoke() ← firing the actual event

So now you know what a ‘null ternary’ operator is! Congratulations!

If you are interested in being notified when a subscription happens, in your own class you can subscribe to the event like so:

MyNewspaper.newspaperSubscription += onSubscription;

which in turn will call the method ‘onSubscription’ in your own class.

The logic flow is something like so:

Publisher → Event → Callback → Subscriber

I know, it’s sort of confusing, check that tutorial link I shared above – it does a great job of explaining what’s going on. I’ll wait…


Ready? Ok great. So now that you understand Delegates and Events and their confusing syntax etc I’m here to tell you that…you can do the same thing except simpler – with Actions!

An Action is a standard C# way of doing basically the same thing as a delegate + event, except in a single line, and much simpler to understand (in my opinion anyways).

Here’s a great tutorial that explains how Actions work

The short version is that you can replace this syntax:

public delegate void NewspaperCompany();
public static event NewspaperCompany newspaperSubscription;

with:

public static event Action newspaperSubscription; 

much cleaner, no?

Everything else about actions behaves the same as delegates / events. To fire the event you call

newspaperSubscription?.Invoke(); 

and to subscribe to the event in another class you call:

MyNewspaper.newspaperSubscription += onSubscription;

So they work the same as before, just with an infinitely easier to read syntax.


Phew that was a lot.

Back to my example above. I have a ‘Service’ (the LobbyService) that interacts with Photon via the MonoBehaviourPunCallbacks and listens to events like OnJoinedRoom, does some stuff and then fires an Action event as shown above:

For example, here’s the ‘OnLeftRoom()’ photon callback and resulting code:

As you can see – all this method does really is write a debug log message and fire the OnLeftRoomAction event. Simple, right!

Now, you might be wondering why this method doesn’t do more? The answer is in the View layer of the puzzle!

The View Layer

The next piece of the puzzle is the view layer. In DystopiaPunk, a View is simply a MonoBehaviour, no different than any other, with a bunch of event listener hooks in the Start() method, like the following (a snippet from the LobbyView.cs script):

private void Start()
{
   LobbyService.OnJoinedLobbyAction += OnJoinedLobby;
   LobbyService.OnRoomListUpdated += OnRoomListUpdated;
   LobbyService.OnLeftLobbyAction += OnLeftLobby;
   LobbyService.OnJoinedRoomAction += OnJoinedRoom;
   LobbyService.OnJoinRoomFailedAction += OnJoinRoomFailed;
   LobbyService.OnJoinRandomFailedAction += OnJoinRandomRoomFailed;
   LobbyService.OnPlayerEnteredRoomAction += OnPlayerEnteredRoom;
   LobbyService.OnPlayerLeftRoomAction += OnPlayerLeftRoom;
   LobbyService.OnPlayerPropertiesUpdateAction += OnPlayerPropertiesUpdated;
   LobbyService.OnCreateRoomFailedAction += OnCreateRoomFailed;
   LobbyService.OnStartLevelLoadAction += OnStartLevelLoadAction;
}

I’ve abbreviated the above, but that’s basically what the core of the view is – a number of methods that listen to actions being fired by the core Lobby Service.

LobbyView is where all of the ‘View’ logic happens – if the player joins a lobby, we need to update the UI with a list of the rooms that we can join. If they join a room, we need to update the game details screen with a list of the players in the room, and so on.

For example:

The above method (OnLeftLobby) listens to the event from the LobbyService and handles the view / UI updates accordingly (in this case, hiding all of the UI panels that might be visible and showing the main menu again!)

This keeps the business logic (the LobbyService) that interacts with Photon itself cleanly separated from the View layer (updating the UI)!

For an event like OnLeftLobby(), above, the logic flow looks like this:

Of course some events involve more code than the above, but you should get the basic idea!

The Presenter Layer

The final ‘presenter’ layer that I described above is quite simple – these are custom components that I use to update UI prefabs with specific information.

For example: when the player joins the lobby, we display a list of Rooms that are available.

The LobbyRoomEntry prefab has a simple component that updates the various UI elements (TextMeshPro text fields in this case) with the relevant info.

Note: This is the FIRST layer of the UI that actually has actual references to a Unity object – in this case, elements within the prefab itself. Everything else in the system, from the UI system to the connection between the LobbyService and the View – are all done ‘at compile time’, meaning that they are automatically configured and ready to use, and aren’t dependent on me doing something specific in the Unity editor.

This component also has a single method: Initialize(), defined below:

This method is called from LobbyView when we get an updated list of rooms. For each room, a RoomListEntry prefab is instantiated and the Initialize() method called to update the room entry prefab. This keeps everything nicely compartmentalized and clean!


This same basic structure is how the majority of the game is structured – service layers that interact with view layers via Action events!

That’s it for this time. In the next dev blog I’ll go over some fun networking issues that I’ve been having (bug hunts!) and show the latest updates in the game! Fun!

Until then, hit me up on Mastodon as always, or join me on the newly created DystopiaPunk discord channel (how fancy!)

Thanks for reading!