#Code Architecture in Large-Scale Unity Projects

1 messages · Page 1 of 1 (latest)

proud steeple
#

It seems despite anything I try, Unity wants me to write the most horrible spaghetti code ever seen. Maybe I am just in the wrong mindset. How do I write reusable clean Unity code in very large projects without resulting in a mess? I've tried rolling out my own event bus, etc... But nothing seems to work. Anyone can point me in the right direction?

rose kiln
# proud steeple It seems despite anything I try, Unity wants me to write the most horrible spagh...

Split your project into decoupled & coherent modules with a single responsibility, that have strong contracts with small public APIs that compress meaning and hide complexity. Never leak implementation details and communicate between modules only through module-scoped top level manager/host/system per module. Keep module orchestration/wiring in one place. Avoid deep references that cross module boundaries. Avoid easy to abuse patterns like global-anything, particularly singletons and message bus, which easily turn messy in large projects. Keep these patterns scoped to individual modules. Use data oriented design and aim to separate systems-code from gameplay scripting. Use inversion of control and dependency injection to think clearly about ownership, responsibilities and control flow. Avoid dependency injection containers (unless you plan to unit test everything or when you are in smallish teams, they need a lot of discipline). Establish strong conventions around Unity lifecycle callbacks or build your own to solve sequencing issues. Use formal models like state machines and enforce their constraints to manage complex transitions and side-effects. Document and assert your assumptions in all public methods.

proud steeple
rose kiln
# proud steeple > communicate between modules only through top level manager/host/system objects...

It doesn’t necessarily mean only that. It’s more of a principle about where the boundaries are crossed to avoid leaky abstractions and unhelpful couplings. Direct references between module's hosts/managers are a simple example. What this often implies is (and that’s the powerful bit): all gameplay/business components of a module are registered with a host/manager and the lifecycle of all these components can be centrally known/managed. if you also put each module into an assembly you force yourself to think hard about the dependency hierarchy between them and you’ll find that your project specific glue naturally lands in one orchestration module with everything thing else in narrow separate slices. This only happens when you stick to the ‚communicate through top level managers/hosts‘

#

One more thing I forgot: separate your views from your logic and manage persistent state in your module hosts/managers in simple, dedicated data objects (not on MonoBehaviours directly).

unique hazel
#

You should try to nail down what about your code feels bad

#

I think there's a temptation to look at any large project and think "oh my god, it's all spaghetti"

buoyant chasm
#

different types of games have wildly different needs but state machines are usually a good starting point, games are usually made up of transitions between various states and codifying that into state classes can help get you started breaking things up logically

#

i don't think IoC containers are necessarily to be avoided but if you haven't used one before it's probably best to start without it and pick one up if you find yourself writing lots of boilerplate to keep things separate (and go with something simple like vcontainer that only does what you tell it to)

#

also my favorite design tip is to ban the word "manager" lol, make everyone come up with names that explain what the class does

earnest rover
#

Just be very wary of over engineering. Don't start using patterns just because you think you should be using them. Code for your current problem and not hypothetical future problems.

unique hazel
#

side-eyeing my PlayerManager class

unique hazel