Search Unity

[Question] Unity Systematic Scripting with Design Patterns for Beginners

Discussion in 'General Discussion' started by MockTurtle, Feb 25, 2021.

  1. MockTurtle

    MockTurtle

    Joined:
    Nov 25, 2020
    Posts:
    2
    I have worked on a couple of indie projects to delve into game development.
    Now I am working on my dissertation in my college and take the scripting scalability of the engine itself into consideration.
    Through my dissertation, I want to talk about some of the problems I confronted during those developments and later suggest some solutions.

    To start, I see many videos, text tutorials and books online that teach us how to make specific ways of coding specific features of gaming features like making player stats or making magic skills.

    But I barely see that many scalable teaching materials that are intended for entry-level developers.
    If there is, it requires hours and hours of studying theories. But implementation is another problem. Not to mention, it is very advanced while specific to one sample project.
    So to lower the entry threshold, I would love to hear your opinions and help newcomers start their projects, not to be overwhelmed by scripting difficulties.

    Of course, there are assets that help them code visually but I aim to teach without such packages and only through coding. Maybe just a couple of template scripts?

    So, first, I would like to ask the community
    • how you first learned their coding in Unity,
    • and if there is any, what frustrated you in developing your projects.
    • Are there any particular design patterns that you use often in scripting your projects?
    • What system or features do you develop integrating the design patterns in them?
    And lastly, how do you think about these efforts done by Unity Learn?
    Are they effective or still too specific?
    I'd really appreciate your help :)
     
    Verne33 likes this.
  2. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    Scalability in general often involves building your own custom systems to support that scalability, to get around some inherent limitation or to improve a workflow which otherwise doesn't scale well.

    Personally, I don't think that is very beginner friendly. You're likely to get a beginner lost in the weeds without really understanding why they are doing this at all. I think it is better for them to "learn the hard way" by first running into these scaling issues themselves. If done that way, they will have a clear understanding of the scaling issue they are then trying to solve, and why they need to solve it.
     
  3. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    That's because these things don't really go together.

    You're talking about beginners but you want to have them do advanced stuff. You need to walk before you can run.

    Part of the issue is what @Joe-Censored already said. Scalability is going to be slightly different per game, so you necessarily need to be defining your own solutions. This pretty much rules out following tutorials. As someone writing a dissertation I'm assuming you know the purpose, scope and place of a tutorial as compared to other learning formats? They work well for specific, practical application of small theory components. That's not a scope which "scalable large game design and implementation" fits into.

    Another part of the issue is that when it comes to implementation the code isn't the hard part. Large projects are just as much about managing data and managing workflow as they are implementing whatever your features are, and those things are at least a level or two of abstraction above implementing your game features. To be able to effectively design scalable systems you need to understand the parts of those systems well enough to make reasoned decisions about different approaches you may take to each of those parts. And if you need to follow a tutorial to make that part in the first place then you're not there yet.

    Basically, you've got some broad layers of abstraction as follow:
    Features -> data/content -> workflows -> people.
    To build scalable systems you need to have enough experience to be able to plan changes at the "features" level of abstraction and figure out how it will flow on to at least data/content and workflows, and probably also people. If you're still figuring out "how do I make it work?" then you're not ready for that. And that's cool. Everyone who can do that stuff had to learn the beginner and the intermediate stuff, too. Rushing it won't help.

    To abuse one of my old analogies: if you're just learning to play guitar then it's too early to start talking to record companies. That will be a valuable skill one day, but for now you've got other stuff to focus on.
     
    Ryiah, Socrates, NotaNaN and 2 others like this.
  4. MockTurtle

    MockTurtle

    Joined:
    Nov 25, 2020
    Posts:
    2
    While I agree with both of you in terms of the development from smaller experiences with various features to larger abstractions--the freedom Unity gives us, yet the point of the post is to think of the ways that can teach how to organize Unity scripts with conventional programming design patterns.

    I will have to reformulate the question: whether we can introduce the scripting in Unity so "Unity beginners", who are but not new to programming itself, can take scripting without too many obstacles.

    From my experiences, unscalable projects often demotivate further advances in learning. In some cases, the projects come to a halt due to an unorganized mess in scripts. Monobehaviour attached to GameObjects is Unity's choice, to begin with, but it often leads to violation of the open-closed principle. And people learn by mistakes.

    Like that, a lot of game devs started with nothing, built their skills upon that basis and are proud of it. But if I can help others with an easier, more accessible way to learn the same skills, I would love to help build the way. Unity strives to be beginner-friendly, don't they?

    The fact that each project needs its own solution cannot be argued easily, but Unity itself is a solution to provide an abstract ground for game development. Of course, it is built over a long time by experienced people to create what is an engine.

    There, I finish my train of r̶a̶n̶t̶s̶ thoughts.

    Now that I think about it, my approach to this project may be too big of ambition for merely half a year to achieve.
    All I wanted was to simulate some brainstorming to tackle this problem even if it may remove some degree of freedom in using Unity. Maybe later.
     
    Verne33 likes this.
  5. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,980
    It sounds like you are trying to slam your university knowledge about programming together with unity, which doesnt play ball.

    You talk about violations of open-closed, but unity is a component based system. Its not violating anything, this is the architectural choice. Concepts like open-close, SOLID etc they are not universal. Because they exist does not mean you have to apply them, and for a lot of architecture it wont make sense.

    This is one of those cases.

    Trying to reconcile unity development with regular c# development is often difficult because while unity uses c#, it does not abide by the same rules that a lot of c# software will use. That doesnt mean its wrong, its just up to you to learn the differences and why they exist.

    There is nothing stopping you from using minimal components and doing everything using classes structs etc and maintaining very strict SOLID or similar principles, everyone does this at some point for some project or another - and its suitability varies depending on what the project is.

    Its up to you to build what you need the way you need it, its just a toolkit provided. How you use it and why is whats important, not whether or not the usage theoretically abides to some priniciple you have learnt.

    What you learn at uni is different out in the wild btw, not everything you are taught you will use, a lot of it will be outdated, and the rest will be generalised and not really focused specifically on something as specific as the unity engine itself. So basically, take everything you are learning right now with a pinch of salt, and dont get stressed out worrying about problems that do not exist simply because university told you to develop a specific way.

    Nobody at university teaching you will have been very successful in the games industry btw, there are so few lecturers out there who made a good career of it.
     
    Ryiah, Joe-Censored and ippdev like this.
  6. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    That's because scalability is not an entry level topic.

    I had a couple of years of experience with other languages (C++ and others), so I picked up C# by tinkering with the engine for a month or two.

    Equals() operator is a very puzzling thing to deal with after using C++, due to ambiguity of its purpose.

    Cached static singleton and lazily evaluated variables pop up often. That is an object which has static property called "instance" which does something like
    Code (csharp):
    1.  
    2. public static Blah instance{
    3.     get{
    4.       if (!_instance){
    5.           _instance = FindObject<Blah>();
    6.       }
    7.       return _instance
    8.    }
    9. }
    10.  
    This question does not compute. Design patterns are general purpose principles so they can be used in pretty much anywhere.

    I pretty much never used UnityLearn and instead read documentation. Script reference and manual.
    Once upon a time I watched a single video tutorial which, I believe, described inner working of animatorcontroller, and that was it.
     
    Ryiah, Joe-Censored, NotaNaN and 2 others like this.
  7. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,775
    So now you know what you need, you can go startwith DOTS.
    It has plenty resources to allow you build large scalable games.
    But how comfortable you will feel with it, is purely based on your previous programming experience. If you, or other beginner never done such programming, picking it may introduce very hard time.

    Don't expect however, that academia will teach you about it. Maybe mentioning at best.
    Academia goal is to teach you how to learn, how to search for knowledge, answers and introduces to principles in the given case studies.
    You not meant to become an expert after finish such academia. Just a junior. Then your real learning starts on the job, with years of experience behind.
     
    Ryiah, MadeFromPolygons and NotaNaN like this.
  8. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I am going to post this which is notes I made after a company I worked for hired a bunch of academically trained C# guys to work on a project made the Unity component way. They were lost and hated the most powerful tool in the Unity arsenal..The Inspector. It may help wean you away from your propensity for playing by the rule and using design patterns.

    "
    Unity, C#, Mono and .NET


    C# engineers are _initially_ going to be the wrong people for be programming games in general. They're trained in practices that work actively against how Unity uses C#.


    So they need to understand that game development is about making up new rules (breaking them) because the rules aren't designed for games.


    In the world of game development, it's all about optimizations because it has to run within a specific time limit in order to be interactive and enjoyable.


    This means breaking the rules.


    The earliest game developers (since the birth of the very first game) up to today, had to be about breaking rules because the hardware wasn't (and still isn't really) designed for games. So to make games you need to play outside the established set of rules. That means using things that aren't designed for something, and not using things that are designed for something (in a language and hardware context).


    This typically means that most optimizations are breaking the "rules".


    Of course to any game dev using Unity, we're not really aware we're breaking rules, we're just flexible enough to think well outside the box, because that's a normal every day mode of thinking for the humble game developer.


    At its core, Unity uses Mono. Mono is an implementation of the .NET framework. That by definition means it's not the same framework .NET developers may be used to. Even though it's supposed to behave the same as the framework they're used to, not all things are implemented the same way (if even at all).


    Unity took this a step further and changed a few rules about what to expect from C# via operator overloading -- using bool to check vs null, and overloading equals to check against a fake null being just two examples. This also broke newer C# features such as null propagating and coalescing operators. With constructors being forbidden, readonly fields become harder to set. And so on.


    Hell, Unity doesn't even use polymorphism to call Update() in MonoBehaviour derived classes. It feels a bit alien to not include "override" when creating an Update() function. But that's just how Unity works.


    So if anyone argues "But it works this way in .NET..." it's already irrelevant. There is a definitive distinction between "C# vs Unity C#”.Some random thoughts:


    1) If they want to stick with their .NET code, then fine. Just keep it in DLLs. And create an API to let their .NET code talk to Unity and vice versa. If they want to work within the Unity namespace, they must adhere to Unity's rules. No ifs, ands, or buts about that.


    2) You use an API in the way it's meant to be used. You don't try to bend the rules or work around it, then complain that the API doesn't work the way it should. Thinking you can write your own buttons for example and use physics raycasting instead of a Unity Button component hogties any system that uses the UI eventData looking for interactable bools, loses for free animation and colorBlock transitions from your toolkit. Using the OnClick event delegate is the exact same but faster as intercepting it in script. Real world testing has proven this even though it looks like they are the same. Unity does something under the hood with pooling and reassigning delegates on the fly.


    3) If upgrading Unity versions break your code or objects and force you to re-engineer it, then see point #1 about adhering to Unity's rules. Note, I am not talking about Unity bugs -- these are very valid reasons on skipping certain versions while upgrading. I am talking about how if you try to access internals via reflection, then your reflected code breaks because the internals changed, well guess what, that's on you for depending on internals to never change. (Note: This should be extremely unlikely now that Unity open sourced their C# code, but the point still stands). Setting up objects to be normalized with a uniform scale and Quaternion.Identity rotation and it’s coordinate system position at 0, 0, 0 will avoid serious headaches when Instantiating objects.


    4) Similar to #1 above, if they've ever worked with WPF, they should be familiar with MVVM. Or MVC. Keep the logic and UI code separate. Apply this line of thinking to Unity -- if you're going to get all fancy with your .NET code, keep it out of Unity code. Changing your code should not break Unity. Changing Unity UI should not break your code.


    5) As has already been said, game dev is very different from business dev. Unfortunately it's very hard to convey this point to folks, especially if they have little technical knowledge of the underlying issues. You have people with specialized skills and you don't try to put them on jobs they're not suited for. Even though architects and electricians work together to build a house, you don't expect an electrician to draw up blueprints for the house and expect the house to not collapse. Or architects to not die from electrocution. And so on.


    The irony in saying that games development "breaks all the rules" is that it can be tempting for a non-technical person to say, "Well isn't the opposite also true? Can't you break game dev rules then?"


    This isn't even a C# issue. There are similar arguments way back in the C++ days when avoiding the STL was a way to optimize your game. Avoiding the STL was a positively alien concept to business devs. Some things never change, I guess


    1. unity uses C# as a scripting language, not as C#, not as .NET

    2. as a scripting language, C# has some benefits, none of which come from .NET

    3. unity is a constantly evolving game engine, getting better all the time

    4. unity is a game engine, not a corporate platform, like .NET

    5. using the latest releases of Unity has LOTS of advantages, few negatives

    6. moving up through Unity versions doesn't entail a lot of scripting changes

    7. moving up through Unity versions often bring benefits worth the effort

    8. Unity is better than it ever was, for most platforms, in 2018.1 onwards

    9. capable .NET programmers are perfectly equipped to move up versions


    I think the really important distinction to be made is that while you're writing code in C#, the majority of what the computer is doing isn't in the scripting runtime. I would point out that the scripting runtime sits on top of a game engine, and understanding what's going on in the game engine part of the system is really important. A dev might know C# really well, but that's only part of the picture.


    "to a man with a hammer everything looks like a nail".


    Whether you use IL2CPP or Mono or not, data must be marshalled to C++ since that is what the actual engine runs in. The engine will always run as C++


    Another thing of interest is how to allocate variables in a class. In most business .NET software, variables get declared inside functions and cleaned up automatically as they go out of scope. With a game, there is often some performance to be gained by declaring some variables at class level and re-using them throughout functions. It goes against modern scoping concerns, but it addresses memory and performance issues that are usually more important in games. If you need a reference then store it at Start. Do not ever get a reference in an Update loop.


    On Unity nulls


    if(gameObject!=null)


    A faster solution to a .NET dev is to use the following:


    if(!System.Object.ReferenceEquals(gameObject, null ))


    Keep in mind that those two lines of code do not actually do the same thing. If you destroy the GO, the top line will work as you expect, but the bottom line will not since it still references the container object. Unless you actively assign null to the variable, that won't work in a lot of cases. Unity uses a fake null object and overloads ==, which means that using reference equals does not return the same thing as a regular null check. A GameObject can pass a reference equals check, and still throw a null reference error on the very next line.


    Instantiate/AddComponent creates a C# object and the corresponding C++ object together. It registers the newly created GameObject/Component with the various systems Unity uses. (Incidentally calling new on a GameObject does the same thing too, don't be afraid to use new GameObject("SomeName") when the situation calls for it).


    New is used for creating regular vanilla C# objects. It goes ahead and calls the constructor.


    The thing is you can't use new to create a MonoBehaviour or to add Components to a GameObject. Unity will throw an error. Even if you do create your entire structure without GameObjects, you still need to use GameObjects and Components to interact with Unity's rendering system (and physics ect too). Which means in most cases, you are better off just using components in the first place, rather then putting an extra layer in between you and the engine.


    Under the hood Unity reserves the MonoBehaviour constructor for its own purposes, and does stuff that isn't fully publicly documented hooking up internal engine stuff. There are other ways that probably could have been handled, but that's what Unity picked, so constructors are just out of bounds to us. Because of that they instead give us Awake() and Start(), but they really aren't the same thing for two notable reasons:

    1. You can't give them parameters.About the closest we can get is a static CreateBlah(...) method which calls Instantiate or AddComponent and then does whatever else we would want to do.
    2. Awake and Start are called at different times to a constructor. I believe that Awake is immediately after construction, so that one's pretty similar, but Start is called as a part of the next iteration of the engine loop. Understanding that engine loop and what it means for your objects and their lifecycles and when you can reliably access what things is pretty important, and fairly specific to a game engine (or similar real-time systems).
    This is of course only relevant for MonoBehaviour and friends. You can make plain C# classes and use their constructors and that's all groovy. You can use those classes from your MonoBehaviours, and you can have those classes reference MonoBehaviours to interact with the scene.


    I despise going though code and seeing myVal = 7f or myText = "hello world" and then looking in the hierarchy to find what this generic thing is referencing and everything has the default name. It smells to me like copying sample code and shows no regard for other team members working with your stuff.
     
  9. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Wait what? Component driven design done right is very much SOLID. Strategies is one of the corner stones of SOLID and strategy pattern is just another name for components
     
  10. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,980
    Solid was just an example, my point was all these ideas are just ideas, its how relevant they are and how useful to your project that matters :) but yes, semantics ;) You are right ofcourse if being specific, but if you read I was not specifically saying solid itself does not match components, just that not all priniciples match unity and all unity projects.
     
  11. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    I don't get why you guys are so against patterns, they are just giving names to stuff we do everyday. At my day job I do alot of code reviews (tech lead at a enterprise company) imagine doing the same thing without terms and names everybody understand and already knows.

    Most patterns and design choices from SOLID and gang of four etc work just fine in game domains.

    Edit: It was not specifically aimed at you btw
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    There's a pretty wide spectrum between "being against" something and "following it as dogma without question".

    Different people here fall in different places on that spectrum, but I can only think of one or two who are actually at one end or the other.

    Not everything is "us vs. them". People can just be different. :)
     
  13. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    The design pattern I use is Controllers control components with an Update loop and sit on the GameObject to be controlled or at the root of it's hierarchy.. Managers control data and have no Update loop and are a conglomerate of methods for accessing data for controllers. Utilities assist in the setup of Controllers or Managers methods and passing data, triggering methods or altering variables based on queries or interactions between Controllers. This is entirely scalable and keeps things neat and tidy and i do not follow the rules into a painted in corner nor waste time trying to jam enterprise paradigms into something not built for that purpose.
     
  14. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    People aren't "against patterns", they're generally against cargo cults. Overuse of patterns tend fall under cargo cults, and in case of game programming coupled with C# come with costs, and overuse of OOP in the first place is the biggest trap in computer programming. So you can easily end up proudly sawing your leg off while proclaiming that you're doing the right thing.

    Personally I think SOLID is not the best principle to follow.
     
  15. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    When it comes to OOP stay away from inheritance and use composition. All other design choices are secondary to that :)

    I don't follow design patterns like a slave.i do always have them in the back of my head though and I try to follow them.

    Edit: solid is one of my favourite patterns. Open closed is almost by default with composition.
     
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
  17. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Sorry I sometimes use them in place of one another. Principles are the general idea and patterns are implementations of said idea.

    For example open closed principle can be implemented with a strategy pattern.
     
  18. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    "Pattern" in computer programming tends to refer to "Design Patterns" which are well known OOP models.
    https://en.wikipedia.org/wiki/Software_design_pattern
    Adaptor, Command, Flyweight, Facade, Singleton, and so on.

    SOLID is not a pattern.
     
  19. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    We just cleared the term above. But yeah. Patterns are a way of achieving the princliples. For example open closed principle is probably most common solved the strategy pattern. But there are other ways. Example from what I did at my dayjob this week.

    I work at a big insurance company and Im lead tech on a system that handles matching incoming payments with invoices. If we can match but the amount differs we execute a deviation strategy. classic strategy and a way of reaching o/c.

    Code (CSharp):
    1.         private PaymentInvoiceMatchResult Deviation(MatchJob job)
    2.         {
    3.             return _amountDeviationStrategies[job.Invoice.AmountDeviationStrategy].Execute(job.Invoice, job.Payments, job.Diff);
    4.         }
    But there are less obvious ways of solving o/c, here is example of using visitor pattern for it :p

    Code (CSharp):
    1.     public class RefundStrategy : IAmountDeviationStrategy
    2.     {
    3.         public AmountDeviationStrategy Strategy => AmountDeviationStrategy.Refund;
    4.  
    5.         public PaymentInvoiceMatchResult Execute(InvoiceReadInfo invoice, IReadOnlyCollection<PaymentReadInfo> payments, decimal diff)
    6.         {
    7.             var refundParty = invoice.InvoiceType == InvoiceType.CustomerInvoice ? (int?)invoice.ReceiverParty.PartyId : null;
    8.  
    9.             if (diff < 0)
    10.                 return new PaymentInvoiceMatchResultBuilder(payments)
    11.                     .PaymentCategory(PaymentCategory.WrongPayment)
    12.                     .QueueCommands(payments.Select(p => new RefundPaymentCommand { Id = p.Id, RefundPartyId = refundParty }))
    13.                     .QueueCommand(new SendInvoiceRefundInformationCommand { InvoiceId = invoice.ExternalId, ExternalSystem = (ServiceTypeEnum)invoice.ExternalSystem });
    14.  
    15.             var invoiceAmount = Math.Abs(invoice.Amount);
    16.             var combos = Enumerable.Range(1, payments.Count)
    17.                 .SelectMany(i => new Combinations<PaymentReadInfo>(payments, new PaymentCompare(), i))
    18.                 .ToList();
    19.  
    20.             var payingPayments = combos.Where(c => c.Sum(p => p.Amount) >= invoiceAmount).OrderBy(combo =>  Math.Abs(combo.Sum(p => p.Amount) - invoiceAmount)).First();
    21.      
    22.  
    23.             var refunds = payments.Except(payingPayments).ToList();
    24.             var result = new PaymentInvoiceMatchResultBuilder(refunds)
    25.                 .PaymentCategory(PaymentCategory.WrongPayment)
    26.                 .QueueCommands(refunds.Select(p => new RefundPaymentCommand { Id = p.Id, RefundPartyId = refundParty }))
    27.                 .RerunMatchFor(invoice);
    28.  
    29.             if (payingPayments.Sum(p => p.Amount) != invoiceAmount)
    30.                 result.QueueCommand(new RefundSplitPaymentCommand { Id  = payingPayments.First(p => p.Amount >= diff).Id, Amount = payingPayments.Sum(p => p.Amount) - invoiceAmount, RefundPartyId = refundParty});
    31.  
    32.             return result;
    33.         }
    34.  
    35.  
    36.         private class PaymentCompare : IComparer<PaymentReadInfo>
    37.         {
    38.             public int Compare(PaymentReadInfo x, PaymentReadInfo y)
    39.             {
    40.                 if (x!.Amount < y!.Amount) return -1;
    41.                 if (x.Amount > y.Amount) return 1;
    42.  
    43.                 return 0;
    44.             }
    45.         }
    46.     }
    Take the RefundSplitPaymentCommand visitor as an example. It looks like

    Code (CSharp):
    1.     public class RefundSplitPaymentCommandHandler : ICommandHandler<RefundSplitPaymentCommand>
    2.     {
    3.         private readonly IPaymentRepository _repo;
    4.         private readonly ICqsClient _cqsClient;
    5.         private readonly IBusinessContext _ctx;
    6.  
    7.         public RefundSplitPaymentCommandHandler(IPaymentRepository repo, ICqsClient cqsClient, IBusinessContext ctx)
    8.         {
    9.             _repo = repo;
    10.             _cqsClient = cqsClient;
    11.             _ctx = ctx;
    12.         }
    13.  
    14.         public async Task Handle(RefundSplitPaymentCommand command)
    15.         {
    16.             await _ctx.StartTransactionAsync();
    17.             var refundId = await _repo.ExecuteRefundSplit(command.Id, command.Amount);
    18.             await _cqsClient.QueueCommandAsync(new RefundPaymentCommand{ Id = refundId, RefundPartyId = command.RefundPartyId});
    19.         }
    20.     }
    We could easily replace ICommandHandler<RefundSplitPaymentCommand> implementation without changing RefundStrategy class

    We use same in game, just not as elegant as DI
    Code (CSharp):
    1.         public void Awake()
    2.         {
    3.             projectileInfos = new Dictionary<uint, ProjectileInfo>();
    4.      
    5.             System = new BallisticsSystem(LayerMask.GetMask( Layers.Ground, Layers.Default), CalculateBounds());
    6.             System.RegisterBallisticsHandler(new ImpactHandler());
    7.             System.RegisterBallisticsHandler(new DamageHandler());
    8.  
    9.             if (GameManager.Instance.DebugMode)
    10.             {
    11.                 Diagnostics = gameObject.AddComponent<DiagnosticBallisticsHandler>();
    12.                 System.RegisterBallisticsHandler(Diagnostics);
    13.          
    14.                 if (Networking.PrimarySocket.IsServer)
    15.                 {
    16.                     Networking.Instantiate(Resources.Load<GameObject>("ServerClientDifferenceDiagnosticsHandler").GetComponent<SimpleNetworkedMonoBehavior>());
    17.                 }
    18.             }
    19.         }
     
    Last edited: Mar 1, 2021
  20. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    Well... this reminds me of another piece of advice.

    "Code is written for other people to read it".

    If I recall it correctly, Donald Knuth was a proponent of "literate programming", although in that case an interesting idea was taken too far.

    https://en.wikipedia.org/wiki/Literate_programming
     
  21. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Yes, above code is very easy to follow and for any teammember to dive into.

    Edit: but I understand you are being sarcastic and dont agree :)
     
  22. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    I have no doubt why your development is so slow. You are jacking insurance payments routines into an FPS and thinking yer all superior because of your day job.. I could handle this in about four lines of code and two simple components with a few lines of code each and it would be scalable and readable by a Unity noob. No interfaces. Inspector driven. You know... that thing that handles DI automatically and elegantly which you seem to ignore in your spectacular displays of self aggrandizement.
     
    JoNax97, neginfinity and QJaxun like this.
  23. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    No, it is awful. Both unity and insurance payment side.

    However, this code snippet explains why you spend six months on something as trivial as trigger pull which should've taken between 1 and 6 days of development time.
    You're breaching both KISS an YAGNI principle and overloading the code with heavy use of DI, except you don't even need it. And in doing so, you're fighting the framework.

    Like @ippdev said, this is something that is done with a few small components a dozen of lines each. For example, you don't need to "register" things because you already have components for that.
     
  24. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Its just the right amount of KISS, for large complex systems like this you need these seperations . If you are a one man team making a small game you can get away with high coupling and low cohesion.

    I use unity components when they make sense. They don't make sense in that scenario.
     
  25. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Furthermore down at the metal his instruction sets to the CPU are bloated no doubt. He has me perma-blocked for my comments on his code and development pace. Think of all the valuable advice and cogent commentary he is missing out on :)
     
  26. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    No, it is complete lack of KISS there. The code will also self-destruct on hot-reload, but I guess quite a lot of people do not care about hot reload these days.

    With the right amount of KISS, you Awake() would look like this:
    Code (csharp):
    1.  
    2. public void Awake(){
    3. }
    4.  
    Although at this point you could just kill it.

    Baiscally, unless there's a performance issue, Ballistics, and its handlers would become components that self-register. And would not be intiialized by hand. (The fun part is that this setup will also survive hot reload, but like I said these days people often do not care)

    Other minor issues include using non-descriptive type for map keys (but I guess using typedefs to redeclare uints as BulletIds aren't a thing in C#), relying on a magic string and resources load (wasn't recommende) etc, but this is all minor stuff.

    The major issue is that you're fighting the framework and making life unecessarily complicated for yourself. This problem is pretty much screaming in reader's face when you see the snippet.
     
    JoNax97, MadeFromPolygons and Ryiah like this.
  27. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Only valid point here is resource load. But that is target to resource heavy prefabs. You shouldn't put prefabs with heavy assets in resources.
     
  28. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,569
    The business logic part is also hemorrhaging garbage allocations. If similar approach is used on unity side, that will be "ouch".

    Well, at least now I see why Epic is not fond of autos (C++ equivalent of var).
    This line:
    Code (csharp):
    1.  
    2. var invoiceAmount = Math.Abs(invoice.Amount);
    3.  
    Instantly made me remember "Never use floats for financial data", as its type is unclear. Thinking about it, it is probably a decimal, but nothing in the code guarantees it.
    ----------
    All points are valid there. You're writing your own mini-system for something that already exists in the engine. Then you claim it is KISS.

    Nope, not KISS enough.
     
  29. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Offcourse it's a decimal it's industry standard for a financial system. Garbage allocation doesn't matter in most enterprise applications. Maintainability is more important. Performance wise its good enough, the entire matching logic can match 40k invoices to 60k payments in 10 seconds included SQL IO.

    Our game allocates zero bytes in hot loops. And almost every other none hot code too.

    We even help our fellow dev's fix allocation problems https://github.com/microsoft/ProjectAcoustics/issues/42
     
    Last edited: Feb 27, 2021
  30. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    You want as little noise in the code as possible. Type declaration are noise. Thats why var is actually better than typing out the type for readability and maintainability. For same reason Microsoft now let you declare members like

    Code (CSharp):
    1. private readonly MyType _instance = new();
    Though I would have liked this better

    Code (CSharp):
    1. private readonly var _instance = new MyType();
    edit: Though the microsoft way is same has generic constraint new() so guess it makes sense