Search Unity

How to make the programming architecture of my big game?

Discussion in 'General Discussion' started by AlanMattano, Jun 21, 2020.

Thread Status:
Not open for further replies.
  1. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    How to make the programming architecture flow of my game inside Unity using game objects and components?
    Because my app is a wire spaghetti western hell!

    I love the concept of components, but Game Objects, prefabs and now Super prefabs, multiple scenes...

    Actual stage of one scene of my project
    upload_2020-6-21_1-39-28.png

    How to hood all together without losing control in a good way. Is there a nomenclature?
    Create systems, structure, boundaries, the meaning of controller, managers, and do I need a central database? Maybe I do not use Interface? How the code must flow in the big picture? This is the 3° time that I'm rewriting my code after refactoring.

    SystemFlow.jpg
     
    Last edited: Jun 26, 2020
    the_unity_saga likes this.
  2. jamespaterson

    jamespaterson

    Joined:
    Jun 19, 2018
    Posts:
    401
    I like the style of your diagrams! How much programming experience do you have?
    If you have already refactored three times there must have been a good reason to change the architecture before? Surely this must provide some direction in how to structure your code?
     
  3. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Feels like you could be overdoing it.

    However I'd need practical examples to say anything specific.

    In general, good practices to follow are KISS, YAGNI, and Murphy's law. Kiss means you keep things simple. YAGNI means you only implement functionality you actually need. Murphy's law means that you at the same time recognize that there are bugs in the code and put sufficient effort to prevent creation of additional bugs.

    In case of unity, component system works well for creating objects that act within physical sandbox and do not interact with each other at all. I.e. drone swarms.

    In practice.

    Let's say you're making a single plane controlled by the player. That whole thing could be one component attached to a rigidbody with things like engine power, lift and so on being configurable parameters (and maybe some debug drag wireframe).

    Let's say you're now making a cutomizaebl plane. It can have different type of wings, engines, they can be at different spots and that affects your flight model.
    In this case you split it into components. "Engine", "Fuel tank", "Wing" become mostly data-only components that have no code. "Plane" turns into "Plane Controlle" and upon startup gather components and based on them determines how does this plane fly.

    Let's say now you want plane to be ai controller. You extract control code from the "PlaneController", and create subcomponents "UserController" and "AIController". From here you could go two ways - either User/AI controller grab "PlaneController" on the start and manipulate values directly (like engine output, state of the wings), or you make both of them derived from IPlaneControllerInterface and have PlaneController grab IPlaneControlInterface and read input from it. Neither way is wrong.

    Let's say now you need fleets and airports. Now, at some point you'll have a "PlaneManager" that will keep tabs on planes one way or another, past that point you can go whatever way depending on what you want to implement.

    Let's say now you want to add helicopters into the game.
    In this case, PlaneController turns into AircraftController, you add "Helicopter Whinch" part and upon startup the aircraft tries to determine what kind of aircraft it is, and adjust control behavior accordingly.

    Let's say up to this point the planes didn't fly using physics and you want to change that. As in have engines generate actual forces and the like.

    That is work mostly for AiController, and will be split into few tiers.

    * Low level control: maintaining balance and lift.
    * High level control, which issues commands telling the aircraft where to go and how fast.

    Those can t alk to each other.
    Low level control would be dealing with things like maintaining proper engine output to keep lift functioning, cancelling unwanted torque, and so on. This is on drone programming level.
    High level control could be keeping tabs on your flight path and point of destination, and at the same time verify nearby obstacles, and take them into account.

    That's roughly the feeling.

    --------

    Regarding software architecture in general, there were books on extreme programming in the past. While I would not recommend to adopt all their idea, they had a good point - programming is not architecture and is subject to change. So while you can plan ahead and attempt to make a foundation and build on top of it, there can be a moment where entire foundation has to be replaced. Programmer is suppsoed to be able to tackle that, and code should not be really treated as something set in stone.

    Something like that.
     
  4. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    Stop rewriting. Start refactoring. I mean really refactoring.

    Don't worry about trying to have a grand design that is right. Focus on developing a sustainable methodology for continuously improving your design.

    Refactor every time the design impedes a change but only do enough to make it easier. Focus on very small changes - ones so small you know they don't impact behavior.
     
  5. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,192
    Agreed, and I think this is in line with the thread he created concerning Bob Martin. While he may have written practical books on approaching software design, the most important thing of all is being able to ship. If you can't ship just throw away the idea that you need to rework your code to be "proper" and focus on getting the game made.
     
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    What are the problems you're trying to solve?

    This. Don't try to make your code "right". Make it work, then review what you did and where you ran into problems, and apply what you've learned to to the next project you make. Being aware of concepts such as those in the Uncle Bob video you posted is great, but don't try to solve them all at once. That stuff comes with practice.* And that practice comes one step at a time.

    And even after many years of practice I don't try to get them all perfect all the time.

    * And that's one of the many reasons I advise people to start with small projects and work their way up slowly. That practice is far cheaper on a small project than it is on a large one, and you can iterate on your approach much faster.
     
    TeagansDad, Ryiah and AlanMattano like this.
  7. MaxGuernseyIII

    MaxGuernseyIII

    Joined:
    Aug 23, 2015
    Posts:
    315
    It occurs to me that there may be a different question underneath the one you asked.

    What do you want from your architecture? Specifically, what do you want that you're not getting?

    As @jamespaterson intimated, you must have some kind of motivation behind all this. What is it you desire of your codebase that it currently doesn't provide?
     
    AlanMattano likes this.
  8. Darkgaze

    Darkgaze

    Joined:
    Apr 3, 2017
    Posts:
    397
    I agree with the others.

    Let me add here a little bit, after a long while working with Unity and working with two types of people : Those who have no engineering background or interest in doing things right whatsoever, whose code is absolutely terrible (not understandable at all and terribly inefficient), and people who were hardcore engineers coming from database programming, whose code was absolutely terrible (too complicated and not oriented to Unity). Everybody ended up not understanding the architecture and doing things "wrong". Both end up in spaguetti code.

    Most programmers coming from other worlds (not videogames) that know about software architecture, patterns and such, generally approach Unity programming the same way they worked on c++ or python. They generally do it wrong.

    Unity is meant to be used as a COMPONENT BASED architecture. Yes, you can do other stuff, you can even have just one component in the scene, and do the rest in non-mono behaviours. Or have a HUGE component with 200 parameters all in the same component. I've seen that too.

    My rules are:

    - If the developers team is working together with an artist/semi-technical team, let the artists use the tools you create by using small components that they can connect by emmiting stuff that happens to them using Unity Events. Then create slots in your other components for UnityEvents, so that artists can connect them in the UI, this is something nobody does, but that's the way it's meant to be used. I've seen projects with generic components that emmit events. And other components listening to those events and connected using the UI.

    - Components should never know where they are attached, or what their parent gameobject is. Never. People who do this break encapsulation. I've seen people getting the parent gameObject and doing something in it. Instead, emmit an event and the parent will listen or be connected.

    - Follow the Component Oriented System, not the Object Oriented System we all learn: Components should be small, do just one thing and can be attached to any gameobject to add a "property" to it.
    For example: A "glass" game object can have components for "breakable", "transparent", "weight", and you set the properties that way. Breakable could have a UnityEvent emmited when it is broken by using a collider. The parent game object could have a "furniture" component that listens to any "break" event.

    - Don't create huge components with a crazy amount of parameters. They are not classes with all parameters. It it gets big think about how to divide it into different reusable components with some properties..

    - The funcionality that is generic and shared by other components, or stuff that doesn't need a UI, separate it and create classes (not monobehaviours). For example, "math tools", "mesh tools", etc.

    Just a hint.

    Read about Component Based Architecture to work properly with Unity and design your software the way it is meant to be designed:

    https://www.tutorialspoint.com/software_architecture_design/component_based_architecture.htm

    https://en.wikipedia.org/wiki/Component-based_software_engineering
     
  9. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    Do Unity Events no longer generate garbage? Because otherwise over reliance on them seems pretty bad.
     
    Deleted User and AlanMattano like this.
  10. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    There was already some good advice but I too want to add something here.

    First off - this video:

    My game works entirely without singletons and this makes everything more testable. I can drag any gameObject prefab into an empty scene to test its behaviour, almost everything works on its own.

    When you want to add a new feature, like a jetpag for your character or birds in the sky or a breakable wood box - all those things can be coded in one day in an empty scene ... you should ask yourself why it takes more than one day in your ongoing project and if you can do anything, so that you can easily add new features fast.
    Usually big systems (and singletons) are preventing you from adding functionality to your existing stuff - things like a "GameManager" that keeps track of the players health and damage output, so you can't just increase the weapon damage from another script, because the "GameManager" wouldn't know. Get rid of those overarching systems as much as you can, if you need some system to count your damage output, raise Events, something like
    "PlayerShootWithDamage", 10
    and let another independent system listen for this exact event.

    So the basic things you will need are: EventSystem and UnityEngine.Object-Pool (static dictionary based ones are the best I've seen so far for both)

    About inheriance and composition: Use composition when you want to swap out components, use inheriance to avoid copy&paste code and if you know you won't add any other functionality to the script.

    Conclusio: Make everything work on it's own, so you can add new features without touching any of the existing code. Use Events to seperate your systems. Don't use Singletons (they are a common pattern but ususally they cause way more problems than they solve). Don't overengineer - gameDev is hacky and if it works, it works, customers won't care about your code.
     
    AlanMattano likes this.
  11. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    If you don't call them from any update loop, you should be fine. It's not much garbage after all.
     
    AlanMattano likes this.
  12. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Ah, the "SOLID" lecturer? Not a fan of SOLID, so I wasn't watching that thread.
     
  13. Tzan

    Tzan

    Joined:
    Apr 5, 2009
    Posts:
    736
    I have a class called MeshTools
    Its not a MonoBehaviour
    It is Static

    :)
     
  14. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    To be clear I was asking for help.
    Does game object with scripts and transform 0 0 0 need to be marked as static for better performance?

    I learn basic in 1984 in a private specific course. But I learn C# just a few years ago. I feel like a beginner with less than 10 yr programming experience. Like out form school and making my first real big project.
     
    Last edited: Jun 22, 2020
  15. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    No. Modern hardware is very powerful. A single object being/not being marked static won't make much of a difference.

    Use a profiler.
    https://docs.unity3d.com/Manual/Profiler.html
     
    AlanMattano likes this.
  16. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    My main problem here was/is that my program is taking with Xfoil Fortran.
    So it makes it more difficult to debug than usual.




    Thanks for the Component-Based Architecture tips!!
    Yes, I was not using Unity Events in my code and need more of that but I'm a little afraid of them in my big project. In Unity, there are some missing reference problems within a big project. When pressing play the missing references are lost. And @Ryiah was very helpful to find a solution for finding connections between components.

    Yes, the first time I was afraid of losing the linked connections.
    It was impossible to understand how to improve the code without rewriting it.


    I think the problem is when there are a lot of systems running in parallel using components: the plane, the ai, the atmosphere where is in, the UI, the instruments, a database, a file system, starting setup, game state, player state, the environment. Individually are ok. But now that the project is big, components are talking to a lot to multiple systems.

    Thx @neginfinity for the detailed explanation. I'm able to make the airplane. I'm not able to make the aviation system. I do not know as an example If I need to make (as I'm doing now) a Start System, Save System, Input System, a database, or (As I was doing before) individual game objects has its own save component, data components, start setup (with relative load file).

    My first code was whatever it came out and looks to work. Was working but understandable and I was not able to scale it up or understand it. I rewrite the code so that was more organized. But some bugs where so wire to track and fix that I started rewriting the code for 3° time. And actually I'm refactoring it and rewriting. And my final gold is to "make it work" without crashing and capable of scalability. I'm learning the tools to achieve that. So any reference is helpful.

    This is a nice new question. Probably when I'm programming I'm experimenting as a solution (following) process instead of creating (be active making definitions and taking control).

    I notice that if I make one component wrong the whole system does not work.
     
    Last edited: Jun 23, 2020
  17. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Half of those can be probably ditched.

    I wrote about it before, but one possible way to program in unity using component architecture is to assume you're making a drone swarm.

    Where each drone knows nothing about other drones.

    For example. Why should Atmosphere be a component?
    At some point in your code you'll have something like getAtmosphericPressure() or getAirDensity().
    But, nothing says that those should result in calls to components.The easiest option would be to leave a single method which can be used to grab current state of atmosphere, and leave the rest of the code unaware of how exactly atmosphere works. If you need to make atmosphere a component later (which can happen if you implement interplanetary travel), then you'll be able to change what those methods do. Until that happens, they can return even a hardcoded value.

    Now, UI is mostly unaware of majority of things that happen in the game, for example, "Options" button could be simply triggering an animated sequence that makes "options" page appear. Next, AI does not need to be aware of existence of player. Instead it could have a generic mechanism for interacting with "enemies", and player would simply happen to be recognized as an enemy.

    Basically... components should talk to as few systems as possible, and if there's a "concept" in a system you're implemneting, that does not mean that said concept should be turned into an Object or Component.
     
  18. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    I disagree. If the atmosphere is stored in some kind of AtmosphereProvider Component, how would you reference it, when you instantiate Planes at runtime? A scriptableObject would be better but still - why does a plane and other objects need to know about the AtmosphereProvider at all? What if you want to use the plane in a different project - you would need the AtmosphereProvider, the EnemyPositionProvider, the FuelCapacityProvider and all those scripts in your new project, just because you want to use the plane.

    The plane just needs the value, calculated by some script - the best solution for this problem I can think of right now is a event with a callback or just with a return value (
    System.Func
    ).

    so when the airplane needs the AtmosphericPressure or any other crazy value from a completely different system, it could just call
    float atmosphericPressure = GlobalData.data["AtmosphericPressure"].Get<float>(currentPosition);

    So it only knows about the static GlobalData, but not about the specific AtmosphereProvider Component or any other provider at all. If you then, implement some fallback values in your "
    Get<float>()
    " method, you can use the plane with or without the AtmosphereProvider, also you can exchange the provider completely without touching the code of the plane (or any other system requesting those atmosphere values).
    Also you can easily debug different values, by just overriding the Func(), have a listener to count how many times this gets called and if the calculation provides wrong results and so on.

    The biggest problem with this example is, that a atmosphere provider is an extremly specific object - if you build up dependencies across your systems, you always want them to refer to big communication systems, which are used by all of those smaller systems and components - so you end up with basically one dependency - the one to the connectionSystem of your choice .. in my case thats a EventSystem. Almost every single component in my project depends on this system but none of them depends on each other.

    Decoupling is one of the really important parts of programming (in my experience). Working on isolated features is just simple - e.g. everyone can make a snake game in about one day .. now how long would it take to implement a snake-game on the city-blocks of an GTA game? Controlling the top-down camera, disabling the UI, ignoring collisions between the snake and vehicles, problems with LOD, particles, lightning, overwriting player controls and so on ..
    Keeping things isolated, also keeps things simple. 99% of unity projects with bad code are a dependency hell.
     
    Last edited: Jun 23, 2020
    BrandStone and SSSekhon like this.
  19. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    Sorry @neginfinity I think I read your comment wrong, we are talking about the same thing, right?
     
  20. That is true. But also 99% of the projects aren't GTA5 either. And 99% of the projects are one-shot. They ship and cut. Trashcan.
    Game development isn't a business application (usually) which is kept alive for 5 or 10 or 20 years. You push it out of the door and you're usually done.
    In the case when you're working on a giant game, then yes, you need all those things and more. If you are working on a connected game (MMO or Looter Shooter or something), which will be maintained indefinitely, then yes, you need those stuff.
    But to make a middle sized indie game, you don't need event-bus and total separation of concerns. (It does not mean you don't have to try, it is just not a big failure if you end up with some coupling)
     
    angrypenguin, one_one and AcidArrow like this.
  21. one_one

    one_one

    Joined:
    May 20, 2013
    Posts:
    621
    I think that's a pretty important, if not perhaps the most important aspect of architecture. KISS & YAGNI was mentioned above - this also extends to software architecture. For small projects that won't be touched later on? You'll likely be fine doing lots of hacks and tight coupling. For larger projects with multiple programmers? You'll definitely want at least some rough overarching structure to your code. Pick the right tools for the job.

    However, this also means that you need 1) the ability to judge how much structure & architecture you'll need (in advance!) and 2) some idea about potential structures, clean code and how to achieve & maintain that. Sadly, the second part doesn't seem well spread or even acknowledged on this forum - in fact, some people here seem to be openly hostile to the idea that their way of cowboy programming may be pretty damn harmful in some projects. Yet, clean code and architecture are possible and useful in many projects without having to bend over backwards for it.

    Anyway, in addition to all those great pieces of advice above, here's my personal opinion: Make sure to read into architecture and clean code - learn it, practice it, then put it into your toolbox. All the while, keep in mind that the greatest virtue in software engineering is discipline: Stick to your architecture, comment/document your code and refactor regularly. And then get back to writing code and working on your game with discipline, instead of overthinking architecture!
     
    Last edited: Jun 23, 2020
    angrypenguin likes this.
  22. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I started this post to disagree with "refactor regularly", but then realised it depends a lot on what an individual considers "refactoring".

    For major reworks, a big thing to keep in mind is that terrible code only matters where it impacts performance or productivity. I've often had chunks of crap code in a project which I've deliberately left alone for significant periods of time because a) it worked and b) nobody had to touch or work with it. There would have been zero benefit to making it nicer, but there would have been the cost of doing the work and subsequent testing, and that could be invested elsewhere in the project where it'd make a difference.

    But as soon as there's an appropriate benefit to paying that cost, yeah, do it.
     
    one_one likes this.
  23. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,074
    Everything will be ok if your systems are loosely coupled.
    If you design with subsystems in mind you will generally have a solid application.
     
    AlanMattano likes this.
  24. one_one

    one_one

    Joined:
    May 20, 2013
    Posts:
    621
    Good point - there's not much use in refactoring pieces of code that do their job and sit in relative isolation, that's for sure. Refactoring shouldn't be done for its own sake. The reason why I put it like that, though, is that I've often seen refactoring being postponed until it becomes a huge task. At which point it is either done begrudgingly (while breaking lots of things along the way) or... given up completely so the project either enters development hell or will see that part completely rewritten.
    Any way, refactor regularly => restructure your code if it starts feeling messy and will be worked on in the future, instead of taking on technical debt by hack after hack.
     
  25. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    "IF". Why is there an atmosphere provider? Do you have different types of atmosphere? Are you trying to fly on mars? Does atmosphere change often?

    If not, you should kill it.

    And what if you don't? Then you have wasted hours programming for nothing.

    A "Provider" needs a reason to exist. If it has no reason to exist, you should kill it. A "provider" may emerge during development, but you shouldn't create one preemptively.

    I would kill all those concepts if they existed, and wouldn't have made them in the first place.
    My idea would be to make the plane self-contained, like a drone. It would learn of enemies based on data from on-board "radar", and it would also know its own capacity based on fuel-tanks attached to it.
    And in the case you need a different atmospheric pressure, there would be a single point that needs changing. "getAtmosphericPressure()".

    Actually this should be a very simple problem.

    GTA has collision detection and physics built into it, so you could simply creatively reuse current concepts. A segment of the snake would be represented by modified hoverbike (gta has overbikes), then the snake would be implemented as a chain of hoverbikes, where each follows path of the previous one. So, basically, as soon as you find a flying object and a raycast, you have solved the problem.

    I don't know. You tell me.
     
    Last edited: Jun 23, 2020
  26. ShokWayve

    ShokWayve

    Joined:
    Jan 16, 2013
    Posts:
    136
    DarkGaze this helps me soooooo much. Thank you!!!!!!!
     
  27. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    One more thing.
    This:
    Code (csharp):
    1.  
    2. float atmosphericPressure = GlobalData.data["AtmosphericPressure"].Get<float>(currentPosition);
    3.  
    Is a major landmine in a game code.

    You're referencing a parameter of atmosphere via its string name, and additionally you have variant system in place, which casts value into different types.

    So, potential bugs are "You've made a typo" or "Value is not a float". You also can no longer refactor atmospheric presure, because it is not a variable, but a value in a dictionary that may not be present. The extra fun part is that those problems can be only found in runtime and if you have those problems in a rarely called places, then you now have a heisenbug.

    Better solution.
    Code (csharp):
    1.  
    2. var pressure = GlobalData.atmosphere.pressureAt(position);
    3.  
    Or:
    Code (csharp):
    1.  
    2. var pressure = Atmosphere.pressureAt(position);
    3.  
    This can be refactored easily, and a typo can be caught at compile time, so you have smaller chance of heisenbugs.

    --------

    If you REALLY insist on using a string key that may or may not be in the dictionary, then it should be.

    Code (csharp):
    1.  
    2. public static class ConfigKeys{
    3.     public static readonly string atmosphericPressure = "AtmosphericPressure";
    4. }
    5.  
    6. ///
    7. var presure = data.get(ConfigKeys.atmosphericPressure);
    8.  
    Because in this case the key is no longer a "magic string" equivalent of a "magic number", and you have only one spot where it can change.

    ------

    It is also a good idea to use templates, I mean generics in such way where they chose implementation based on their parameter, so you won't have to type variable type again. Meaning, it would be
    Code (csharp):
    1.  
    2. float value;
    3. data.get("key", out value);
    4.  
    As you cannot auto-resolve generic type from return parameter.

    Even that scenario is not that great, as you have to type the variable type explicitly, but it is useful in scenario where you're reading struct/class data fields from a storage.

    Code (csharp):
    1.  
    2. var result = new SomeStruct();
    3. data.get("field1", out result.field1);
    4. data.get("field2", out result.field2);
    5. return result;
    6.  
    Or something like that. It is a bit a silly example, but helps to get some ideas about reducing verbosity of C#.
     
    Last edited: Jun 23, 2020
    Deadcow_, BrandStone and AlanMattano like this.
  28. ShokWayve

    ShokWayve

    Joined:
    Jan 16, 2013
    Posts:
    136
    I do have one question regarding a component-based system. To use an example talked about earlier in this thread, supposed I have an airplane flying through the atmosphere. Using components and events, how would the airplane get the atmospheric pressure or wind speed and direction at its location? If the atmosphere published that information for all locations via an event in update, that would consume vast amounts of processing power. The plane would then have to search for its location and thus also consume processing power. So, in that situation, how would the plane know the atmospheric conditions it needs to know.
     
    AlanMattano likes this.
  29. ShokWayve

    ShokWayve

    Joined:
    Jan 16, 2013
    Posts:
    136
    In your solution, doesn't the airplane have to know about the atmosphere? Is that too tightly coupled - especially for a component/event based system?

    Thanks.
     
  30. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    In my solution there would be one object somewhere which would return atmospheric pressure on request as a static method.

    Basically
    Code (csharp):
    1.  
    2. World.getAtmosphericPressureAt(position);
    3.  
    Said object could be implemented in many ways, but it doesn't matter how exactly it made.

    Or I would have an atmospheric pressure getter within airplane:
    Code (csharp):
    1.  
    2. public class Airplane: MonoBehavior{
    3.     protected float getAtmosphericPressureAt(Vector3 pos){
    4.     //stuff
    5.     }
    6. }
    7.  
    Which coul call more detailed implementation, including static method I mentioned prior.

    I don't see a point of "decoupling" this further, and, for example, providing a callback for the airplane.
    Code (csharp):
    1.  
    2. public delegate float AtmosphericPressureGetter(Vector3 pos);
    3.  
    Because for such callback to exist, a situation where each airplane receives pressure via a completely different method has to be common. If in no event two airplanes receive pressure using a different method, there's no reason to make requesting pressure configurable.

    One important thing that is worth keeping in mind is that unity objects basically do not have a constructor. And callbacks are normally passed through constructors. Your object is brought into the world via Instantiate, and Instantiate doesn't care about your plans for dependency injection, callbacks or anything else.

    Meaning, your airplane will have to acquire callback from within the Awake, OnEnable, or the object on the calling side has to be aware of internal workings of Airplane class, and that it needs extra initialization once instantiated. That erases benefits of callback and instead creates a bug. Because when object REQUIRES a callback via a parameter of a constructor, you're made aware of this requirement via a compile error, and when the callback has to be passed as an extra initialization step, you'll only learn about this behavior via a runtime error, and not immediately at compile time. The need of having to implement airplane babysitting system in the name of dependency injection erases benefits (if any) of dependency injection. So there's no reason to bother with it.

    Instead it is reasonable enough to have a single entry point within Airplane which calls specific implementation.
    Code (csharp):
    1.  
    2. public class Airplane: MonoBehavior{
    3.     protected float getAtmosphericPressureAt(Vector3 pos){
    4.     //stuff
    5.     }
    6. }
    7.  
    Once you move your code to the new project, it will fail to compile at exact this point, and by fixing this part only, you'll make your airplane work again.

    One, of course, could attempt to go further, and indeed, attempt to implement IAtmosphericDataProvider, from which a component would need to be derived, and then make airplane grab this component at runtime within OnEnable/Awake, and make it dependeng via [RequireComponent(typeof(IAtmosphericDataProvider))] (see: https://www.reddit.com/r/Unity3D/comments/59na4t/psa_you_can_use/ ).

    But. This breaches YAGNI. Right now you do not need variant behavior for atmospheric data, and by default you will not ever reuse the airplane class in another project. So you don't need this interface. Introduction of this interface moves compile time error ("I don't know what World.Atmosphere is"), to Runtime where the game spews errors only when you hit "play", which is inferior to compile time checking, as runtime errors can turn into heisenbugs.

    So. You can't fully decouple, as constructor is inaccessible. Attempting to provide flexibility in a way comparable to a constructor requires a babysitting method, of which future user has to be aware of, what's more failure to properly implement this method results in a runtime error and not a compile error, which makes it inferior. What's more, you're introducing one more interface you don't actually need, as it only becomes useful in a "what if" scenario (what if I decide to reuse this class in 50 years from now?), which almost certainly will never occur.

    Ther'es also minor stuff like that callback failing to work within unity serialization system, meaning implementing it would disallow you to hot-reload your game while debugging.

    So. That leads to a conclusion.
    Trying to provide flexibility into airplane via DI or faux-DI is a waste of time, as it is not really required (all airplanes measure pressure the same way by default, there's no variation). A simple solution is a to wrap pressure-getter in a single method that calls more detailed implementation, and should you reuse the class in the future, you'll have only a single fault point, which will happen at compile time, and not at runtime as you would if you wrote an interface.

    ----

    By the way, I don't really feel like debating any of it, as it makes sense to me, and worked well enough in the past. That's simply my thoughts on the scenario presented.
     
    Socrates and JoNax97 like this.
  31. ShokWayve

    ShokWayve

    Joined:
    Jan 16, 2013
    Posts:
    136
    Thanks so much for your reply. I am not debating. I am actually learning. I am so thankful for your comments and the comments of others because my code is often a nightmare to manage and can be confusing - even for me. LOL! So your comments and the other comments in this thread are helping me learn a a lot and I am exciting to try the new ideas I am coming up with based on reading this thread. Thanks again.
     
    neginfinity likes this.
  32. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    Thats a nice idea, I'll implement this into my BlackBoard System, which I had to write for my AI. ^^
    Also I am using Enums (casted to (cached) strings at runtime) to avoid magic strings - in VisualStudio, you can add new values to an Enum without visiting the enum, which is basically like writing strings that can be referenced.

    Yes, the Type casting is .. not perfect, but afaik blackboards are the industry standard when it comes to enemy bots (GOAP, Behaviour Trees or other flexible systems) and they are written like this in every example.
    If this system is useful for things like AirPressure? IDK. It is possible and decouples the system almost perfectly - the downside is indeed that the errors will only occur on runtime, as there is no direct code-connection and therefor it cannot fail at compile time.
    Working on one thing at a time, I don't think thats a problem at the end because everything AirPressureProvider has to do, is to register the calculation method in the database when the game starts - with
    [RuntimeInitializeOnLoadMethod]
    in any C# class, this will always be provided as long as the class exists - so it's a low risk that the method returns wrong values or is not set at all.

    Refactoring a static GlobalData class with 2000 lines, linked to almost all systems in the game seems like a nightmare.
    On the other hand .. if it just provides Methods, it is quite similar to Debug.Log.
     
  33. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Seeing this in every example does not necessarily mean this is the right thing to do. Unity tutorials had a nasty habits of using bad practices.

    If you don't know if it is useful, then it is not needed for this particular task and therefore should be scrapped.
    For every thing, there should be a reason why this thing exists. If there's no reason, then it is not needed. A thing that exists for no reason contributes to complexity of the program and in turn increases its maintenance cost.

    "decoupling things perfectly" does not contribute to getting things done. And downside of runtime error instead of a compile time is a major one.

    Or you could scrap provider and the database and keep one small method until a need for something like that actually arises.

    It doesn't need to be linked with all systems, and 2000 lines is not exactly a lot. Most likely scenario is that you would want to split something like that into multiple subobjects, as long as functionality that is grouped by common theme arises. For example, if GlobalData spends awful amount of code dealing atmospheric calculations, those can be grouped into Atmosphere class. To keep them in one place.
     
    John_Leorid and Ryiah like this.
  34. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    In my case, the atmosphere has some curves value that changes over time. The airplane read that curves and get the values in the intersection altitude ( transform position Y). For more detail: https://forum.unity.com/threads/wip-soaring-stars-lab-flight-soaring-simulator.411299/#post-3516680
    The atmosphere contains the wether situation and evolution (humidity pressure-wind direction temperature) and the component calculates the local variables by looking at the terrain angles that surround the transform, the wind matrix, the local thermals, and get the Reynolds number, wind intensity, and 3D direction, etc.
    The atmospheric system injects data into the component. And there is also a thermal system and a wind system (using audio as an XZ source) that also copies the terrain mesh Y as layers by altitude for making the modification for wave and rotor simulation. So the atmosphere receiver component that is applied to the airplane wing section transfers gets the final by combining all this vectors data and feeds the aerodynamic local component (with Re, intensity, and direction) that generates the light and drag forces for each section of the wing. The aerodynamic component calculates induce drag etc.

    I'm making a relatively larger project without ever working or looking to a large project before. So I do not know-how systems, framework, dataflow are done. Programming is not like painting, modeling, or looking to a shader graph where you have already view some awesome examples that you can get inspiration from.

    I do not remember where I put some particular functionality. It was on the save function, on start, on the object, prefab, or system, etc..? Nomenclature helps me to remember now. System controller manager etc but I'm just guessing.

    And a problem this that is that I cant visualize connections when they are missing. Inside VS or Rider, you can go faster from one function to another. But on the hierarchy is hard. I wish a 3D hierarchy showing the connections.

    Code Refactoring purpose, is readability as I understand it is opposite to rewriting.
    • Improving nomenclature renaming
    • Avoiding repeat code by regrouping
    • Extracting into a new function or class when code is too long or complex only if necessary.
    • Improve performance (? only if done safely)
    Nothing that can brack the code. Nature is like a big memory catcher keeper. But In Unity, I'm always afraid of renaming! and losing drag and drop references.

    Extracting can be controversial since "entities should not be multiplied without necessity". Extracting must have a purpose. If the purpose is readability then it looks like it goes in contradiction with performance. So after Refactoring I think is better profiling and improving performance where is necessary. And so is not inside the cycle of Refactoring. Personal opinion.

    Thank @neginfinity so much for real code examples!
    What is not clear for me is when to use
    GlobalData.atmosphere.pressureAt(position);

    or
    Atmosphere.pressureAt(position);

    Looks like a centralized tree or a parallel tree.
    I prefer parallel and multicore but How is the rule to decide or how to decide if it is better between the strategies?
     
    Last edited: Jun 24, 2020
  35. Actually it is. You're just not that interested in it. But that's okay.
    But we, who do this for a living often review other people's code and techniques for inspiration and to broad our vision and get ideas how other people solve problems.
    I especially like to review old game sources, where the performance is/was crucial, so a couple of interesting techniques were used to achieve something.

    But again, I grew up on C64 and we developed assembly demos/intros and then we showed off our techniques and sometimes even our code to each other (called Scene) and we learned from each other. So I grew up in a community where we inspired each other to get better and get clever.

    Nowadays, the business software development is a boring wood-cutting if we compare (sorry, wood-cutters).
     
  36. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Component architecture from my POV that works every time in small or large games or Unity based apps ...
    data->data controllers[the action done with data, data can be a part of controller component or scriptable objects for varied data profile the controller component consumes]->controllers manager/s[synchronizes only controllers need for actions to be synched] -> overview manager[usually relates to UI I/O].
     
  37. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Aye, and even some of our languages are now being designed partly around the idea of limiting people so that they don't trip over their own feet.
     
  38. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    There are no trees here. And since you appear to be relatively new, "Multicore" should be last of your concerns.

    GlobalData appears when you have a big amount of data about the world. Actually, "GlobalData" is a bad name, as it is non-descriptive. Depending on what it represents, it could be called "World".

    "Atmosphere" without "GlobalData" appears when there are a lot of interconnected functions dealing with atmosphere, but no other global information. In this case related functionality is wrapped within a single class, and that class is called "Atmosphere".

    Programming is very similar to painting/modeling, and shadergraph is programming.

    That's why you use highly descriptive names. You won't remember your program. That's why you should write it in such way that in six months time you'd be able to quickly guess what's going on there.
     
    Martin_H and AlanMattano like this.
  39. Disrop

    Disrop

    Joined:
    Jun 18, 2020
    Posts:
    5
    Read the various guides and I think you will succeed
     
    AlanMattano likes this.
  40. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Last edited: Jun 24, 2020
  41. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    I would advise to take all those advanced concepts, throw them away, and get the game to work. Once it is working, then you can worry about advanced concepts. The way I see it, currently you're trying to devour all terminology and acronyms you come across, however, that won't make your work easier. It will add source of confusion and distraction.

    There shouldn't be a need for parallel distributed databases. Or for databases in general. Databases appear when you have milions or billions of values, or when you're writing backend for user profiles.
     
    Ryiah likes this.
  42. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    One more thing.

    Long time ago I read a short verse that went something like this (can't translate poetry properly, but general idea went like this)


    "When I was a kid, I dreamt of building things,
    From sticks and wood, I built
    My plane, which flew
    By flapping wings.

    Now I grew old, my hair went gray,
    I am respected engineer, and now my plane
    Is built from steel, it has propellers and turbines,
    and when it starts they roar

    Alas, now it can fly no more".


    Now. (I'm not good at translating poetry, especially when I can't remember half of the original verse)

    Your job, being a beginner is to build that first plane from sticks and wood and make it fly by flapping wings, even though it shouldn't work this way. From that point you can improve. Trying to dive head first into methodology and terminology will overload you with information that you cannot use or apply to current project. And that will result in a "plane that cannot fly".
     
    Deadcow_, ShokWayve, JoNax97 and 3 others like this.
  43. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,493
    That's like the Answer to the Ultimate Question of Life, the Universe, and Everything
     
  44. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Alan. Can you take two cubes, make one a fuselage and the other the wings by scaling it just as a graphical representation of an airplane? Good. Now, can you drop a rigidbody and do not check the isKinematic box on whichever is the parent object, write a script that references the rigidbody and controls the physics forces brought to bear on it and use the WASD keys to fly. Can you make it bank into a turn without the nose dropping? Can it do a barrel roll? Does it go in the direction the nose of the graphical airplane representation go when A or D keys are pressed i.e. left and right? You may need two other modifiers keys. Can it accelerate and have its max speed limited? When it goes below minimum flight speed does it begin to fall? You should be able to do all this with one component. Call it FlightEngineController. Concentrate on that first. It should only take you a couple of weeks and reading dozens of questions and the scripted code based replies to tweak your version of it. Expose all variables ...every one of them in the Inspector,. .watch the numbers and adjust according to how you view your graphical representation needs to behave. Do this first and file the diagrams in the 13 round file. They are not helping you solve the most basic problem facing you. Data..the numbers in the Inspector ->Controller->the script controlling that data. Once you struggle through that the next steps will be much more obvious.
     
    ShokWayve and neginfinity like this.
  45. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Yes, I use my plane game object for a fin or a sail as well with relative reusable components. And all pointing as you describe.

    I do that all the time. Is the wrong approach to components?

    Here for "other", you mean the emitter or receiver? In that case, how is the code, script line example?

    For the emitter, I presume one solution is:
    Code (CSharp):
    1. using UnityEngine.Events;
    2.  
    3. public UnityEvent OnMyEvent;
    4.  
    5. OnMyEvent?.Invoke();
    and the listener? is just?

    Atmosphere.UpdatePressure();


    If it is the case, I often miss the references.

    But when you have hundreds of game objects using components? how Is the general picture? Because it looks like a very complicated net. Can you expand or make a drawing?
     
  46. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    I feel like the advice given in this thread is being ignored.

    You don't need events. And listeners. You're new. You need to go with the simplest solution, after tossing out most of the complex concepts you mentioned. In doing that you'll learn and improve your skills. THEN you maybe try more advanced concepts.
     
  47. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,192
    I'm not surprised as based on his posts he's completely stuck on the idea that you need to design everything from the very beginning, but since he hasn't made anything he lacks the experience needed to do so and thus can't move past that to the actual game making stages where he can get the experience.
     
    AlanMattano likes this.
  48. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    The problem with Unity and known programming concepts is that they already did all those complicated things for you.
    They have a performant database, the assetDatabase. They have an internal Event System handling the communication between all systems like rendering, game logic, audio, input - all this is already there.
    If you set up a database or a complicated event system or any other things like the factory pattern - you are building it twice because it is already in the engine.

    When you write code in Unity, this code is just scrips. It's not engine code. Keep it as simple as possible - just try to avoid creating a singleton dependency hell and you are fine.

    The event thing I mentioned is just useful if you really plan on creating modules which you can use in more than one project, which is true for my behaviour trees and radial menus and stuff. It is not true for my player character, I will rebuild one for every game I make because they are all completely different.

    Don't worry, complexity will come over time, you don't have to ensure that it's present from the start. ^^
    Just think about the thing you want to create and then head for the easiest and fastest solution (considering OOP, so don't make everything static to connect them, use the editor to connect things).
     
    AlanMattano likes this.
  49. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    As certain character said... "THAT WILL BE AN IMPORTANT LESSON.".

    Might take some time to learn the lesson, though.

    P.S.
    New avatar, huh.
    ------
    @AlanMattano, the "Keep it simple" advice given in the thread, is given for a good reason. It is not a some cheap words given by a completely layman, not some cheap dismissal of your question, but something that came through practice and often learned at great cost. Basically, long time ago, many people in this thread tried to do the very same thing you're attempting to do now, wasted ton of time, and now showing you a way not to repeat their past mistakes. Because they learned their "IMPORTANT LESSON'.

    By continuing current path, you'll subject yourself to a looong session of running through a nuclear minefield (with many detonations, by the way), will most likely end up with an unmanageable huge codebase, which in the end will fail to work and will have to be scrapped. Past that point, you'll either try to boldly do it all over again (with exact same result), or maybe realize why people kept saying "Keep it simple" to you, and why people ketp saying to throw systems away.

    So, basically, "keep it simple" appraoch will save you months of work.

    But. Your life, your choices, I guess. Have fun.
     
    Last edited: Jun 25, 2020
  50. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,192
    neoshaman likes this.
Thread Status:
Not open for further replies.