Search Unity

Design Patterns Mix-up?

Discussion in 'General Discussion' started by beerinbox, May 9, 2020.

  1. beerinbox

    beerinbox

    Joined:
    Feb 29, 2016
    Posts:
    26
    I am sorry if that does not mean what I tried to mean: When you are programming your game, are you sticking to one kind of design pattern, or do you have some kind of a mix of some of the patterns?

    The reason I am asking this is, since Unity made it free to access their premium learning site, I decided that I might come back to game programming and try my hands on this one more time, but with proper tools and tutorials this time. Unity advanced more than I’d expect, congratulations to the team, and thanks for making learning hub free.

    My learning process is, I get the basic idea, read/watch some tutorials, and then try to add more on top of it. For instance, if they are making a platformer with 3 power-ups and 2 collectibles, I’d think of new power-ups and new collectibles and try to implement it. If they are hand-making a level, I try to automate it to a degree etc. You get the idea, I set myself a next step and I try to take it.

    I am not sure if this is because of lack of proper planning at the beginning of those projects, there was always a point where it’s impossible to go ahead with the project. It was usually when every script was tied to every script and a small change in scriptA would break scriptZ and in return that would break scriptW and crash the whole system whereas I was expecting a change in scriptF. And there I am, in a staring contest with my code and finally losing after a couple of weeks.

    I am a doctor and I was really stressed about covid until recently. Now I have some free time on my hands and I kind of want to stay away from my profession when I’m not working. I stumbled upon a design pattern book review and thought maybe that was what I needed back then. So, I have been watching videos and reading tutorials about a couple of design patterns. The more I read about them the more I think of my “F this, I’m out” moments and how they could have helped.

    A singleton GameManager would make everything easier when I was trying to pass tens of variables to tens of classes and maybe impossible situations where every class had some kind of reference to every other class and you had to sell your soul to add or remove a feature.
    Or finite state machine thing would help when I was buried under dozens of bools and if statements and wondering if another bool would have made it easier.
    And how about the event system I just learnt about! All those GetComponent or FindObject calls were unnecessary and nobody told me anything.



    The answer already feels like “A little bit of everything”, but what’s the strategy? Do you try to stick by your chosen pattern as much as you can, do you “add some singleton on top of fsm”? And what do the tutorials on YouTube or similar platforms use when they are doing a ‘Make a FPS in 10 videos’ series? Do we have to stick to one or more design pattern?
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Given that you're a doctor...

    It is not a good idea to religiously stick to any practice. That also applies to design patterns. The design patterns, originally, were a collection of approaches people found out they're using, so they catalogued them and gave them names.

    In general, set of programming principles I'd recommend to use is this:

    1. KISS (Keep it Simple: https://en.wikipedia.org/wiki/KISS_principle)
    2. YAGNI (You ain't goona need it: https://en.wikipedia.org/wiki/You_aren't_gonna_need_it )
    3. Murphy's Law. (If something can go wrong, it will. https://en.wikipedia.org/wiki/Murphy's_law ).

    What does it mean in practice.

    Keep solutions simple. You don't need to use fancy approaches, as the idea is to get the job done. If it is simple, and gets the job done, it is a correct solution, even if it doesn't rigidly adhere to some principle.

    YAGNI: If you don't need a feature right now, assume you aren't going to need it in the future. Trying to future proof your program will increase its complexity, trying to make it extensible will increase its complexity, therefore don't write things you don't need right now.

    Murphy's Law: There's probably more than one bug in your code, don't assume your program is flawless. As a result it makes sense to take reasonable precautions and use practices that reduce chance of bugs happening.

    All those principles combined seek to reduce development cost. The idea is to solve probelm fast, not to make a mess, and not have it backfire 6 months later. Development cost is combined from immediate cost (how much time you spend writing it now), and future maintenance cost (how much time you'll need to waste on it 6 months later when it suddenly breaks or you need a new feature).

    YAGNI reduces immediate development cost, as you don't implement things you don't need now.
    KISS reduces future maintenance cost, as simple solution will be simpler to comprehend 6 months later.
    Murphy's law reduces maintenace cost at expense of slightly increase current cost, as you'd try use practice that prevent future bugs. This more applies to C++ than it does to C#, though.

    The strategy is to have a goal in mind, try to apply all tolls you know, while keeping common principles I listed above in mind. Then you continue working this way until it is done.

    Basically, in my opinion, this comes from practice. You learn how to split things into classes, functiosn, modules and so on. Then you apply this in future projects.

    Given that this is not your primary job, I'd advise to mostly try to keep things clean, simple and comprehensible instead of trying to be fancy or trying to apply things in the process. Also it is a good idea to have fun in the process, as having fun, in my experience, increases information retention.

    That's the rough idea of it.
     
    superpig, Martin_H, ikazrima and 3 others like this.
  3. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,660
    You should absolutely expect to mix and match the patterns you use to the different kinds of problems you are solving. Your UI code has different needs to your AI code, which has different needs again to your gameplay code, and so on... you use the right tool for the task at hand.

    That said, one of the biggest mistakes I see people make with patterns is to think that everything has to be constructed out of them, to the point where I've seen people say "I'm stuck, I can't implement this because I can't find a pattern for it." Patterns are supposed to be descriptive, not prescriptive; they're observations about how previous systems have done things. It's good to learn them, it can help you see new possibilities and it gives you a language for talking with others about your code - but you should not expect that everything sticks to a pattern.

    A lot of the time, the important thing is to understand the problems that the patterns are solving: for example, your 'changing scriptA breaks scriptZ' problem was, I am guessing, a lack of clarity around the responsibilities that each part of the system actually had. Patterns can give you some examples of possible ways to divide up those responsibilities, like 'have a separate class be responsible for each state' or 'have one class be responsible for all the formatting/layout of the UI, but make a separate class be responsible for actually providing it with the data to display,' etc. It's useful, but at the end of the day, what matters is whether you solved the problem - not which pattern you used.
     
  4. beerinbox

    beerinbox

    Joined:
    Feb 29, 2016
    Posts:
    26
    Thank you both for your answers.

    @neginfinity, I understand that KISS and YAGNI while seem like 'just do it' type of principles, they do not necessarily mean that one needs to just write bluntly and hope it works, right? From what I understand, what they try to mean is, use proper methods, not fancy ones.

    I agree that some of this comes from practice, because at my first attempt in programming, I was reading about separating logic from input etc, and trying to do it first time was a disaster. But as I progressed, I felt like I was developing my own techniques. And actually, this is where I started to wonder how people with proper programming education do similar things. Because it is entirely possible that I am oblivious to a simpler approach to a problem I spend many hours on.

    Hence my curiosity about patterns.

    @superpig, it is good to know that mixing patterns is not bad practice or something like that.

    I was suspecting that depending on the project at hand, it is entirely possible having no pattern at all is also acceptable. I understand that I should not force any pattern into my project, but recognize that using one of them would be suitable for the project. And not fully implementing one or using more than one is okay.

    Again, thank you guys for your input. Much appreciated. I'm back to learning those.
     
  5. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    My patterns are ...
    -Controllers - if it moves or changes the controller controls that change. Usually has Update loops.
    -Managers - If I need to track many controllers for a condition I write a manager to receive a control state from a controller. Oftimes I use the UI controller as a manager. Usually no Update loop but just functions to receive the state of controllers and conditionals to decide if a state or states meet criteria to alter score, lose or win based on the rules.

    Works pretty well. I probably have used every type of pattern that compsci guys have heart palpitations and salivate and get super anal retentive over. I could not give a damn what they call them..usually something fancy so the profession sounds all esoteric and impenetrable. I can usually get from the start to the finish of a game quicker. I have watched some compsci geniuses around here, if you listen to them tell it, not get to the end of their game while I put 8 or 9 on my harddrive or in clients hands. I am just a humble science nerd/accomplished artist who had to learn to code for my art output to put bacon on the table.
    -
     
  6. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    This is the main problem in programming -- everything depending on everything else. I think everyone has to write a giant pile of garbage like that before they can even understand how to write a good program. The fix is often called Loose Coupling -- break things into parts, and try to think of ways to minimize contact between them. Writing functions is the main trick. Classes and interfaces are just one formal way of doing that. But if you don't understand the point, merely using classes won't do a thing.

    Once you're aware of it, fixes are as-needed. Doing damage to an orc might play a death sound and possibly end the level. Who's job should it be to store the sound, play it, and check for level end? Probably not the bullet's.

    But it's just one old book about extreme OOP. Many Design Patterns were things we did already, but rewritten to be slightly uglier and more "object oriented". Since we're apparently required to provide terrible analogies to what we imagine doctors do: most of the patterns and design tricks you see in blogs are like the brochures drug reps leave.
     
    RecursiveFrog and ippdev like this.
  7. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    Seems like the same thing? From an old-school perspective, that's a way to get loose coupling. Suppose A, B, C, and D each need to interact with X, Y, and Z. That's lots of interaction. Creating Q in the middle reduces what each class has to deal with:

    Code (CSharp):
    1. A     X
    2. B  Q  Y
    3. C     Z
     
  8. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,660
    One other thing I'll note: the patterns you have been looking at are patterns of design, but there is a second category of pattern you might want to look into as well - 'refactorings,' or patterns of change.

    It's pretty much inevitable that the design of your project is going to change over time - either because you have let things grow organically and they've gotten a bit out of hand, or because even though your system is very cleanly designed, you want to take your project in a direction you hadn't originally planned for. It's normal, and it's important to give yourself the tools to get out of a situation like that, by making changes to the code which - while they don't impact the overall behaviour of the system - make it easier for you to understand and work with.

    Most refactorings are very small things - even as simple as renaming a variable - but they let you gradually work your way out of a spaghetti nightmare. You don't have to charge in and fix things all in one go; it's often not clear exactly what the better solution would look like, but you can always start by refactoring the aspects that you're sure are wrong, and once they are cleaned up then it can make it easier to see what to do about the rest. It's like the difference between staring at a messy room and trying to visualise exactly where everything should go before you start to move, vs putting away some of the easiest/most-obvious things on the top of the mess first, to make it easier to see everything else.
     
    hippocoder and ippdev like this.
  9. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Q becomes the manager. I tend to find the quickest logical way to couple things to get it out the door. Freelance fixed price contracts does that to a way of thinking. I look at the specs and assets needed, get the artwork set up, decide what i need to control and create scripts with AssetController.cs for each asset and if I have to crosstalk the ControllersManager.cs becomes the switchboard. I usually put a bunch off public bools in that to define each condition or collection of conditions so i can track in the inspector. Often as I do many casual games the UIController is sufficient to manage the controller conditions.
     
    Last edited: May 10, 2020
  10. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    I think if you are running into such problems, you should consider trying understanding why exactly this is happening. For example, try to understand exactly what about your small change to scriptA caused scriptZ/W to break, and why it did not change anything in scriptF. You can use the forums forums for this kind of help, if you are stuck. If you can get a deeper understanding of the problems you are facing, then the potential solutions are going to make much more sense.
     
  11. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,025
    I think that unless you have a really good reason otherwise, it's best to start off with modular object-oriented code with a clear hierarchy, as it is simply the most logically intuitive method.

    When building a system for a game, I try to create it as a separate 'module' that provides a way for other components to 'plug in'. That means that scripts that process data inside a particular module should only require either data or references to other components in its direct hierarchy and module.

    For example, if you are creating a damage module, you might create a script for every damageable object, a script for every damaging object (e.g. weapon) and maybe a 'damage manager' type of script that goes on the root transform of a damageable object to cache references to damageable parts. At least those would be the main constituents of the module.

    Then, I use Unity Events, interfaces, and (only if absolutely necessary) inheritance to connect damage to other parts of the game (e.g. input, visual and sound effects). But you should be generally able to separate an entire module into a new project without dependency errors, except for maybe a couple of components created specifically to interface between it and a completely different module.

    This is not a golden rule, of course, as there are many different approaches with different strengths/weaknesses. But you need to choose something and stick to it as much as possible within a specific scope.
     
  12. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    That seems to be adding 2 extra things. One bit of common advice, which seems unhelpful to me, is to start by making proper OOP code -- don't worry about making it do stuff yet. If it's proper OOP, it will magically be easy to make it useful, later on. "modular object-oriented code with a clear hierarchy" sounds like that. I have started projects by building out a big framework, but only if I've done something similar 3 times before and know what I'll need.

    The other extra thing is reusability. "you should be generally able to separate an entire module into a new project without dependency errors". That's similar to having a manageable project. But it's not the same thing. I've found that trying to make a part that you can easily drop into any project is at least twice the work of just making it, and is only useful about 1 time in 4 (it often turns out you never need it again).

    Having just one program that gets bigger and bigger, and it takes more and more time to make any change, or track down any bug -- that's a real thing, and thinking how to fix it is the basis of everything else.
     
  13. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    The problem when you tell beginner to "make proper OOP code" is that they start overdesigning things, overuse inheritance and OOP features when those are not really needed. Also, "designing PROPER class hieararchy" falls into "amazingly interesting problem" category. "amazingly interestng problem" is something that is interesting to deal with, but has little impact on the project and drains development time for no benefit.
     
    ippdev likes this.
  14. bobisgod234

    bobisgod234

    Joined:
    Nov 15, 2016
    Posts:
    1,042
    Many new developers are probably going to go overboard with any design methodology the first time they use them. I think that is just part of being a new developer. It's hard to understand the pros and cons of a pattern until you have used them with both success and failure.
     
  15. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,025
    It's not specifically the 'official OOP paradigm' that's important. What's important, especially when starting out, is to conceptualize code in terms of physical objects if at all possible. This is because physical objects, with inputs that are processed into outputs, are what people are already used to dealing with on an everyday basis.

    Let's say you want to make the player damageable in your game. How do you approach this - what component(s) do you need, where do they go and how should they be accessed?

    What I would teach someone to do is think of the object that is most analogous. Let's say a bulletproof vest for this example. A bulletproof vest can be hit, undergoes some sort of change as a result, and that internal change has an effect.

    So that already tells you how to structure your code.

    1. Create an object called Damageable (bulletproof vest).
    2. Add an internal Health property that is changed when hit (vest health or integrity)
    3. Add a function that allows the property to be changed when hit (interaction of bullet with vest).
    4. Add an event that is called when the Health property reaches zero (player dies/is injured when the vest is pierced by a bullet).

    Suddenly, you have an object that can be attached to the player character, the AI character, dogs, cats, crates, zombies or whatever you like, which makes them damageable.

    Otherwise, you will get something like:

    1. Where should I add the player health? I have a Player Controller so I'll just add it there.
    2. Wait, how do I access the health anyway? Maybe I'll just add a singleton called PlayerHealthManager.
    3. But in my game sometimes I'm controlling the players dog, maybe I'll add two health values in PlayerHealthManager and a function that starts off with something like "if (player.isDog)..."
    4. Where do I put the isDog property, in the Player Controller...?

    The first method is much better. You can draw the same analogy between a backpack and an inventory, for example.

    It's not magic and doesn't work for everything at every level of complexity, but for a beginner I don't think there's any approach for creating game code that even comes close. The first stop should always be "can I make this analogous to an object I know" and only move on to something else if that doesn't work.

    I agree that separating modular code into a different project isn't all that useful - getting rid of the last dependency or two can add a lot of weird abstraction that isn't helpful. So maybe it's better to say that each module should have a very small number of dependencies on other modules, and data is preferred to object references.

    PS not all OOP concepts are particularly useful to object conceptualisation. Inheritance, for example, when it isn't used properly, actually prevents the real object from being recognized as such. Hierarchies of inheritance are especially unhelpful.

    For my asset store stuff, which is geared toward being beginner friendly, I try to leave inheritance as a customization layer for the user, which allows them to basically operate on whatever aspect of the code they want to, because I have to allow the possibility for anything and everything to be customized at some level. Other than that I try to avoid it.
     
  16. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    The first thing to remember is that the Unity engine is not OOP enterprise coding. It is a frame dependent component based architecture. The second thing to remember is that game development is based on breaking rules bigtime to keep that loop chugging along as fast as it can go. The third thing to remember is the first thing to remember and that it is component based. If you have to ignore the rules of OOP and inheritance and other compsci buzzwords so you have an architecture that ignores everything except what is actually supposed to occur in a given frame and can shut calcs down everywhere else then do that and compsci jargon and recommendations be damned. The fourth thing to remember is that you haven't done a damned thing worthwhile to put bacon and butter on the table until the project is out the door except learn how to optimally implement two and three...maybe..
     
  17. unit_dev123

    unit_dev123

    Joined:
    Feb 10, 2020
    Posts:
    989
  18. unit_dev123

    unit_dev123

    Joined:
    Feb 10, 2020
    Posts:
    989
    Do you use something called 'dots' anywhere?
     
  19. Billy4184

    Billy4184

    Joined:
    Jul 7, 2014
    Posts:
    6,025
    Would not recommend to a beginner. That said I haven't used it yet, so..
     
  20. unit_dev123

    unit_dev123

    Joined:
    Feb 10, 2020
    Posts:
    989
    Ok well my friend @Antypodish is very knowledgable on the subject if you wish to learn from his example. It is my next phase.
     
  21. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Standard practices made for C# are very likely to negatively impact performance of your game.
     
    ippdev likes this.
  22. unit_dev123

    unit_dev123

    Joined:
    Feb 10, 2020
    Posts:
    989
    Thanks I was initially linked by someone here to this tutorial when I started. When i complete OOP maybe I will try dots.