How to write code in Unity ?

I have experience working as a backend developer and I do game development as a hobby. However, whenever I attempt to create a game, the code ends up looking messy and unorganized. Additionally, I find it difficult to avoid using classes like "Game Manager" or "Time Manager", etc. Another question is that I'm confused because every class I've written is inherited from MonoBehaviour, so I don't have Core logic like Entities(and all my game classes depend on Unity API - is it okay ?) . Could you please suggest some architectural and design patterns that you use in Unity ? Free tutorials shows only quick solutions (like example they use public fields for each script) for popular questions avoiding any architectural patterns

Here are 3 free ebooks from Unity themselves!

https://discussions.unity.com/t/896831
https://discussions.unity.com/t/915673

1 Like

The only suggestion I can give is to start writing code and keep going. There are tons of tutorials out there, so you should keep looking and reviewing what people are doing and what techniques they use that you like. Also, just reading a c# programming book can help you develop patterns that work for you.

Also, Unity has several things in their blog section about programming styles, patterns, etc.

If you get stuck, showing your code and asking questions can be a great way to start learning.

But, it's difficult to just ask a broad question like that and really get a good answer. While many programmers have similar styles, you'll still end up with a bunch of different answers overall.

2 Likes

Thank you, i will read it

I'm also backend dev originally, so hello. I will answer from my own experience after a couple of years with Unity now.

  • Messiness is a bit of a thing because coding is often experimental. I started off by separating everything with interfaces but I got rid of them because they ended up being just clutter.
  • This is scripting and 100x simpler environment than backend. Everything ends up being simple in the end, therefore having clean everything is not as important as it is in the backend. If something doesn't work, you can usually figure out the problem in a relatively short time, and you basically can't cause too much trouble by being messy.
  • Use namespaces and place code in equivalent folder hierarchy
  • Use prefabs for everything. The prefab parameter override system can be a headache but it helps to try and edit the prefab objects directly - you can access them from the > sign in Hierarchy view
  • I use Debug.Assert (three parameter version) quite extensively. It sorta replaces testing which is not the same at all in Unity as in backend
  • Spend time naming your variables and parameters well and use static typing unless it's something very obvious.
  • Add lots of comment lines. Only don't add comment lines if you are sure it's redundant and your naming is good enough (Clean Code etc), I mean seriously write those lines there. The stuff you built last week you won't remember next week, so make sure to code-document why that one special bit was this particular way (cos you spent half a day googling and experimenting with it)
  • I also write Manager or Handler classes but I sort of see it as an AOP or microservice sort of thing in terms of design. One handler takes care of a certain thing solely. You don't need to worry about coupling because your game is a sole entity and will remain such.
  • For my gameobject design, I separate the Monobehaviours into aspects, one Monobehaviour handles one feature of a gameobject. It helps to keep Monobehaviours smaller, I don't think I have any that exceed 1000 lines. It also helps you decouple and create diversity into gameobjects because they don't all need to have all features.
  • A good way to decouple elements is to use events. I am using a third party event system, but Unity has its messaging. For example UI and your game should be decoupled with events.
  • Something I just recently started doing more: Build good Editor tools and make your gameobjects such that you can instantiate them from the game but also the Editor.

So here's just a few, you're welcome. I'm back to coding :)

3 Likes


Thanks !

1 Like

The word “Manager” in a type name is a code smell!

This innocuous word gives away that the purpose of the type is not clear, or deliberately left open to evolve into a “monster” of a script, a “blob of code”.

What does a “Manager” do? Quite possibly a LOT of things. Try to come up with a definition out of the blue and you’ll likely struggle to explain it. :wink:

Here’s the first definition I found via google:

It is absolutely vague. It also says it’s a “process”, and makes it clear that “controlling things” is its main purpose. Whereas “dealing with” couldn’t be anymore open to interpretation - like, it could literally mean “does anything”.

So … what does a GameManager do? Does it “manage” the game? What does that entail? Same questions for TimeManager, or any other “Manager” type class.

That’s where the problems start: the use of “Manager” classes in the Unity community is over-inflated to the point that it has become an almost de facto standard to name any class “SomethingManager”. Unity’s own API is to blame as well to some extent, though there are only 11 types with “Manager” in their name, their examples often use the “Manager” suffix.

If you force yourself to NOT use “Manager” (or one of its many, similarly non-descriptive replacement words) in a class name, that’s how you start with better separation of concerns. For instance, the GameManager for all I’ve seen in the past, could and should often be separated into classes like:

  • QuestProgress

  • quests completed, failed or in progress

  • quest counters / timers (for repeatable quests)

  • unlocked levels and locations

  • GameSettings

  • Volume, Difficulty, UI settings, input settings, etc.

  • SceneLoader

  • Load (or download) additional scenes, callbacks

  • PlayerStats

  • Current Health, Mana, Experience, Level, Skills, whatever

  • EnemySpawner

  • Spawns enemies but may also ensure they don’t exceed a given number

  • Inventory

  • a database for storing instances of whatever the game considers “inventory items”

Note how each of these classes would be less informative if they used the “Manager” naming scheme:

  • QuestManager (Sir, I urge you to proceed with quest #49 lest you may fail to attract the interest of the young princess of Castle Quagmire.)
  • SettingsManager (uh … what kind of settings need management?)
  • SceneManager (hmmm … does it also “manage” the active scene in some way and it’s also in conflict with Unity’s SceneManagement / EditorSceneManager class)
  • PlayerManager (it befuddles me that the player needs to be managed)
  • EnemyManager (hey, Ork #34 go over there! Imp #13 stop giggling, this is your last warning!)
  • InventoryManager (according to my records, we are running low on health potions, let’s order some more)

Observation on the side: I’ve never heard of anyone using an EditorManager for their editor scripting needs, but GameManager usage is abundant.

Avoid MonoBehaviour where possible

One other thing worth noting is to force yourself NOT to use a MonoBehaviour for everything. In theory and often in practice, the MonoBehaviour exists ONLY to provide context to some operations - by this I mean mainly the serialized fields shown in the Inspector and the Unity event methods such as OnEnable, Update, etc. and providing access to the GameObject instance the script is attached to.

For complex systems specifically you should strive to use the MonoBehaviour as the ModelViewController to your underlying Model classes. For example whenever I need something that is more than a simple collection like a class that manages a grid (2D array), or possibly chunks of grids, then those will be non-MonoBehaviour classes. The MonoBehaviour also acts as an interface for other classes accessing/modifying that data but it will not give away the internals of its data structures.

Possibly the worst thing you can do not just in Unity is to have a type that provides public write access (read-only is fine) through a property or worse, a field. Any collection type MUST be encapsulated in a class and NEVER exposed to any other classes outside the type. This is possibly the single most important rule to follow that prevents code to be heavily entangled, hard to change and maintain, and full of bugs.

4 Likes

Could you please tell more about it ? You mean i should create non - monobeahivour entities ? What is View and Controller then ? Thanks

MonoBehaviours are popular because they provide easy ways to
A) hook into the objects lifecycle: https://docs.unity3d.com/Manual/ExecutionOrder.html
B) store data that is editable in the inspector in the scene.

Both things can be done without them though.

A) You can use just one single monbehaviour to hook into the lifecycle methods and then use these to pump your game logic (you can also hook directly into the player loop). This way you keep control. The order of Unitys magic functions (which Awake() gets called first?) is not defined so this setup may save you a lot of headache when initializing a microservices system as mentioned above.

B) Storing data on objects in the scene can be replaced (without losing editability in the editor) via scriptable objects: https://docs.unity3d.com/Manual/class-ScriptableObject.html
Whether the data on your object is something that should be externalized to an SO is up to you. Personally I tend to externalize any data that may be referenced more than once (i.e. it's not only for that single object). A bit like D.R.Y. principle for data (you may know it as "normalization").

1 Like

[quote]
You mean i should create non - monobeahivour entities ?
[/quote]

Yes. And at least one of them (the model’s “root” object) would be a serialized field in the monobehaviour. The Mb then just forwards any event method as needed.

If the model needs configuration editable via Inspector I’d make another class or struct like “ModelSettings” that is [Serializable] and has public or serialized private fields. This object can be passed along to the model object either on start or with every update call depending on whether the settings are editor only or runtime settings.

1 Like

Welcome to gamedev! You’re welcome to fight it but when your product lead decides to flip the entire game structure one week before worldwide, well, no time for a full rewrite, get out the ugly code and smash it into place with the big ugly stick and ship the game.

But the impulse to “keep it clean” is a good one. Just recognize it can only get you so far. Be ready to think about systems used in a way they were not originally intended to, and be fast at wrapping your mind around the particular “areas of concern” that a quick refactor affects, etc.

2 Likes

I’d just like to note, this is a scripting environment. If you write eg bash scripts, they have access everywhere and the writer is responsible for not wreaking havoc. Their writer has been granted system-wide trust.

I think the rule of encapsulation is generally a good one, also in modern environments where we don’t necessarily use OOP languages but we could use, say, bash inside a microservice container that exposes a public API. I just think that in the context of games your complexity rarely gets so bad that you’d actually need it, and rather the adherence to technical restrictions can give you unnecessary overhead.

By saying this I definitely don’t mean one should write web-like couplings in your games or any systems. I mean the fact that enforcing encapsulation by means of private variables is strictly not necessary, and, to some degree, goes against the spirit of the platform. You can (and should) have written coding conventions for your team how stuff is done, and everybody play by the rules. I think if stuff gets overly complex and buggy, this is rather indicative of lack of such coordination rather than OOP principles.

I’m gonna tell my next manager that he’s just a blob of human destined to become a monster.

3 Likes

I'm currently experimenting with everything I've read in this thread.I use ScriptableObjects, separate models from MonoBehaviour classes and call Unity Event functions in one script component. It would be great if someone gave a feedback

When the player dies, you need to very slowly display a red “YOU DIED” and play a menacing sound effect.

1 Like

I'm curious what back-end is here. In some frameworks it means working very much inside the framework, writing small callback functions, using mostly time-saving built-ins. Or in React, say, it means coding to build everything in modules with trees (letting it auto-tear-down and rerun that code as needed) with strict special rules for variable access. But in others it means simply knowing more about data-bases, writing larger programs, and being a better programmer in general than front-end coders.

To put it another way, I think Unity either gives much more freedom than a back-end programmer is used to; or it gives less and seems to force using too many odd built-ins, depending.

1 Like

For me, Unity programming is quite different from other types of programming. When I started I watched popular tutorials, and I noticed that many of them disregarded principles such as Single Responsibility, Encapsulation, etc … which I’m used to follow in backend programming.Maybe it’s a problem of tutorials that made by non professional developers

1 Like

Worth considering most folks starting out with Unity are, well, kids, who want to make whatever the latest popular game is. Next to that is folks coming in with zero C# or programming experience in general. It’s enough to learn Unity on it’s own, adding C# and programming in general on top of that is a huge load. Imagine trying to learn SOLID on top of that?

I imagine around 95% of people who set out to learn Unity (or game making in general, I should say) don’t last a week.

Most popular youtubers are personalities more so than professionals (as you’ve accurately noticed).

You have the distinct advantage of being a programmer already. All you need to learn is Unity itself, and you can probably more or less apply your own preferred coding style and practices to your projects.

4 Likes

Junior coders are usually eager to learn and eager to be coached and to have somebody to check their work and to provide assistance. They also need this and can't work on their own.

Then theres more experienced coders who have acquired skills and can handle their energy, motivation, can learn things independently. But they often have a pitfall of being idealistic and adhering to strict rules, and they tend to think black-and-white and preach these rules as holy doctrines. It's a competitive thing, they want to advance and to show and prove their skill, and it's like that everywhere in life, not just IT. But then if everybody goes ahead and follows the idealist, this wont make things any easier for everybody because it's not about thinking for the product and the team in the first place but about showing off their knowledge and a competition of whose idea is right.

Often it's only senior coders who know that strict adherence to doctrine is impractical, and all ideals need to be applied in a reasonable manner by weighing their benefits against their overhead. Wisdom is to know the rules, and to know when to break them. And this is something where one needs to prioritize product quality standards over personal preference. All rules are there to be used to produce high quality, but at the same time, all these rules are pointless and also counterproductive if they, when applied in the specific project at hand, fail to actually increase product quality.

Having said that, in games, I just think that product quality requirements are significantly lower than, say, backend. And this all starts from the fact that if the game crashes, client data is not lost and you don't need to pay millions in damages and to be the news headline for "Company X customer data stolen, idiot coders to blame".

3 Likes

I don't think I've personally experienced any pain points in practice from using "manager" classes myself. I feel like they are a pretty decent way of grouping multiple members that are all related to the same thing in a centralized location, where they are easy to discover.

For example, let's say we had a class called PlayerManager that contained a handful of events, properties and methods related to player objects:

class PlayerManager : Manager<PlayerManager>
{
    event Action<Player> PlayerChanged;
    event Action<Player, string> PlayerNameChanged;
    event Action<Player, int> PlayerHealthChanged;

    Player LocalPlayer { get; }

    void Register(Player player);
    void Deregister(Player player);
    void SetLocalPlayerName(string newName);
}

This class arguably doesn't follow the single responsibility principle.

If one wanted to be really strict about always following the single responsibility principle, they could refactor this class into something like seven smaller classes, with much more specific names than a "Manager":

class PlayerChangedEvent : Event
{
    void AddListener(Action<Player> listener);
    void RemoveListener(Action<Player> listener);
    void Invoke();
}

class PlayerNameChangedEvent : Event
{
    void AddListener(Action<Player, string> listener);
    void RemoveListener(Action<Player, string> listener);
    void Invoke();
}

class PlayerHealthChangedEvent : Event
{
    void AddListener(Action<Player, int> listener);
    void RemoveListener(Action<Player, int> listener);
    void Invoke();
}

class GetLocalPlayerQuery : Query
{
    Player Execute();
}

class RegisterPlayerCommand : Command
{
    Execute(Player player);
}

class DeregisterPlayerCommand : Command
{
    Execute(Player player);
}

class SetLocalPlayerNameCommand
{
    void Execute(string newName);
}

Now is this objectively better?

I think there are some downsides to this approach:

  • Encapsulation suffers; there's less opportunity for hiding implementation details when multiple classes need to implement similar kind of functionality.
  • The dependency graph gets more complex with more objects being injected to more objects.

  • Discoverability gets worse; it can (in my experience at least), be easier to find functionality grouped inside 25 manager classes rather than 250 tiny classes.

  • There's more code, more classes, more "noise".

  • The API for all clients becomes a bit more complicated. Instead of acquiring one object and calling three methods on it, they'll need to acquire three different objects and call a method on each one.

I've worked on an MMO game that had more than 50 manager classes, and I don't think this was ever a pain point of any kind for anybody in the team.

But I think having the concept of managers did make it easier to know how new features should be implemented into the game. Need to implement notifications? Start by adding a NotificationManager.

I think the abstraction of a manager helped make the code base more unified. It also made it easier to plan, discuss about and enforce architectural rules (e.g. "UI layer should not communicate with managers directly, there should always be a Data Handler in the middle").

We did have a couple of managers that ended up with more than 1000 lines of code in them, which didn't feel ideal, and did make it a bit more difficult to work with them - but still I feel like having the concept of managers was a net positive for reducing the overall complexity of the project.

So I think these sorts of higher level abstract concepts like "manager", "service", "handler", "builder", etc. can be useful to have in a code base, to help give everything order and increase cohesion.

That being said, I do also recognize that sometimes people seem to add totally redundant suffixes to classes at random, that only serve to complicate things. Why is this component called a "PanelController", and not just a "Panel"? Why "InventoryHandler" and not just "Inventory"?

Sometimes such things have some logical reason behind them, like avoiding name collisions, but sometimes people just stick the name of the design pattern being used at the end of the class name, which I don't personally think is always such a good idea.

3 Likes