I’m not experienced in this and I find it very difficult to apply some common design patterns like MVC, dependency injection etc in Unity as monobehaviour’s constructor is not available. Taking Unity’s component based approach, if treating every component as an object, as my code becomes larger, many of these objects become intertwined, they have to know each other. Especially my UI related code, I find that I need to drag a lot of things into the script attached to a game object used as a window, and do a lot of different things in that script, because a window can have many different functionalities. This seems to be a serious violation to single responsibility principle. In the end I find my code becomes more and more messy. Does anyone know any books, articles that talk about clean code in Unity?
Have an ‘init’ method that you call after you add the component. All a constructor is, is a function that is called after the memory for the object is allocated. It being streamlined into the syntax of the language is just sugar on top.
Them being a component vs not a component wouldn’t change this. If you’re intertwining code, that’s not the components fault. Components are just objects like any other object. They just happen to be composited to GameObjects.
Wouldn’t this technically be “dependency injection”? I mean sure, it doesn’t look like any of the fancy DI frameworks out there. But at the end of the day DI is just the act of handing objects references to the other objects they work with. In the case of the editor, you’re injecting the reference to the Collider/Mesh/CharacterController/etc via the editor. Or if you prefer a service locator type approach you do so during the Awake/Start event (think of like a parameterless constructor) by using GetComponent (for local objects), Find (for slowly doing it globally), or your own custom service locator.
Create different components for your different Window functionalities.
Nothing is stopping you from having WindowTypeA, WindowTypeB, WindowTypeC. And if they happen to have similar shapes, then create an IWindow interface as a contract to relate them.
Not the fault of components though… stop designing your objects from doing so much.
Unfortunately I do not know of any books/articles.
But if you have specific questions, I’m more than happy to assist. And I know many others here on the forums are also more than happy to assist.
I second everything lordofduct said, but with one additional comment. With RuntimeInitializeOnLoad, you are perfectly capable of creating a single static entry point into your program when it first starts (like static void Main()), without using anything clunky like a singleton component on a GameObject (what you’ll commonly see in tutorials, for some reason). From then on, there’s nothing really stopping you from using the application as you would absolutely any other piece of software- your own MVC/MVP implementation, dependency injection, service locators, etc.
If you need access to assets from that entry point, Unity provides Resources.Load and Addressables, which work splendidly. Even if you don’t want to load 1000 assets simultaneously using those methods, you can store “config” files as ScriptableObjects, and then load those with Resources.Load instead.
Unity doesn’t prevent you from doing much of anything, they just have their own way of treating objects in the scene as a hierarchy, which necessitated their own initialization process for Components attached to those objects. That’s all. The inspector is essentially just a visual coding system for generating prefabs, like blueprints in Unreal. Even if you don’t drag and drop and generate a literal prefab asset from a series of GameObjects, you’re still making a scene prefab. There’s nothing inherently wrong or evil about this, it’s a tool like anything else.
It’s not preventing DI, it is DI.
That said, if the class you’re writing doesn’t need to be in the scene because its single responsibility isn’t in helping to control the display or behaviour of a sprite or mesh being displayed to the user, then don’t make it a MonoBehaviour, and do whatever you like with it. There’s no rule saying everything you do needs to be a Component, or controlled by a Component- don’t fall into that hole IMO. It doesn’t really sound like you need a book on Unity so much as you just need to study up on programming outside of Unity.
To be specific, it’s supposed to be a mobile RTS game, basically the players select one of their characters in a scene and move to a battle scene to fight other players.
For example, in the window that displays player owned characters, the player can select different characters, open a mini window to see their detailed information, traits etc; This window also has a few buttons that lead to other windows. Currently all character loading, displaying, button callbacks are handled in a single script attached to this window, and other windows may also request some information from this window script, and it in turn may also need to know what other windows have done etc.
It looks ugly to me, but considering that I have only 4 or 5 different windows, and there are already 30+ scripts for handling other stuff for these windows. I fear that if scripts keep piling up, it may impact performance. I don’t mean to ask other people to do my work. I took over the project from someone else and found myself in an embarrassing situation where the code feels bad to me but very little I learnt from books could be applied here. So I try to look for some good examples to study, thinking that it might be because Unity is different from many other environments.
But if you say it’s rather unrelated to Unity’s design, then I think this project is probably screwed, beyond salvation. I probably need a few more years practice to make things right.
By the way, the previous design of the project is even more weird, almost everything is created from code. My colleague did most objects creation, component adding in script. And he used Transform.Find and AddComponent extensively for that job. Practically managing everything centralizedly and ditching the Editor interface entirely. I’ve been refactoring code like this and as I said, I was not experienced myself, thus get confused and start panicing, and seek some general guidance.
It might just be an older project- Unity was more difficult to manage before it had proper support for nested prefabs and ScriptableObjects. If you’re working on an inherited project though, you really need to weigh the pros and cons of making significant changes, especially if you aren’t very well-versed in Unity yourself. If it’s as much of a mess as it sounds though, and it’s not near completion, I’d seriously consider just starting over from a programming standpoint (you’d still have the assets themselves, and that’s more than half the battle).
But yeah, single responsibility tends to mean a lot of scripts- you can’t really have it both ways, sadly. If you want less scripts, you need to make them more general-purpose and re-usable. Understand when you can use the built-in types, like EventTrigger, instead of writing your own. Use composition over inheritance- that’s what Unity’s good at anyways.
As for general guidance, events are your friend. If the managers are aware of the objects they’re managing, and the managed objects are aware of the manager, it sounds like a command structure problem. Ideally, the relationship should really be one-way, and tied to an interface rather than a concrete type. Instead, you may want to create some public EventHandlers on the “managed” objects, so that the manager can register when it instantiates them, or when the whole entity is instantiated in the scene/as a prefab.
A good example is the inventory slot for a UI- the slot doesn’t need to know about the inventory system at all, it just needs to know that it fires X event when it’s clicked, Y event when it’s dragged, Z event when something is dropped onto it, etc…
I would recommend cutting out almost everything that uses AddComponent, and instead switch to creating prefabs of the hierarchy structures you need for different situations. An entire “slot” object in an inventory window should be one prefab, so that you can create as many copies as you need slots. As a prefab, it would be ideally general-purpose, not tied to a specific implementation of an inventory system, and have a single MonoBehaviour script on its root that manages how the slot is used from the outside. Likewise, the inventory window as a whole would be one more prefab, and have a single controller (again, on the root) that manages how it’s used and displayed, and that shouldn’t really be aware of the UI system that’s instantiating it.
Dividing them up into prefabs, having each be its own self-contained “entity”, really helps a lot with command structure problems, because the Components on the prefab object can’t store references to objects outside of its prefab structure- only the objects internal to it. The events are all that are needed to tie into them. It also makes it much easier to implement object pooling, for performance.
It started out with unity 2017, when prefabs needed to be dragged in scene for editing. It was somewhat annoying.
I didn’t know Unity’s EventTrigger, thank you for mentioning that. But I’m not sure about this. Regarding UI components, the project employed a strategy that a lot of buttons have a dedicated event message type and event handler. It fires to a centralized event queue and forgets, but I found this difficult to maintain, because it is difficult to find out who is handling the event. So I’m doing things directly in some callbacks, because I think these windows have not been reusable all the way along. Did I just do something wrong?
It is good to hear that, Find() + AddComponent() is so annoying that it’s clueless in the editor, you only see what happens during run time. Judging from what you said I suppose I’m doing every individual thing right. However, consider the this scenario: In the window that lists player owned characters, the player can see their detailed information, which involves loading some high poly models etc, and in another window where some characters are sold, the player can also click on them and see their details; these are pretty much different windows serve different purposes, but also have some common functionalities. So it ends up with them knowing each other’s status, when to hide the character details, like the windows belong to the same group despite doing different things conceptually. Is this some sort of cross-cutting concern that’s so difficult to isolate?
There is no “right” or “wrong” really, just “easier” and “harder” in context. There are a dozen ways to accomplish any goal, and all of them have pros and cons depending on the situation. If you find something easier to use, then barring significant performance problems, that’s a valid way of doing it IMO. Everything is a tool in the toolbox, nothing more and nothing less.
Setting up specific callbacks in the inspector is fine- as has been said, the inspector is just a form of DI. As long as you’re setting the targets there, the Component is reusable for other things, which cuts down on scripts (EventTrigger is a great example of that, but really, so are any of the built-in types like Buttons, Images, etc). It’s mostly the same as wiring into events from a manager (maybe in the Start function) into the managed buttons and such under it, and getting alerted when those events occur.
The only problem is that you can’t really prefab/pool the child objects separately if the children need to be aware of the parent, because they can’t maintain an object reference outside of the prefab. They won’t be able to contact the manager object without something like GetComponentInParent() in Start(), which is no longer using the inspector and sort of defeating the purpose in a lot of ways. This is why I like leaving that control to the manager, because it’s the one that’s going to be getting and setting objects back to the pool as needed, it’s the one instantiating, and so it’s the one in a better position to know when events are needed or not. This is ironically even more important if the real decisions are being made even further up, and it’s just functioning as a middle-man. You might end up with multiple tiers of prefabs, and that doesn’t work with inspector-assigned callbacks.
As far cross-cutting concerns go, I would argue that if two different windows need to know eachother’s status to that degree, they need to have some sort of a shared controller, or a common event they both subscribe to. They still really shouldn’t be directly aware of eachother IMO.
Of course, there’s always a point where you can no longer abstract things out, delegate responsibilities, and make things “generalized” and “reusable”- where you have to actually have a manager who’s in charge of making real decisions, and telling others what to display all of the way down the hierarchy. I don’t think it should be at the level of like Buttons or anything, because that’s really pushing it and not very reusable at all, but maybe that’s your window components, in which case you may need a unique script for each one (all implementing IWindow or some common interface). There’s no shame in that. In my case, I use proxy components in the scene that know very very little about the data they hold, so I can get away with abstracting a little further- only the services (non-MonoBehaviour manager types accessible from anywhere, at any time) actually have any real knowledge of how things are run.
That said, that approach isn’t for everyone, and it’s a bit contrary to the Unity methodology. To keep things simple, just be conscious of the level of awareness each Component in a hierarchy actually requires to do its job properly. Limit it to only what’s absolutely necessary in order to be an effective tool, generalize as much as you can, if you can, and when you get to a level where you can’t keep generalizing, start making your control scripts- handing down commands to the poor mindless peons under it like some sort of heartless dictator.
… I may need sleep. Anyways, as always, just take this as one perspective- one stance, one mindset, and nothing authoritative. I’m just one lowly software engineer who’s incapable of comprehending the word ‘concise’.
Thanks you very much for your time @DonLoquacious , even though I don’t get a straight answer for my original question (as there is none currently), you certainly enlightened my mind. It’s probably because of a mix of my lack of experience and the real problem I’m facing, and at the same time I’m reading Uncle Bob’s clean code series, and a DI book, by their standards I’m very unsatisfied with our current code base.
But seeing as you also use some non monobehaviour classes to manage things, which I considered almost the same as a bunch of global variables, you also think they are a bit contrary to the Unity methodology. This reminds me that it’s very hard to make things perfect, and I just can’t become proficient in Unity over night, or even a few months, especially I just started writing applications recently (I was dealing with Linux device driver after college). I need to keep reading and learning, and find what works and what doesn’t in real world problems. I hope I didn’t take too much of your time, thanks again.