I have a very specific way to organize my code. I love to think in architectural terms and my code is always evolving.
And that's my current approach:
I like to say that my programming is the Unity-way. Not because I like the way the things work at Unity but because I'm working using Unity for a while and coherence is something very important in programming so, for sure, using other Engine, my way to solve the things will probably suffer a huge change. And what is the Unity-way?
Actually, the Unity-way (sometimes called "oriented to component") means: Think in each component as a separate program that implements a single behavior.
Unity is heavily based on components. If you look closely to the built-in components you will find the patterns below:
- Simple: Simple to be used.
- Generic: All components can be used in several contexts.
- Flexible: All components are flexible with some key parameters exposed so the user can easily tweak things.
- Self Contained: Usually each component work by their own. You don't need to plug several references to a component. If there's a requirement the code automatically adds the required component and that's it.
PS: OO is your friend. Component-based is not against OO. Actually, Unity is an awesome example of OO programming, just look at the Script Docs and you will see how OO is used to build a robust and flexible system.
Take a small piece of your code and think: "How a kick-ass solution for that should look like?"
Jump now into Unity context and ask: "How this feature can be simple, generic, flexible and self-contained?"
A good practice is to describe your class and list expected Inputs and Outputs before anything else. This will help to look to components as components and avoid the temptation of create god-classes. Inputs should be implemented as public methods, receive up to 4 parameters and return void. Outputs should be implemented as UnityEvents or UnityEvents derived classes passing up to.... yes... 4 parameters. This way you can pass most of your flow to the inspector and let each component handle with their own business.
This gives a huge boost on your flow and creates fully decoupled Components that can be reused in other places.
Try to keep your classes with few lines. A small class should have less than 100 lines. A mid class, less then 400. And the main classes less than 1000 lines. If you are passing this numbers, you are probably creating a god class and we really want to avoid that.
I usually don`t need to use "Find" or even put a serialized field to another component in my project since everything is indexed using UnityEvents. If you need to use a GetComponent, make sure that [Require Component<typeof(RequiredComponent)>] is used.
Take all your references on Awake. Use Start for initialization. You avoid hundreds of issues related to null pointers using this approach. Reducing your total coding time by half. (Believe me, the time expended on solving null pointers and related bugs is, being generous, half of the time that you will expend on your game).
When I think about game architecture, all classes are divided between 4 layers: Game Layer, Scene Layer, Core Layer and View Layer.
A Unity project are usually divided into scenes. A scene is a self-contained structure. From the architecture point of view, we can say that a scene is a huge prefab. Unity uses this structure to resource management and it also make level structures smaller and simple.
You should let a scene be independent. Is easier to test each scene individually and it`s terrible when you can`t do that for some reason.
However, some elements really should survive between scenes, for instance, several Unity built-in managers work for the entire project and, as this managers, your game also need some overall logic and cross-scene data. Static classes, static variables and Scriptable Objects can really be helpful on that.
So my top-most structure are just that. Something to act globally. You can use auto-instantiated Singletons here or just instantiate a Game Layer prefab if it`s not present on your scene. You can even use a Game Layer scene and load this scene additivelly.
The fact is that I really reduce this structures to a few as possible taking care to use some solution that permits the game run from whatever scene I want. Most developer don't figure out how important is that until are wasting hundred of hours in order to enable scene test.
On the next layer are the classes that should affect the entire scene. This layer implements the first part of the gameplay logic. I call this layer of "Scene Layer". Here is where the victory/loss conditions, the logic behind your score, the difficulty progression, your level/grid generation and all this sort of scene mechanics are.
On the next Layer are the second part of your gameplay. The Core Mechanic. What I call the core mechanic are the 3C's (Character, Camera, Controller) and the stuff that are already implemented on the above layers. That's why I call this layer of the "Core Layer".
Most game designers argues that the most important part of your player experience relies on the 3C's and a lot of effort are used to polish this behaviors so have this structures apart from your game logic enable much more reuse. Reuse on that layer means a huge time/money economy.
The last layer is the "View layer" and that's where everything else remains. The view layer is use to show expose parameters from the layers above or, in other words, is where you'll your UI.
The important part of this architecture are Managers don't know about who they are managing. They just give their outputs through public UnityEvents. All other components, don`t know about who is giving their order, they are ready to receive a order and this is done direct into the inspector.
Not sure if this architecture works on all cases. I'm using this approach for more than a year and achieving incredible results delivering projects in 1/10 of the predicted schedule. For sure, the fact of the nature of each project be similar to each other, are the main responsible for that but even for different projects, I've noticed a huge development boost.
Not sure about literature talking about similar approaches. If you know similar text, please, e-mail me.