Search Unity

OOP vs Component Based Architectural Design

Discussion in 'Scripting' started by Nigey, Dec 10, 2013.

  1. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Hi Guys,

    I come from Unreal, hail! I've been with C++ and UnrealScript for 2yrs and am well used to OOP architecture.

    It looks like from what I can find online Unity uses a component based design for code. It doesn't have any code hierarchy architecture.

    With that in mind.. How do you design code for large professional projects, with a lot of class inter-relation without a structured hierarchy?

    I can only find short tutorials on creating Unity features. Not any on professional code design.

    Any ideas on best practice?

    Thanks
     
  2. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
  3. orb

    orb

    Joined:
    Nov 24, 2010
    Posts:
    3,038
    With the component model you might have one core class that knows about the possible components. For example a player class (always present) that looks to see if there's a jetpack class (and knows to use that instead of regular movement when jumping, for example).

    Typical damage effects could be a component you temporarily add on top of an actor object. Set something on fire by applying the Fire script, which looks for a health component and ticks it down over time, then removes itself. The health component takes care of removing the object if it dies.

    With AI you just make a movement component that makes it do stuff every update, instead of inheriting from some type of generic movement script. Flying, swimming, running enemies would all be different.

    There's also the game manager-based approach, which could work for some types of games. Then every object has components completely unaware of each other, and the manager accesses them as needed. Useful for more abstracted games like strategy.
     
    kendallroth likes this.
  4. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I think you may not understand precisely what OOP and Component-based design means. They are not mutually exclusive, even if sometimes they do clash.

    OOP is a language paradigm: http://en.wikipedia.org/wiki/Object-oriented_programming
    Component-based architecture is a "is a branch of software engineering" (whatever that means): http://en.wikipedia.org/wiki/Component-based_software_engineering

    You can do Component-based architecture in OOP... And you can do Component-based architecture without OOP.

    OOP is a way to build a logical hierarchy of classes, while components is a way of aggregating the different behavior of an objects. Note that component structure are not exactly "visible" in the code, while a hierarchy of classes is.

    Let's be clear that class != object. A class is the definition of what will become an object.

    For example, not every object in the world has a visual, a collision, and so on. From what I remember on my time on Unreal 2.x, Unreal is not "pure" OOP in that aspect. Since Unity is using language like C#, you can build your own hierarchy of classes. Polymorphism is simply a too big feature to not have it around.

    http://www.openchord.org/wordpress/2011/09/unity-and-the-component-model/ is a rather diminutive view on what could be done.

    It is just one way to see it. Personally, I would go:

    Component -> NPC
    NPC has a "Behaviour" parameters;
    - Object -> NPCBehaviour -> Animal Behaviour -> Cat Behaviour -> Kitten Behaviour
    And it also may have the parameter which handles motion and skeleton;
    - Object -> Motion -> Quadruped

    I assume that "NPC" has enough code that is shared by all NPC, such as communication, path finding, inventory, health, and so on.
    That "Kitten" is exactly the same behaviour as a "Cat", but may only attack other kitten and never deals damage.
    I also assume that all quadruped control their skeleton in a fairly similar way.

    The question I would ask is, does "Young Animal Behaviour" contains enough generic code for any type of animal to warrant a reference instead of a hierarchy?

    So
    Code (csharp):
    1.  
    2. GameObject
    3.     |-> Visual
    4.     |-> Collider
    5.     |-> NPC
    6.          |-> Kitten Behaviour
    7.          |-> Quadruped
    8.  
    When you tried to split up a behaviour into too many separated component, you get communication problem as each pieces may need to talk to each other. If I remember right, the question you should ask yourself is "Is X a part of Y, or X is Y?" Is there real benefice in splitting something in smaller part?

    Just my 2 cents...
     
    Last edited: Dec 10, 2013
  5. orb

    orb

    Joined:
    Nov 24, 2010
    Posts:
    3,038
    Exactly. There's more than one way to skin a model.
     
  6. SmellyDogs

    SmellyDogs

    Joined:
    Jul 16, 2013
    Posts:
    387
    Its just jargon bingo. It all winds up as Spaghetti in the end.
     
    seriousguy888 and MarvelTile like this.
  7. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,822
    You can use both together quite nicely.
     
  8. mescalin

    mescalin

    Joined:
    Dec 19, 2012
    Posts:
    67
    i have no idea what that means... all my c# is object orientated
     
  9. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Yes, C# doesn't give you the option, everything in it is an object. However, it's not the case of all OOP language. For example, you can code in C++ (the non-managed version) without any objects.

    What many people fail to realize is, OOP is only a language paradigm, and in some case it may influence your final code design. However, it should never be a limitation. For example, I knew someone who so strongly believed in OOP in every sense of the term, that he would never use any static class and would frown on singleton, because it goes somewhat against the notion of "objects".

    A language is a collection of tools and paradigms. You can choose which one you use and when you use them. If you want to use a screw with a hammer, it's up to you, even if I'll think you're a moron. When you'll pull that screw out, you might rip out more of your code with it.

    All in all, there's thousands different ways of doing thing in code. However, some will be longer to write, and some will be harder to maintain. Some will give a quick short-term result, but will take longer to expand, while some other will take a lot longer initially, but will flow out quickly in the end.

    After a while, I've come to notice the pitfall Unity tends to push coders towards. For example, many tends to compartmentalize their code within multiple components, making it very hard to maintain, both in data and in code. Many completely forget the existence of ScriptableObject or even object derived from System.object, which both have pros and cons versus MonoBehaviours. Or worse, some projects I have seen, the coders enforced a strong code-driven approach, to the point that doing anything new or any changes always involved coding.

    However, the worst I have seen, some less experienced coder completely forget the notion of hierarchy or polymorphism. I have seen a project where every class only derived from MonoBehaviour! I can partially understand the issue. Serialization of polymorphic class in Unity is no walk in the park. It took us a while to implement a framework that would give designers complete ability to create instances of a derived class from the type of a field, from within the inspector.
     
  10. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Well, it does have hierarchy, it just doesn't use it heavily.

    As a general rule of thumb, "composition is better than inheritance", and this is generally the principle that the GameObject/Component design of Unity follows. Rather than having different types of GameObjects, GameObjects can contain different combinations of Components. The Components give the GameObject its functionality, and ideally each component should do one thing and one thing only.

    If you're heavily into OOP then this is a bit hard to get your head around at first. It took me a while, but the more experience I got with it the more I liked it.
     
  11. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    Made me laugh, cause I think often it is true.Best intentions and all...
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
  13. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I disagree. It will become chaotic, only if you let it happen. It's a constant ongoing war between order and chaos.
     
  14. marak-v1

    marak-v1

    Joined:
    Jul 7, 2013
    Posts:
    20
    I think you just summed up 90% of my work, fighting between order, chaos and sanity.
     
  15. Patico

    Patico

    Joined:
    May 21, 2013
    Posts:
    886
    Special for this issue component-based architecture assume to have a broadcast messaging, in Unity this is implemented by SendMessage() method. As result, it's not necessarily to have a link to other components - just send broadcast message and if gameobject has a suitable component it will get it.
    But if you want to be sure if recipient is exist and message will be delivered, you can use SendMessageOptions.RequireReceiver parameter of SendMessage() method, or [RequireComponent] attribute and get component by GetComponent<>() method (but I suppose it could make coupling a little stronger)

    This technique allow to break links between components, and make them are totally independent. If my InputControllerComponent call SendMessage("GoForward") - doesn't matter what components gameobject have - KittenBehaviour or BirdBehaviour, if it have GoForward() method it will go forward.
     
    MarcSpraragen likes this.
  16. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I think LightStriker is quite right: the component-based approach that is heavily favored in Unity tends to lead to rather poor architecture in big games. It doesn't have to be that way, but it often is; to make a good clean architecture, with a separation between model and view that allows for robust testing, AI, etc., requires discipline and hard work. Conversely, just throwing all your game state into the scene graph is the quick easy path.

    This is exactly what I was getting at in my "Are Unity components the Dark Side?" thread.

    Personally, I've decided to go ahead and embrace the Dark Side when working with my boys, especially on small projects. We have a big library of components now that we can just snap together in myriad ways to make a game very quickly. This gets them quick feedback and gratification, and makes the whole process fun, which is the most important thing for them right now. And, as Patico suggests, we use a broadcast system to avoid any tight coupling between the components (though we don't use SendMessage, preferring instead a broadcast/receive system of our own design).

    In my own more serious projects, though, I'm keeping a strict separation between model (game state) and view (scene graph). I write the model classes first, using test-driven development, and keep my view and controller (i.e. glue code) as thin as possible. This avoids the nightmarish spaghetti that comes when you mix state and display together in a large game. Coming from a strong OOP background, Nigey, it sounds like this approach might sit well with you too.
     
  17. Dantus

    Dantus

    Joined:
    Oct 21, 2009
    Posts:
    5,667
    As this thread is about good practice for huge code bases, you can exclude SendMessage and any other string based call. There are a lot of topics you can discuss, but string based calls are just a bad idea if you want to have maintainable code. You can easily produce hard to spot errors. First, you can have a typo for the string and the same is true for the method you want to call. And good luck for the next refactoring when you want to rename a method.
    If you make it solid, you find those issues at compile time already and you won't have any issues with refactoring.

    I know that MonoBehaviour was implemented like that, it still doesn't make it a better idea. Anyone who ever made a type in something like OnCollision... or whatever method knows exactly what I mean. This could easily be avoided with a solid design.
     
  18. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    To me, SendMessage is a hack to try to patch an horrible disease, not a cure.

    "KittenBehaviour" could be a parameter of your component and it would be a far more solid approach without any string calling.

    Code (csharp):
    1.  
    2. public class NPC : MonoBehaviour
    3. {
    4.     public NPCBehaviour behaviour; //Put the kitten behaviour derived class in there.
    5. }
    6.  
     
  19. Nigey

    Nigey

    Joined:
    Sep 29, 2013
    Posts:
    1,129
    Well with all these different answers it looks like I have my work cut out for me. So all in all, it's pretty difficult to structure massive projects in a clearly understandable segmented fashion.

    Looking through the (massive) list of answers, there's a few different views. Some people just think it is messy so why bother. A few more think there's several methods to organise large projects clearly. Some think you just need fight through it and try to keep it organised.

    What I'm taking from this so far, is that it is actually very easy to make messy spaghetti code that becomes reliant upon having a code/scene relationship. I guess the best option for a large design is to write it out on paper first (as stupid as that sounds). For me atleast, if it's a large project I like to create a full design of the entire project and make allowances for changes in paper design and then create all the game objects in alignment with the design theory I've written. So atleast I have in my head the way I want the overview of it to be done. For me if I can segment everything clearly in my head then I'm able to change and modify everything cleanly. Like creating a clean workspace.

    So the best way to do this is to try and make the classes by clean separate components, with or without a main attached class that recognises what components are attached to the object and work from that?

    What I have found in my SHORT time in Unity (keep in mind here guys I am here to learn, I'm no master and wont pretend to be) is that it's easy to start writing code that directly relates to what is in the scene at the time, and not what is in the project, meaning it's all too easy to tie up unnecessarily objects by code in the scene, so it's less clean when writing new levels with new features and more individual tasks than the less generic ones of each level.

    It looks like the best way to get organised code is to either create generic classes that segmentally (a real word lol?) make up the gameobjects you want. Then create prefabs with all the code attached, mix and matching the different classes each one needs, and try to make them as generic as possible, while needing to subclass more specific features of the generic ones. Or perhaps even a controller class that attaches different components runtime instantiated gameobjects?

    Keep in mind this theory is very different to what I'm used to.

    Thanks for the many responses by the way guys. It's something that since I've started Unity, is something that is the most immediate thing to know (imo), but something that I've never seen mentioned comprehensively in any tutorials.
     
  20. SmellyDogs

    SmellyDogs

    Joined:
    Jul 16, 2013
    Posts:
    387
    Teach me master. :D
     
  21. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Lesson 1; You don't have the time to not take the time.

    This line was first made when I was a designer. In a project, the producer tried to shovel the game design forward and force the production to start right away while some of the mechanics were not designed yet. In the end, you lose more time if you don't take the time to design and prepare yourself. Others calls it front-loading, but this is mostly said when the production is already underway. I also found it applies pretty well to coding too. Some people want to start coding and want to see result right away. They make bite-sized code block that exist only for a very specific purpose, and any changes will automatically involve code rework. The idea is not to waste your time, but to make it meaningful down the road.

    Take the time to decide if it should be generic or specific codes. Generic code can be complex, it doesn't matter because it's generic. Generic code should be shielded against design changes. For example, I've built a camera system that is completely generic.



    The design could change a thousand times, I'll never have to rewrite any part of that system. I simply can make every possible camera you could think of, without adding any code. Since it's built in a "lego/data-driven" style, adding a new missing part is a matter of a few lines, and that new lego block can be combined with all the existing one. The system is currently used in 3 different projects, and no new code is needed!

    When you decide that you must do something specific to a design request, take the time to understand the GOAL the designer wants. Most designer will express how they think the thing should work, but most designers don't have a code/math background. You have to take the time to decipher the task they want it to perform and to get rid of all the "how it should work". Finally, take the time to analyses the shortcoming of what the designer asked. Is there loopholes? Logic flaws? What the design could ask you to change? What parameters could you expose or create to give him more flexibility, so that if he wants to change something, it may not automatically involve code rewriting? Finally, since it's specific, make it as simple as possible, because it will most likely have to change at some point.

    Just that allowed me to keep most of the spaghetti away. :p
     
  22. marak-v1

    marak-v1

    Joined:
    Jul 7, 2013
    Posts:
    20
    Nice answer lightstriker, nice to hear from someone with experience (as compared to myself learning by f$%&ing up haha)
     
  23. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    bit of a late reaction... but I learned deriving from Scriptable object gets you the closest to "standard" OOP.
    I just give all my classes that need to be in the scene a GameObject field that will load the prefab it needs for the Resource folder.(using an enum to avoid string typos)
    and so i can access Monobehaviour components if i need them (i.e. collider) via the prefab. On the other hand i can easily create instances of my classes in code to work with, without them having to be a component alive in the scene on a gameObject.

    AND... serialization is quite handable with ScriptableObject! ;-) using the unity build-in system [System.Serializable] and [SerializeField]
     
  24. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    interesting. could you please post an example? is the gameobject field static? what is the enum used for? converted to string for resources load?

    is serializable and serializefield still required with scriptable object? i thought it saves the whole class automatically but i'm probably wrong.
     
  25. Giometric

    Giometric

    Joined:
    Dec 20, 2011
    Posts:
    170
    Any class that derives from ScriptableObject will be serialized by Unity automatically. [Serializable] is only needed for objects of any type that aren't serialized by Unity normally (like Vector2, Vector3, float, etc), or don't inherit from UnityEngine.Object, that you want Unity to save the values of.

    So, for example, if I create a class like this:
    Code (csharp):
    1.  
    2. public class Cookie : ScriptableObject
    3. {
    4.     public int numChocolateChips;
    5. }
    6.  
    The class Cookie will be serialized by Unity since it inherits from ScriptableObject. The field numChocolateChips will be saved as well because it's a basic type that Unity will serialize (Unity will serialize all basic types except for long and double I believe). So some other MonoBehaviour/ScriptableObject based class that stores some Cookie objects can have:

    Code (csharp):
    1.  
    2. public Cookie cookie;
    3.  
    And the field will be serialized. However, if my class Cookie does not inherit from MonoBehaviour or ScriptableObject, it needs to have the [Serializable] tag placed above it:
    Edit: fixed this next example, see LightStriker's post below for an example of [SerializeField]
    Code (csharp):
    1.  
    2. [Serializable] // (or [System.Serializable] if you don't import System namespace)
    3. public class Cookie
    4. {
    5.     public int numChocolateChips;
    6. }
    7.  
    Now a MonoBehaviour or ScriptableObject class would automatically be able to serialize any fields of type Cookie. Note that for its fields to get saved, they have to be serializable by Unity as well.

    [SerializeField] is for fields that Unity wouldn't serialize normally because of their protection level (private, protected). You use it when you want to be able to save (and access in the inspector) fields, but keep their access private/protected for scripting purposes.
     
    Last edited: Feb 25, 2014
  26. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    You cannot put [Serializable] on a field. You can only put it on a class definition. (Class|Struct|Enum|Delegate)

    Code (csharp):
    1.  
    2. [Serializable]
    3. public class Cookie
    4. {
    5.     public int cost;
    6.    
    7.     [SerializeField]
    8.     private float chips;
    9. }
    10.  
    11. public class Factory : MonoBehaviour
    12. {
    13.     public Cookie cookie;
    14.  
    15.     [SerializeField]
    16.     private Cookie secretCookie;
    17. }
    18.  
     
  27. Giometric

    Giometric

    Joined:
    Dec 20, 2011
    Posts:
    170
    Er.. woops! My bad, will amend my post/example above. I've done the serializable class thing more than once, even, not sure how I mixed that up.
     
  28. jister

    jister

    Joined:
    Oct 9, 2009
    Posts:
    1,749
    yes only public variables get serialized automatically...
    example:
    This would be my base class for any character
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [System.Serializable]
    5. public class Character : ScriptableObject
    6. {
    7.     [SerializeField]
    8.     private int _id;
    9.     [SerializeField]
    10.     private string _characterName;
    11.     [SerializeField]
    12.     private GameObject _thisObject;
    13.     [SerializeField]
    14.     private Texture2D _icon;
    15.  
    16.     public int ID
    17.     {
    18.         get{return _id;}
    19.         set{_id = value;}
    20.     }
    21.     public string CharacterName
    22.     {
    23.         get{return _characterName;}
    24.         set{_characterName = value;}
    25.     }
    26.     public GameObject ThisObject
    27.     {
    28.         get{return _thisObject;}
    29.         set{_thisObject = value;}
    30.     }
    31.  
    32.     public Texture2D Icon
    33.     {
    34.         get{return _icon;}
    35.         set{_icon = value;}
    36.     }
    37.  
    38.     void Kill()
    39.     {
    40.         DestroyImmediate(this);
    41.     }
    42. }
    here's a dropbox link to a partial example project https://www.dropbox.com/sh/34srhtag9ykxy4w/hl7I3ECE3D

    also the [System.Serializable] give you a nice code file in unity you can double click and see all it's values

    and I love using enums for all sort of thing, as you said to convert to string for a resource load i.e.
     
    Last edited: Feb 26, 2014
  29. Valdeco

    Valdeco

    Joined:
    Sep 7, 2014
    Posts:
    12
    Sorry for reviving this topic. I'm just learning how Unity3D works and stumbled through some posts like this.
    LightStriker, I'll allow myself to rectify some of your statements. This specifically:

    OOP:
    Animal -> Four Legged -> Cat -> Kitten
    Components:
    Cat : contains Generic Animal behavior, Four Legged behaviour, Cat behaviour, and Young Animal behaviour.​

    I think there are some wrong assumptions here.
    First of all, I don't think Components is a separated branch from Software Engineering as opposed to OOP. Components is actually one of the strongest OOP Design Patterns, and Unity3D is brilliantly built on it. So I'd say Unity3D relies a lot on Object Oriented Software.

    Second, Animal -> Four Legged -> Cat -> Kitten is a classical example of bad inheritance, or bad OOP, because it violates the Liskov substitution principle (https://en.wikipedia.org/wiki/Liskov_substitution_principle). And the recommendation for fixing that in OOP is exactly your "Components" example. Maybe you've heard of it as "Composition-over-Inheritance". And I think this fix is perfect! It's really the best way of designing this problem!

    And when you say composition exists without OOP and not the other way around... well, I'm sure OOP would be really poor without Composition pattern (which is also tightly related to Dependency Injection), but I have no idea what Composition without OOP even means. I mean, generic animal behavior is an object, four legged animal behavior is an object, so is young animal behavior, etc.

    Before I get misunderstood, I've been reading a lot of your posts and I respect your knowledge a lot. In fact, I think you know much more OOP than me (and of course much much more about Unity3D than me). You just apparently don't know that what you've been using is actually OOP.
     
  30. Valdeco

    Valdeco

    Joined:
    Sep 7, 2014
    Posts:
    12
    Also, always when citing Wikipedia, be sure to check the article's citations too. Check the amount of "who" and "citation needed" on this section: https://en.wikipedia.org/wiki/Compo...#Differences_from_object-oriented_programming
    This section should not exist. Component based software cannot differ from object oriented software, because it IS object oriented. There's no discussion in that. This section is just a bunch of non-sense.
     
  31. JulioA

    JulioA

    Joined:
    Oct 1, 2016
    Posts:
    1
  32. passerbycmc

    passerbycmc

    Joined:
    Feb 12, 2015
    Posts:
    1,741
    Unreal 3 and 4 already have been using a component based structure for a while, main difference is in unity you can create objects and put components on it in the editor side of things while in unreal all of objects types you can place are objects that inherit from UActor and either get functionality via the inheritance tree or you add components to them in code from the constructor. Things aren't all that different just that in unreal it all has to be defined in code, and even that isn't true since in ue4 you can simply create data only bp's that act very much like unity GameObjects.

    If you wanted it more unreal like in unity you simply could just create a MB for what you would consider the top level of your object, and use require component to ensure it has everything else it needs.

    On the coupling side of things this is where i would encourage the use of interfaces that are named via adjectives describing what something can do (IDamageable, IUseable, ITakable, IConsumeable, etc), over using SendMessage like @Patico describes. This lets you interact with GameObjects without caring about what components it has on. Can simply use GetComponent to check if anything on it has the interface you care about and do the action via the interface.

    P.S. its not a OOP vs Component, the 2 concepts can work together just fine