Dependency Injection and Editor Scripts
Hello all,
I would like to make an entry about one of the biggest obstacles I found to making nice architectures in unity. You want to be able to make nicely decoupled systems with good separations of concerns. To elaborate, what we would like is if each script did one important thing and fitted nicely with the rest of the scripts in the system. In a previous post I used the analogy of gears and right now I am tempted to use the analogy of puzzle pieces. In fact we do NOT want our system to be like a puzzle. In a puzzle each piece arbitrarily connects to many other pieces and placing them together is the goal. This kind of non-design is often called spaghetti code. Unity in many ways encourages this kind of design - there are no natural boundaries between which script you make that can reference which other script or library . This leads to a kind of design means that each script touches many, many other scripts. When this happens it is very difficult to separate what happens where. So more than likely you put something where it doesn’t belong and then when you want to change it, the first step is to actually find where in the world that line of code is before realising that it is impossible to make the changes you need if it is where it is. You change that one piece of the puzzle and all the connecting pieces change (imagine that each piece is not roughly square and can have as many connections as you want).
Back when I first started studying at AIE, I used to use a lot of singletons. The reason was very simple - you want other pieces of code to be able to get dependencies. Back then I had an intuition that you needed to separate things based on what they were. I would have never simply dragged in a heap of different UI references for instance. Instead I would group the UI together and then since I needed it everywhere I would turn it into a singleton because having to ensure eighty objects in the scene had a reference is not a fun task. Singletons will get you a certain distance in making a system because at least you have combined functionality together which works together. The problem with singleton is that once something exists in a global scope you can access it everywhere, so you do. That means in your puzzle every single piece connects to this one piece. So if you change the shape of it…
I hope you will give me a moment to take a breath. Even now the horrors of changing a singleton bring back terrible memories. Why is this a problem you might ask (or perhaps not depending on where you are in your programming journey).
I think Dave Farely (a software engineer who advocates for test driven development that I respect a lot - check out his youtube channel Continuous Delivery for an amazing selection of useful resources on test driven development) said it the best.
“The most important quality of software is your ability to change it.”
Change is inevitable. To improve your code is to change it and in fact even just maintaining your code requires you to change it. Software improves all the time and with it so does what you can do and how you can do it changes. In games in particular there will always be something cool you want to add. So you had better make sure you can change your code so you can in your spare slot of time to work on your project actually be able to add that little cool flair you have been thinking about all week.
Before you can think of high level techniques and systems you need to be able to take small steps towards improving your design. One thing I have run into trouble recently was doing too much refactoring using my new found skills from studying software engineering. The other thing Dave Farley likes to talk about is taking small steps. So what is the smallest step you can take from here?
Dependency Injection. Using serialised unity references as the only point of reference between two scripts actually is a form of dependency injection. The important part is that a script can work with another one without knowing how it got that reference. As I said above it is far from a foolproof solution. It is far too easy to forget a reference and if it is something important well… the game screeches to a halt.
My next solution was having a base class which had a function called “Inject Reference” and creating a class where you could assign everything which has those references. At start it would helpfully inject those references. For a class where you could just assign the reference this is a bit overcomplicated. By typing in the name of the class in your unity search bar (which will show every gameObject with that class attached) and then selecting every reference you can assign the dependency in one hit. Then you can just debug a warning if you missed a reference. No base class is required, which is a good thing because let’s face it - your class often already has a different base class.
What about an interface? The problem with interfaces in Unity is that you can’t assign those in an inspector (I have seen a workaround of this but I never got it to work). So while you can put them wherever you want in classes and they work great in testing, they don’t work well if you have to make a list of them in Unity. At that point I had a look into send message and broadcast message functions. Which is when I read a comment saying that you should never use them because in the newer versions of unity GetComponent in children supports interfaces. Rather than needing a list you can just put all of the children under the injector class and it will get the component and set the reference. Which is pretty good except… if you want to be efficient you don’t really want to go over every single object in the scene. That said, for a modern game on modern hardware I don’t think that doing that once actually matters that much but I still wanted something better. Which is when I realised something.
What if you just called that function in the editor? I have had some brief forays into unity editor scripts but I have always found that the system to create editor scripts was so painfully bad that I never wanted to make one (though I hear they are working on better ways to make them). What I found is that you can do this instead:
// Only compile what is in here if in the editor.
#if UNITY_EDITOR
[ContextMenu("Set all control UI references")]
public void SetAllControlUIReferences()
{
// The slow find function you want to avoid during run time
YourComponent[] components = (YourComponent[])GameObject.FindObjectsOfType(typeof(YourComponent));
foreach (YourComponent component in components)
{
// the function want to call - can be anything you want (including a different editor function!)
component.InjectReference(this);
// This part is very important, your script won’t compile in build
// if it has a using statement for UnityEditor so you must reference it like this
// Set dirty informs unity that your asset needs to be saved, without it unity will forget you called the function when you change scene.
UnityEditor.EditorUtility.SetDirty(component);
}
}
#endif
Functions marked with [Context menu] functions will appear on a script here in the editor.
To recap the comments we wrap in #If UNITY_EDITOR to ensure that the UnityEditor.EditorUtility.SetDirty(component) line only compiles in the editor. Without this your game won’t build as that function only exists in the editor. Of course this is only the tip of the iceberg. You can do a LOT more than just assign reference. You can use AssetDatabase functions to assign references such as audio files to your objects - I did this to great effect to load a folder of over eighty audio files into a scriptable object. I have also used it to go from storing settings in a script to having a list of settings on the object by calling an editor only script on every object in the scene.
Which is to say it has all of the power of editor scripts, without having to deal with the layout of the inspector or the weird syntax of editor scripts and if you only wanted a button that does something the context menu works fine. Depending on who you work with you may need to move onto editor scripts (simply because you need those big buttons for less technical people) but even then the code will be the same so you can just paste it over. What is important is to remember that you can get the computer to do boring repetitive tasks for you and you don’t need to choose between good design and hours assigning references in the inspector.
That’s all for me today - I am going to get cracking on adding more tests to Illic.
Get Lords of Illic
Lords of Illic
Are your tactics good enough to become a Lord of Illic?
Status | Released |
Author | OrangeDrake |
Genre | Strategy |
Tags | Singleplayer, Turn-based Strategy |
More posts
- Version 19.045 days ago
- Version 18.667 days ago
- Domain Driven Design67 days ago
- Version 18.5Sep 03, 2024
- Burn away the ThornsAug 18, 2024
- Second Hand Musings Part 3: ShareableAug 13, 2024
- Version 18.4Jul 16, 2024
- Version 18.3May 27, 2024
- AIE LivestreamMay 13, 2024
- Version 18.2Apr 22, 2024
Leave a comment
Log in with itch.io to leave a comment.