Any fans of Dependency Injection? New framework released.

Hey @eventropy first thanks for the framework. And many thanks cause you introduced me to Fasterflect. Couldn’t have been more happier about it, I really felt like a kid in a candy store. You mentioned that Fasterflect didn’t work out of the box in Unity so you had to make some changes, and included the source for the version you changed. However, building that against anything other that .NET 4.0 didn’t succeed (mostly compile errors about runtime generated code, and not being able to find Microsoft.CSharp assembly - just like when you try to use the ‘dynamic’ keyword) Am I missing something? I know you have a dll there that I could use but it’s handy to have some source code that I could add/modify and build from.

[EDIT] My bad I was building the Benchmarks project not the main project. I thought you modified all the projects to work so I thought if one didn’t, then the others won’t.

Another question, I read your code, you still seem to be using regular reflection (like invoking a method info) and not cached Fasterflect API (the DelegateForXXX stuff) - is there any reason for that?

I haven’t really used your framework in depth yet - Will give thoughts when I do. It seems very promising. What you’re trying to do is very noble. Unity doesn’t seem to like but from my experience

Can Zenject inject UnityEngine.Objects dependencies other than binding to an instance? For ex

public class Script : MonoBehaviour
{
     public BoxCollider boxCollider;
     public Transform target;
}

The Script uses those references to do some work. How can I inject those dependencies? (I guess it doesn’t really make sense here? Cause the usual way is just to assign them from the inspector, or use GetComponent/InChildren/Parent)

@Dustin-Horne : I’m not sure what you mean with your line

container.Bind<YourGameObjectType>.To(yourGameObjectInstance)

What you mean “YourGameObjectType”? You can’t inherit GameObject.if that’s what you mean…

@eventropy I have some more questions if you don’t mind

1- From what I understood, if you bind a concrete type A to an abstract one B, then A will be used (injected) wherever there lies a B.

container.Bind<IFoo>.ToSingle<Foo>();

That means (correct me if I’m wrong) that wherever there is an IFoo reference, it will be assigned to a single Foo instance (i.e. all IFoos will reference the same object)

And

container.Bind<Foo>().ToSingle();

Means that wherever there’s a Foo, the same Foo instance is used.

Now what does it mean to bind more than one concrete class to an interface? You mentioned in your doc this will create a list of dependencies, not really sure what that means.

container.Bind<IFoo>().ToSingle<Bar1>();
container.Bind<IFoo>().ToSingle<Bar2>();

How will an IFoo reference be resolved now? i.e. if I have an IFoo somewhere, will it be a Bar1 or Bar2?
This is kind of like what you did in your example with ITickable, you bind to it many types like Ship, AsteroidManager and GameController.

2- It says in Zenject’s doc “I prefer to avoid MonoBehaviours when possible in favour of just normal C# classes.”

First, why?

Second, doesn’t that get rid of something powerful Unity is characterized by which is being Component-based? i.e. with MonoBehaviours (Components in general) I could just write classes that gives my entity unique behaviours and ‘craft’ it all the way through. Ex an Item GO for ex, I could attach it a Usable behaviour to make it Usable (used on triggers to unlock doors etc), a Consumable behaviour to make it consumable (food, health kits etc), Combinable behaviour to let it be combined with other items, etc etc. And at any time (during edit/runtime) I could add/remove behaviours to/from my entity. How will dealing with plain C# classes give me this flexibility?

Third, what about coroutines? No MonoBehaviours == No Coroutines.

Don’t get me wrong, I’m not saying regular classes are not useful, System.Object classes are pretty good for things like Settings, etc (mainly things that don’t need to be attached to GOs) and they also have a lighter memory footprint.

3- Triggering an assembly reload while playing the asteroid example breaks a lot of links (references) thus I get too many NullReferenceExceptions - I always like to have my stuff play well with Unity’s serialization system, so I could edit code while the game is running and still see the effect of that without having to restart my game. Seems like Zenject’s code needs some modification to achieve that?

1 Like

Sorry, I meant MonoBehavior

Hi vexe,
Great questions. I’ll attempt to answer them

I originally integrated Fasterflect for the purposes of using its caching features but ended up doing the caching myself, since it was easy enough to do (which you can find in TypeAnalyzer.cs). But I still found Fasterflect useful for some of the extra functionality that it adds (such as the Flags.ExcludeBackingMembers setting, more readable names for generics, etc.). But honestly, given that I never really used it for its original purpose I don’t think Zenject needs it anymore and the library would be much simpler without it, so I’m tempted to extract the little bit of functionality I’m using from it and get rid of it entirely.

Thanks! I think Unity is really lacking in this area (code architecture support) and we found something with Zenject that works well for us internally so I wanted to spread it out to the community. What do you mean “Unity doesn’t seem to like but from my experience” ?

No, you can inject into Monobehaviour classes as well, you just need to add [Inject] attributes like the following:

public class Script : MonoBehaviour
{
     [Inject]
     public BoxCollider boxCollider;

     [Inject]
     public Transform target;
}

If the MonoBehaviour is attached to an object in the scene, and that object is a child of the composition root, then it will be injected during Unity’s “Awake”, which means that the dependencies will be available for use in the Start() method. However, if the monobehaviour is created dynamically such as through a factory or a binding that uses ToSingleFromPrefab, ToTransientFromPrefab, etc. then you must use a [PostInject] method instead of Awake() or Start() methods. You can use Awake() or Start() however you should be aware that the dependencies will not have been injected.

In this case, if you have a class that requires a single IFoo reference, then Zenject will throw a “ZenjectResolveException” when you attempt to instantiate a new instance. Zenject follows a somewhat similar convention to what’s used in Ninject here. If an interface is bound to multiple types, then its interpreted as a list.

So, given this code:

container.Bind<IFoo>().ToSingle<Bar1>();
container.Bind<IFoo>().ToSingle<Bar2>();

The following will throw exceptions:

class Qux
{
    public Qux(IFoo foo)
    {
    }
}

However, the following will work, with the result being a list filled with an instance of Bar1 and an instance of Bar2.

class Qux
{
    public Qux(List<IFoo> foos)
    {
    }
}

That line in the doc might be a bit off topic and a little subjective - so thanks for pointing it out :slight_smile:

I think the better question is why use MonoBehaviours if you don’t have to? If you’re writing a class that needs access to the API provided by the MonoBehaviour class, then sure, make it a MonoBehaviour. For example, if you want to display a GUI using Unity’s built in GUI, then you’ll probably want to override the OnGUI() method. Or if you have a bunch of different behaviour all of which is associated with a transform or a mesh (like in your example) then it probably makes sense there as well.

But I don’t think when you start writing a new class you should start by making it a Monobehaviour - you should make it a MonoBehaviour only after you realize you need to

In terms of coroutines, there’s nothing stopping you from using them in normal C# classes. Unity isn’t doing anything all that special to make that happen (as explained here: http://www.altdev.co/2011/07/07/unity3d-coroutines-in-detail/)

This is a great point and I completely agree. I’ve worked on projects before that retained Unity’s ability to change code and re-serialize data live and it’s been wonderful. This didn’t occur to me because it’s been awhile since I’ve worked on a project that made that possible however. I’m definitely going to look into adding better support for this when I have time (or if you want to give it a shot it is open source remember ;))

Thanks for your quick reply.

I’m sorry my bad, it seems I accidentally post without continuing my statement.

What I was trying to say, is that what you’re doing is noble, but I think it’s kind of like fighting against Unity. It’s kind of forcing it to work in a way that it’s not really meant for it. I’ve fought with it a lot too! Usually it ends up with Unity winning and me bashing my head against the wall.

Don’t get me wrong, I would love to do TDD, I would love to program using IoC Container, but not in Unity, IMO all those techniques are for business apps, not Unity…

In Unity you could still program against interfaces, If you want to switch implementations you could do it from the editor! Here’s one way to do it:

public class AI : BetterBehaviour
{
     [SerializeField, ShowType(typeof(AIStrategy))]
     private SerializedType strategyType;

     private AIStrategy strategy;

     private Awake()
     {
         strategy = Activator.CreateInstance(strategyType.Value);
     }

     public void Action()
     {
          strategy.Perform();
     }
}

public abstract class AIStrategy
{
    public abstract void Perform();
}
public class Fallback : AIStrategy
{
    public override void Perform() { Debug.Log("Falling back..."); }
}
public class Attack : AIStrategy
{
    public override void Perform() { Debug.Log("Attacking..."); }
}
etc...

SerializedType is a serialized representation of System.Type. Using ShowType you will get a popup of all the non-abstract implementers of AIStrategy. Just choose one, and then at run-time you create your strategy with whatever type you chose (we could benefit from Fastreflect here…)

(BetterBehaviour, ShowType, etc are some of my home-cooked property attributes in ShowEmAll - which will be available on the asset store for free soon)

What else… Unit testing? - I’m a huge fan of it. I always encourage it because it encourages very good design habits, like decoupling and writing highly focused/cohesive classes. In business apps it’s a must. But again, come to Unity, it’s also nice to do - but that’s in Theory, in practice though is it really feasible? Is it really worth it to add all those layers of abstractions, write all those interfaces, add extra complexity just to get a test working?.. IMO no.

I also saw StrangeIoC today, downloaded their examples… and oh boy… it’s just an over-overkill. The examples were very simple, yet the code was highly complex. That’s for a small example, imagine how a large project would be?.. I think great code is one that’s easy to understand by everyone - More POO than OOP (Programming fOr Others)

It’s not our fault though that Unity’s API is shitty. But good news is that they admit they made a lot of design mistakes and are fixing it… Hopefully we see more support for Unit testing and interfaces.

I’m never against learning something new - something with a high learning curve. I’m willing to spend time, money and effort if the thing would repay me back what I put through it. But IMO, the problems that comes with IoCC when applying it in Unity are as many as the good value it provides.

I would love to be proven wrong. I would love to hear from you how far you went with this, how big of a project have you used IoCC in?

Lastly,

public class Script : MonoBehaviour
{
   [Inject] public BoxCollider boxCollider;
   [Inject] public Transform target;
}

I still don’t get what will the values be for ‘target’ and ‘boxCollider’?

I hope I don’t have any chopped statements this time :smile:

Thank you!

I have been doing some thinking about IoC and its OOP design. What I am trying to wrap my brain around (and I think it relates to what Vexe is saying) is that does IoC lead you to the problem - a concrete class for each kind of entity? Richard Fine refers to the problem here - http://www.gamasutra.com/view/news/124682/Opinion_The_Six_Misconceptions_I_Had_About_Unity.php - in misconception #2. I have been down the road of a terrible hierarchy of “types of objects” he refers to and it is not a good one.

Unity is not “fighting” so much as it stresses “has-a” relationships. IoC seems, to me at least, to stress “is-a” relationships. (Now I am aware that Zenject can inject MonoBehaviors, but this behavior is much the same as linking objects in MBs. So it kinda devolves to the Service Locator pattern, which Zenject can do as well). IoC seems to shine when working with pure C# classes (because you don’t have to pass int the dependencies).

My thinking is still evolving on this so I would love some feedback. Vex did I accurately rephrase what you are trying to say?

Partially yes. When I first came to Unity, I came from a pure OOP environment. I didn’t know anything about Unity’s component-based approach so I initially designed all my classes in a pure hierarchical form (pure 'is a’s). It ended up pretty bad, very inflexible - Things were very different after I started thinking in terms of components (has a).

But I was talking more generally, I don’t know about you, but I’m kind of a stubborn guy, if something doesn’t work the way I want to I try to force the situation this is why I end up ‘fighting’ Unity. For ex trying to make property drawers work for arrays or properties, or when I first came in, I didn’t know that Unity isn’t so friendly with interfaces and generics, I would have all my designs depending on having to deal with interfaces and generic types, only then to find out that Unity doesn’t serialize interfaces nor generic types, so I fight, I try to force it to be friendly with those, but it just won’t… (there are solutions now to interfaces and generic types of course…)

I’m just saying, a lot of Unity’s API and design decisions are bad, if we try to force it to play well with good coding quality standards, things “might” back-fire against us (at least from my experience)

I hope to see IoCC used more in large scale projects to see if it will make it back in shape.

You mean drag-dropping objects via the inspector? Yeah that’s the thing, if you have an abstract reference you could just drag/drop the implementation you want.

I think if one would shy away from the idea of an IoCC in Unity it might be because of:

  • It’s natural to resist at first cause it’s brand new to him. How did people first react to the idea that earth is not flat? They prosecuted the guy who claimed so… It’s ok to feel some resistance but it would be foolish to be closed-minded, eyes and ears shut not to listen to what the other guy has to offer and say, cause it could be the truth!
  • The fear of a back-fire due to the potential of the idea ending up in a fight with Unity (like mentioned ^)
  • The lack of practical real examples.

More on 3:
Say someone’s asking you how delegates work, you explain to him with the following code that you could have a delegate reference a method, and so when you invoke the delegate the method gets invoked too:

void Method() { }
void Main()
{
    Action a = Method;
    a();
}

Him looking at this, mostly likely his first reaction will be “I could just invoke “Method” immediately, why would I do it like this?”
And he’s right, because that’s not how/where you’d use a delegate.

Now let me ask, when would you use Poor man’s DI or an IoCC to inject dependencies? The answer (correct me if I’m wrong) depends on the number of dependencies (hmm, the answer ‘depends’ sounds like we could inject the answer lol). If you had few, you’d go with Poor man’s. If not, you’d use an IoCC cause that’s where it’s meant to be used. An IoCC doesn’t do anything you can’t do with Poor man’s DI (by hand) - it just does it cleaner and more efficiently.

Looking at the examples provided demonstrating the use of an IoCC in Unity, they’re just pretty small. There’s very few dependencies. So it’s natural for people new to the concept to not be able to see the power of an IoCC. If you show them a big project with many dependencies with and without using IoCC they would clearly see the difference and appreciate the concept more, like would you rather do this:

var svc =newShippingService(newProductLocator(),newPricingService(),newInventoryService(),newTrackingRepository(newConfigProvider()),newLogger(newEmailLogger(newConfigProvider())));

or this:

var svc =IoC.Resolve<IShippingService>();

?

The answer is clear. I hope you got my point (the code snippet is taken from here btw)

It’s easy to say that something is an overkill/overdesigned/overengineered/overwhatever when it’s divorced from the practical context it’s meant for.

I agree with a lot of what you’re saying. Good practices such as IoC, unit testing, etc. can often result in too much overhead, to the point where it’s just not worth it. Using an DI framework would be overkill for simple applications, and the maintenance costs of unit tests can quickly become painful. As you say, it’s also harder to justify when writing games as opposed to business apps. And on top of that you have to consider the learning curve… which in the case of a DI framework can be a lot from what I’ve witnessed at my company.

But our experience with Zenject has been a very positive one all things considered. At my company we’ve been working on a multi-year project and there’s just no way we could have had it scale the way it has without using a DI framework.

I’m not going to hold my breath that Unity is going to really fix things though - I think they are pretty limited in terms of making large changes to the current design. Not sure if you’re aware but Unity did release a unit testing framework recently (unity test tools). I wasn’t able to integrate it into our pipeline in a way that I was really satisfied with, but YMMV.

Zenject definitely does ‘fight’ the way that Unity encourages developers to work, and I think that’s a totally valid reason to be skeptical about it. But as you say Unity’s API has a lot of problems so fighting that may actually be a good thing. We’ve iterated on Zenject quite a bit over the last year and I think we’ve landed on something that plays pretty nicely with it

Sorry I totally misunderstood what you were saying. When dealing with things such as Colliders / Transforms / etc. it does seem easier to me to just inject via Unity’s normal method (that is, as public fields in a monobehaviour)

I’m also having trouble seeing this in the domain of game development. A game development specific example would help.

@eventropy , you mentioned two things together that contribute to this. The first is that DI becomes more useful as complexity increases, which I’ve seen for myself and agree with (I’ve been involved in one major project where it was used to great benefit - but that wasn’t a game project). Then you mentioned that games can be amongst the most complex types of software… but I don’t understand where that complexity arises in regards to dependency management, which simply hasn’t ever been an issue to me in regards to game development in particular. Having said that, something doesn’t have to be broken for a better way to exist.

To be fair, I’ve never worked on something with the scope of, say, GTA or anything similarly large, so maybe it’s just that I’ve never worked on a project of the scope where this becomes worthwhile. On the other hand, I could just be misunderstanding where the practical advantages are to me and/or my projects, and if that’s the case… well, I’d love to be enlightened. :slight_smile:

The advantages are listed in the Sebastino’s articles in post #3. Those 2 blog posts were combined into 1 article for GamaSutra. As with almost everything in life, there are pros and cons with this type of approach.

I have… If you assume Assassin’s Creed 3 was of the same scope as GTA, and we never needed or used DI. The complexity of a video game is usually horizontal - number of features - instead of being vertical - depth of a feature. I always thought DI was useful mostly when you have complete sealed system that requires clear communication channel to another unknown - but defined - interchangeable system. To me, it is like a composition pattern but at the system level. Which frankly… I still wondering about the usefulness in video games. Every example I’ve seen just add complexity to the system without any direct gain.

Yeah… My issue with that article is that I find his “Unity golden rules” to be… well… stupid, so it doesn’t start too well. He has an horribly reductive view of what a component pattern can do or should do. On top, his 3 “Unity solutions”… Well, two I never use - GameObject.Find/Object.FindObjectOfType - and singleton is only for game-wide manager, so I should end up with big dependency issues, right? I guess he never used pool, registrable pattern, composition, anonymous broadcast or other pattern I must be forgetting about. “I don’t use singleton because I don’t like using singleton”. Meh. Singleton are a tool like any other tool and it has a specific purpose.

1 Like

What I am trying to wrap my brain around with Sebastino’s golden rules is that it seems to push me toward the problem I mentioned above in the Richard Fine article. If your MBs are only for interacting with the attached GameObject, then all your “Manager” classes are pure C# classes. How then do you bolt (or remove) behaviors on to GameObject programmatically (because bolting/removing behaviors on to GameObjects is the whole reason you use a component based architecture) in pure C#?

Lets take an extremely simple example. Lets say you have racing game. All the racers get one type of a certain number of cars, including the player. The player chooses to race with CarFrameA, say a Prius. The application also gives CarFrameA to one of the AI opponents. Now the game designers want the player’s CarFrame to have an extra behavior, say nitro boost; but not give the extra behavior to the AI’s CarFrameA. If you are creating these “Car objects” programmatically in a MonoBehavior, this is pretty easy. Instantiate(CarFrameA) twice. For the player’s CarFrameA, addComponent().

How do you accomplish this in pure C#?
A) 1 way is to never create objects programmatically. You Prefab everything. But that is really not maintainable. It would be a nightmare to maintain prefabs for each type of car, both Player and AI controlled.
B) I guess you could try to make a system to make pure C# act like components; some kind of IBehaviors[ ] in a base abstract class. But now switch from my simple example to a RPG with many, many types of behaviors. This quickly becomes a mess and any solution I can think of along this line ultimately leads me right back to Richard Fine’s hierarchy of types problem.

@eventropy I have looked at your Asteroids example that comes with Zenject. How would you modify your “Ship” GameObject to potentially add/remove behaviors at run-time?

Yeah, that’s more or less my exact impression too. Like I said, the fact that the way I currently do things isn’t broken doesn’t mean it can’t be improved. But at this stage I don’t understand where this might offer practical improvements for my use cases.

I agree re: the 3 Unity solutions… I looked at that and immediately thought “what about all the other ways you can achieve those things?” Before we even get to the solution, I don’t see that the problem he’s describing actually exists. (I don’t agree with his set of rules, though I do have my own set of rules that work for me.)

Having said that, still, I mostly work on small projects. Perhaps if I was working on something with a large team of coders this stuff could be super handy. As I said, I’ve seen examples of that being the case outside of game development. I haven’t seen examples within game development.

I’m not trying to shoot the concept down. I’m genuinely interested in whether or not it can make my work easier.

@angrypenguin , @LightStriker_1 don’t wanna go too astray, but since the subject’s been brought, could you guys elaborate (if you can) about your ‘golden rules’?

I’m still building my own so it’d be interesting to listen to others.

I did agree on one thing though, where he said MBs should operate on the GO they’re attached to - I did find it simpler to just do that, instead of having the MB operate on a parent object or something. Consistency is nice. It’s not a must, but it is to be preferred IMO.

But I agree that the solutions he mentioned were very trivial, stuff that you see at newbie youtube tutorials. (GO.Find, etc) - There’s many good ways around not needing to do such things.

And… could somebody tell me how to quote someone properly? I don’t see a quote button in the new editor I just use the QUOTE tag but it doesn’t show “XXX said”. Do I have to click reply on somebody’s post and leave only what I’m quoting him?

Reading the rest of his ‘golden rules’

  • MonoBehaviours are reusable, single responsibility classes.
  • GameObjects should not be created without a view (that would be a mesh, collider or something that is really an entity in the game, the only exceptions are for empty GameObjects used as “folders” and other specific cases I will illustrate another time).
  • MonoBehaviours can know other components on the same GameObject using GetComponent.
  • The behaviour of a GameObject should be extended adding MonoBehaviours instead of adding methods to a single MonoBehaviour.

Well:

  • That’s true, I think it’s pretty good to design you components like that to achieve their max potential (S in SOLID)

  • Not so sure about that, he did say he’ll illustrate later.

  • OK… that’s just a fact. If you’re in a MB and write GetComponent you can access other Components on the GO the MB is attached to.

  • That’s good too IMO. The O in SOLID. Open for extension and closed for modification. For ex if you have an AI class - and you wanted to have different AIs, you’d extend the base AI and write your custom AIs instead of writing/adding everything to one class.

Maybe I’m missing something, but @LightStriker_1 care to elaborate why you find these to be ‘stupid’? Or maybe you just meant the first rule, mentioned in my previous post?

As you wish;

To me, it’s an horrible idea. How about spawners? They role is not to operate on themselves, but to instantiate a prefab. How about spline? A spline does nothing by itself, but other GO will use it to navigate or place themselves in space. How about game-wide manager? They are usually a GameObject with a single script that does something related to the whole game, like saving a score, loading a scene, handling sound, pooling objects, managing inventory, loading resources…

A MonoBehaviour can be a container of information, a manager, a creator of other GameObject. They can be many things.

MonoBehaviour are exactly like any object-oriented class. Resuable? Maybe, maybe not. My Player script in a single player game is only used once. Same for my game-wide manager… Why would I need two instance of the same script to save my score? As for single responsibility, it’s usually a good idea when structuring your classes, but it really depends on your definition of “responsibility”.

That’s an horrible idea. Why would spawners need a collider or a mesh? Their uses is to have a position and orientation in space and to instantiate a prefab at those coordinate at run time. A spline has no mesh or collider, but is horribly useful to get coordinate for say… placing a camera, or a moving sport car, or a patrolling guard.

A GameObject is a container that happen to have a transform component. Which means anything that needs a position or an orientation can very well be on a GameObject!

Also, while I would prefer to place game-wide managers in their own loading scope, I end up placing them on prefab so I can change their parameters. So my managers are also on GameObject, even if I don’t care about their transforms.

I’m not sure how this is a “rules”. However, since I’m a user of composition pattern, I don’t always link my component by “GetComponent”. I often create “sub-component” directly from the inspector.

That’s the idea that a component should almost have only a single method and you should build behaviours with countless MonoBehaviour, like lego blocks. For some cases, it’s a good idea, for other, it’s as dumb as a rock. It’s a case-by-case issue, so making it a “golden rules” is simply blocking you from any other pattern or design that would not directly fit in this socket.

Let’s say I want a Particle System to self-destruct once it stops emitting. I made a SelfDestructFX script that does just that. It makes sense for this behaviour to be “one-job oriented”.

However, if I want to add a “jump” to my player, I will not create a “Jump” MonoBehaviour! The communication between script would quickly become insane! I will add methods to my Player class, or even better, if I’m using a state machine, I will create a new “PlayerJumpState”.

I’ll create an independent MonoBehaviour if it makes sense to do so, if it makes sense to be reusable, and if its communication channel to other script are clearly defined. I’ll not build a grenade by adding a “throwable” script, a “fuse” script, and a “exploding” script! I’ll just write a “Grenade” script!

MonoBehaviour does not prevent class inheritance and you should be using that like any other tool at your disposal.

Well, that’s what I do.

As for my golden rules?

  • Use the proper tool for the proper job. Singleton, static, composition, DI, interfaces, inheritance, and all other code pattern are tools best suited for a specific job. Don’t try to fit a square peg in a round hole. Sometimes there is no proper tool for the job. Make one from existing tool, and create a new one. They were not made for that? If it works and there was no better alternative, who care?
  • GameObject are spatial containers of components.They are a point in space or a prefab, nothing more.
  • MonoBehaviour are instances of a class that exist on a GameObject and have access to a collection of method and callback for handling said GameObject interaction. They also have access to the game update loop.They may or may not care about their GameObject.
  • ScriptableObject are loadable container of information.
  • Any of the above type can be created by data - in the editor - or by code at runtime. Choose the method of creation that best suit the specific problem.
1 Like

This is something we could literally write books on. Multiple, large books. The question kind of boils down to “how do you approach software architecture in Unity?”

With that in mind, please don’t take any of the following as me bashing the author’s golden rules. The fact that I don’t agree with them could very well come down to the fact that it’s impossible to do the topic justice in 5 short dot points.

That’s why I prefer to use “roles” instead of “responsibilities”. A role can involve multiple responsibilities, but they should generally be very closely related. If a component is handling unrelated responsibilities then it’s time to re-consider what it’s role is. On the other hand, if you find that a lot of different things have similar, overlapping responsibilities or that related responsibilities are spread over multiple classes, it might be time to see if the roles can be re-organized and consolidated.

This is similar to one of my rules, but it’s not complete in this form. Indeed, as LightStriker points out, what about roles that by definition are all about interacting with other GameObjects?

So rather than having a golden rule that a MonoBehaviour won’t operate on a 3rd party GameObject, I instead have rules about what strategies will be used when operating on different categories of GameObjects.

Like LightStriker, I’m not sure why this is a rule or what it’s trying to say about how things should work.

I have a few policies about how different things communicate with and/or are aware of one another. There are far more ways to do this than are mentioned in the article, all of which can be excellent tools in different situations. For me, the most common (but not the only) three in no particular order are automated hookups* (used wherever possible to reduce manual labour from designers, typically within a single GameObject or between one GameObject and child GameObjects which it is considered to own), direct Inspector hookups (where a designer needs control… if they don’t need control then why aren’t you automating it?), and communication via events (wherever the communicating entities can’t be or don’t have to be directly aware of one another - a situation I do my best to maxmize).

I don’t mind this one, but that’s because I don’t interpret it how LightStriker has. I see it as more or less a re-statement of how component-oriented design works - don’t make monolithic components that do lots of things and are specific to an entity, instead make components that define roles and use different combinations and configurations of these to define more complex aggregate behaviours.

  • GetComponent et. al. are common here, but are not the only available approach. Consider how you might achieve similar things if you weren’t working within Unity. All of those “non-Unity” approaches work here, too, and should be considered in addition to the tools Unity provides via its own API. Don’t let the fact that you’re using a MonoBehaviour blinker you into thinking you have to use the Unity API for everything. Furthermore, don’t let the fact that you’re using Unity blinker you into thinking you have to use MonoBehaviours for everything.