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. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    So lets say you are building a game where you can build your own aircraft and then control it in 3D battles against bots.

    Main Menu

    You'll have a main menu, extra scene, mostly UI, maybe the settings are connected to the save system but more on that later. Connect the UI using the Unity Events, maybe have two MBs (MonoBehaviours) one on your settings menu to actually alter the settings and one to start the game (load the game scene) or open the hangar where you can build your aircraft (load hangar scene).

    Game Scene

    Ok the game, probably a map, some kind of terrain, the PlayerAircraft and EnemyAircrafts (or just spawn points).

    Player
    The Player - I've seen you have a special GameObject handling the input, you can throw it away and use the "new Input System" from Unity where you can setup absolutely everything (Controller, Keyboard, Joystick, TouchControls) and map it to things like "Move" or "Shoot" which are universal.
    OK, the Player, just one Monobehaviour moving the aircraft around - if it needs settings from the hangar, use a ScriptableObject - AircraftDataSO - it will contain all the data the aircraft needs (aircraftParts, position of these parts, speed, weapons, everything).
    So the aircraft builds itself in Awake(), loading the parts defined in AircraftDataSO. (AircraftDataSO is assigned in the editor, drag&drop, like all other connections in this example).
    The aircraft is now visible - now it has to connect to the WeaponSystems - use GetComponentsInChildren() to get those.
    The WeaponSystems themselfs are also MBs, all using the same Interface (or superclass, you decide) they have basically just one Method: Fire() - which will cause them to launch missles, bullets, bombs, lasers whatever.
    They should also provide an Icon, currentAmmo and maxAmmo. Also they can search for Targets on their own, if they have some kind of aimAssist - they should provide this information too, currentTarget (null or an enemyAirplane).
    (To actually deal damage, the projectiles or in case of hitscanFire the weaponSystem can use
    UnityEngine.SendMessage("Damage", weaponDamage);
    )

    OK, in Update() the PlayerAircraft listens for the PlayerInputs (using the new Input System from Unity) and provides data to the UI (current Speed, altitude, orientation) and moves the Airplane.

    If the player decides to shoot, send the new ammo values to the UI.
    If the player takes damage, send the new health value to the UI.
    ...

    If health reaches 0, destroy the playerAirplane (swap it out with a fractured one) and send information to the UI "You Died".
    Thats it for the player and the weaponSystems.

    UI

    The UI is in this example connected to the player (meaning the player knows about the UI) and shows the data thats pushed to it. Example:
    SetCurrentTarget(Transform target){ // show marker and calculate screen position if the target == true (Unity classes have an implicit bool overload, != null can provide wrong information on UnityEngine.Object classes)}


    Enemy AI

    Maybe statemachines, maybe GOAP, maybe BehaviourTrees, maybe just IF ELSE conditions - whatever you use, it is one system on its own, they fly around, attack the player and die. For prototyping you can make them just like the player, one MonoBehaviour disconnected from everything else.

    Hangar

    Same as the Main Menu, mostly UI - also connected to the SaveSystem and the AircraftDataSO. Build your aircraft, save all the parts, their positions, calculate whatever values you need and save them to AircraftDataSO and on the hard drive if you have to keep the values.

    Save & Load

    The save and load system is one static class, where systems can write to with their unique ID, there are so many tutorials on this topic, it's really easy.


    So thats it, the whole game as a prototype - all classes and their connections. No Database, no messaging system, no input controller - we used what Unity provides for all those things and made a (almost) complete game.
    If you need a level selection screen, you can put it into the main menu or make a extra scene for it.
    If you have to count how many enemies are alive in one Level, use a ScriptableObject with a counter.
    Everything can be solved like this example - you create components, you connect them to whatever they need to be connected to by Drag&Drop and thats it. ScriptableObjects are actually database objects, using the AssetDatabase from Unity.

    Any Questions?
     
    neginfinity likes this.
  2. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    The atmosphere is a fake out that you will understand how to do by watching the numbers in the FlightEngineController. There is no atmosphere. There is only numbers that your flight controller will consume to make the graphical object look like a flying plane.It will usually have nothing to do with real atmospheric conditions but will be predicated on the behavior derived from the numbers you understand when building and flying the FlightEngineController. You don't need UnityEvents. More red herrings to set you down the path of wrong-headedness. I had folks with your headspace come into a company I got 3 million in venture capital for with the work I had done when presented with it.. The graybeard there did not understand what i was doing but I built many complex interacting systems rapidly. They hired a bunch of C#-ers with no Unity experience. I left. Three years later they are bankrupt.
     
    AlanMattano likes this.
  3. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    I wish I can do that. but I'm unable to rename. This is just one of my scenes! not al the project
    upload_2020-6-25_18-45-3.png
     
  4. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    0. Does the plane fly? Because that's the primary concern. And not not the UI, databases and so on.
    1. Multiple components can be attached to the same object. No point in making a separate GameObject for every little thing.
    2. You ARE using prefabs, right? Because I'm not seeing even a single prefab icon anywhere in that hierarchy view, and not using prefabs is quite dangerous.
    3. It is highly likely that you're overusing drag and drop references. To avoid manual reference hell, you can have objects find relevant objects automatically. For example, an UI element that displays speed could have "SpeedIndicator" component attached to it and nothing else. Upon startup, SpeedIndicator would search for some sort of data source component to pull data from (FlightUiRoot, for example) using GetComponentInParent, and that would be it. Zero manual setup.
    4. With a LARGE number of indicators, I'd start looking into implementing equivalent of interpolated strings with ship parameters. This way all indicators would be able to use single component.
    5. It is highly likely that you can reduce number of intermediate panels through use of anchors.

    Basically, it is highly likely that you've already coded yourself into corenr, and right now have a mess at your hands.
    The first thing I'd be doing here is exterminating any manually specified references. You seem to have a lot of them.
     
    AlanMattano likes this.
  5. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    AlanMattano likes this.
  6. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Ouch. There should be one parent UIManager. Make each dial, switch and level on the pilot controls a graphic and a controller that gets the input from the FlightEngineController and uses multiplication, addition, subtraction and division to get the needles of the dials pretending they are outputting realistic values based on getting the proper input.

    Watch this
     
    AlanMattano, neginfinity and Ryiah like this.
  7. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    I was expecting drawings and nomenclature and principles were not expected. But they are working!

    Yes, Thx @neginfinity. I'm in the corner since Unity 4/ rewriting and debugging always the same a big mess.

    1 Ok. I was thinking about folders (without a transform) in the hierarchy. But this can help me to be more compact.

    2. yes, I do use prefabs for repeating objects. Only small prefabs like ailerons, flaps, instruments. Not nested. My scene is 600km x 600km and running in-game 30km. SpeedTrees and houses are a problem. To understand how nested prefabs performance works in a big scene, I recommend moving Megacity game objects prefabs. Moving just one tree in a huge project can take around 2 minutes just to move it. So in this big project, I'm trying to separate big prefabs with lots of nested prefabs in a different Scene. And put a specific system that loads them at the start using user preference setup. Uncheck autosave checkbox in prefab workflow is mandatory to be able to manipulate prefabs inside the dense scene. Note, Waiting for the Unity terrain layer system (I try it and look fantastic!)

    3. Find: Yes, I agree. I presume (but never tested) in very large dense scenes Find in multiple game objects will take much longer to start the game than drag and drop. When renaming, "inside quotes", in Rider works. But I'm afraid I cloud fail in Visual Studio. If find fails will tell me via console. But later I will be lazy to take them out hook up all a thousand of connections. So that takes me to drag and drop.

    4. Ahh, fantastic an expression inside a string. Very interesting and compact! Con you expand the concept. I need to learn how to use objects more often as well and pass objects between functions.

    5. The way it set up now is that it can scale up well to more than 16k in resolution, and down too. I use a fast transform, all to position 0 and anchor 0 to 1. In this way, the transform is faster and scales very well. I use a button that moves the anchor to the 4 corners. I never know how much of the logic must run on the button as a component or in a more centralized point of trust group place.

    Thx @neginfinity and @ippdev for the video! I was looking for a general picture.

     
  8. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    I recently spent a bit of time reading things about Godot and I saw someone write that with Godot you're supposed to follow the OOP paradigm intead of the component based architecture. For me that was one of the biggest counter arguments to using Godot, that I've seen so far. The component based approach just makes so much more sense to me...
     
    Ryiah likes this.
  9. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    You should use prefabs for UI panels and windows. Big prefabs.

    You'll hit single precision limits with those sizes. Safe size of a unity scene is 16x16x16 km cube in +- 8kilometer coordinates from world origin. Beyond that point you'll need origin shifting or similar technique.

    You should almost never use "Find". Use GetComponent(s)InChildren/GetComponent(s)InParent.

    You're already at a point where your project is likely unmanageable. No point in being afraid.

    ----------

    I would advise to start a new blank project, put this one on hold, and reimplement everything from scratch using KISS principles. Without managers, complex concepts, databases or anything like that. Hannibal_Leo described a good skeleton approach to the project of this type.
     
    John_Leorid likes this.
  10. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    This picture is how I think insanity would look like. Millions of buttons, inspector fields, gameObjects in the hierarchy, ... - it feels like looking at an entire MMO written in one single File with hundreds of thousand lines.

    Why is it so extremly complex? Do you really need all that stuff? Is the whole editor window just there so you can find the gameObjects in your hierarchy?
    Why is "Database and UI" one Object? The UI has nothing to do with the database, it just draws what it is told to draw.
    Button Manager "KEEP TRACK OF ALL BUTTONS IN A CENTRAL POINT" - why? When you want to add new buttons, you have to write code in this button manager that already handles your existing buttons, thats not extensible, why would you want such a manager? Let every menu handle its stuff on its own. Crosshair handles crosshair, ingameMenu handles ingameMenu --> opens other menus or continues/quits the game, options handles options - modules.
    You need a new Module? You create it from scratch without touching any of the existing code until it works, then you implement it with as few connections as possible.

    Load Save Manager - Why is it linked to 3 other components? If you need to structure your code into multiple files, create plain C# classes and do it inside the code unless you really want to swap them out - but why would you swap out "Export Airfoil" - where a Script named "Export Airfoil" is assigned?

    Namings: what is "Input_Re", "Input_Mc", "Input_Nc" ? And why are those input fields assigned in the first place?
    https://docs.unity3d.com/2018.3/Documentation/Manual/script-InputField.html
    On this page you can see the InputField component and it has a OnValueChange UnityEvent - you can use it to connect it to any system you want without referencing the field from inside the script - the script then just gets the event that the value has changed, containing the string value.

    Display Error Message - why is this component on the "DATABASE and UI System" GameObject and not on the "ErrorMessageUI"-GameObject ?

    Seems like I don't understand any decision you made regarding the structure of this application/ game(?)

    Lets dive deeper: What is OOP? Object Oriented Programming, a human understandable structure of programming. Humans think in Objects, House, Door, Table, Dog, Animal, ...
    Now think of the connection of "systems" in the real world.
    If we interact with something, e.g. opening a door - we know how a door works as some kind of interface - but we only know of the door right in front of us, we don't think about the neighbours door or any door on a different continent - so there is no global door manager, where we grab the door from a list, to interact with it - we interact with it once we see it.

    A dog has nothing to do with a door, but humans can interact with both - those two things are not listed in the same container and thats why there shouldn't be a Button Manager in your game. The player can interact with many independent systems but you should not connect those systems because the player can interact with them.

    Instead of grouping by Type: e.g. Buttons / Data / Input / Movement / ...
    You should group by Module: e.g. AI / Options Menu / Player / Airflow Calculations / ...
     
    Last edited: Jun 26, 2020
    AlanMattano and ippdev like this.
  11. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    I was looking for Programming Architecture Configurations
    Canard: data
    If you have a thousand of how you organize them? in the big picture? drawings, nomenclature,
     
  12. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    I don't know the scope of your project, and I'm just an artist with some half school level, half self-taught programming knowledge, but I'd hazard a guess that if you need a 1000 oop classes or component classes, you're doing it wrong. I used components to model behaviour in a way that makes sense to me. E.g. a "weapon" component has everything needed to model typical weapon behaviour, both for player controlled and ai controlled units. The differences between e.g. a gatling gun and a rocket launcher were fully realized with different parameters on the weapon component and assigning different projectile prefabs to spawn when shooting. If I had ever finished that game I may well have ended up with over 1000 prefabs, but those can be managed with folders in the project hierarchy and don't require you to draw up some fancy dependency hierarchy or whatever. I'm not even sure I had a single class that inherits from another class I wrote.

    I had an "entity" component for things that can take damage and generally "exist as a thing" in the gameworld. A building was an entity. An enemy was also an entity, but with a turret component and a weapon component on top, that gets moved around by a driver component and has a gunner component that tells the turret where to aim at and when to trigger a shot from the weapon component. A projectile doesn't care whether it hits a building or an enemy, it just tells it via an interface what damage it does and lets the entity take care of reacting to that damage. A stationary enemy turret would be implemented by taking a tank and removing the driver, in terms of what components I'd use. If I wanted to add behaviour like linking turrets to a powerplant, I'd have added a component for that, that switches off the "gunner" component when the power is off.

    I'm not saying this is what you should do, I'm just trying to answer your question with the reasons why I never felt the need to have 1000 component classes.

    It might boil down to KISS again. I tried to minimize the amount of classes I need to keep track of and implement things in the most straight forward, simple way that intuitively (for me at least) models real-world behaviour without being burdened by overly fancy "architectural considerations". I like to joke that "I like the monolith design pattern", because I'd rather have 1 class with 1000 lines than 10 with 100 each, but that's just me. I find having many classes needlessly confusing, but that's just me.
     
  13. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    If the answer is restart and KISS components. Is what I'm doing.

    I wish less deep details and much more in the general picture.
    Because is important and I do not know how to manage the nomenclature in the big picture of a big project! I'm searching for patterns drawings nomenclature, standards:

    A centralized database? decentralize, standard, canard, or auto stable configuration UI system? I'm going to add much more staff and be able to track and debug. But before that,

    I need a layout a blueprints. Big picture

    Ho, you know? For an airplane, the "Wing Configuration" can be auto stable, canard, tandem, or standard. You do not need more than that. And I was looking for that knowledge: Programming Architecture Configurations.
     
  14. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Stop searching, and implement the absolutely simplest thing that gets the job done.

    You don't. Unity allows you to throw together simple components, without making massive systens,
    You already have a skeleton given by Hannibal_Leo.

    For the purposes of a flight sim, this information is not even necessary in the game. For a basic sim each configuration can be converted into a set of coefficients, which will determine how the plane flies. So, the plane can move on the screen without knowing that it has wings, and without wings being a concept.

    Lots of things can be simplified, or represented with smoke and mirrors or fakery.
     
  15. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    It doesn't quite work this way.

    Most of the time, when people are programming, they're dealing with a program that is bigger than what they can remember and keep in their head. They don't have full understanding of every single thing. Due to being human. People forget stuff, and make mistakes.

    As a result, you often can't really envision entire system in its every detail in the beginning, make a diagram that will cover everything, then write it down as code and have it work. That's what you're trying to do, by the way. YOu can't, because, due to human forgetfulness and tendency to make mistakes, you'll forget a ton of important things, overlook importance of some things, and focus too much on other things, and so on. So you won't have a perfect building plan.

    So, what do people do? They split the whole thing in small bite-sized problems, and have the codebase evolve as needed. There's of course, more one way to do things, but this way is a possibility.

    For your simulator game, you could start with nothing and plan nothing. Then you make a flying plane. Then a need arise to have more complex behavior, a part of existing component becomes bloated with functionatlity, and you split off into a separate class, module, or subsystem via refactoring. The system evolves based on your needs. Then you continue the process until done.

    With this approach you don't really "plan it" in every little detail as you would plan a house, because complexity can be very high.
     
    Ryiah likes this.
  16. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    I add "big" in the title.

    Is what I was doing until now. Is like talking to a wall but I try to make you understand.
    Yes is exactly as you say. All this is ok for a small game but not for a big game. I need to go on. I'm making as a minimum to deliver.

    I'm looking for a flow communication structure that guides me like the church architecture(centralize), a supermarket, the airport architecture, the wasp nest (covid19 time ). because actually my code flow looks similar to this picture everywhere

    How can I do so that I can remember and make data flow less chaotic?

    Hannibal_Leo "there are so many tutorials on this topic, it's really easy"
    I know how to make a static class. I do not know how to manage the naming and position of hundreds. How to use it in the hierarchy so that I can track and remember.

    LOAD SAVE
    It is not as simple as looking for tutorials when the project is big.
    Actually I'm using 3 plugins for saving + default. Each one do specific load savings task After rejecting 5 out. So there are a lot of plugins (20) (3 Charts plugins types for example). I can't take them out because I use them. So I wish to organize my code and integration code in a way that I can remember. Usually, I use drawings for that.

    Can you make a drawing?

    Do I need a Start department that includes all loading methods and delivers them to the components or Do I need to put a load on start in each component? Do I need to make a Load System that talks with a central database or Do I need to let each individual LoadSave plugin system that talks with each...? Do I need to put the save system in each game object or a centralized save system?
    If I find a solution I can apply that to charts, airplanes, etc.

    I have read a lot of books, Follow a lot of videos youtube here unity and courses udemy treehouse and olders in this 10yr but nothing that regroups all the problem in the big picture.

    The only thing at this moment that let me improve is to jump to TDD. My console is clean! But 1 bug at this point is hard to track and spot where is the source of the problem.

    And sorry that I'm repairing myself. Some concepts I need to give time to understand them. Thanks a lot for all your answers.
     
  17. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Do you have a team?
    If your game is BIG and you're alone, the project is likely doomed to die.
    Or you'll spend next 20 years working on it, dwarf fortress style.

    Then you shoudl hire either a tutor, or a programmer or a studio.

    You won't ever remember every single thing in your code. Like I said, you will keep forgetting most of it, and that's normal.

    Typical way of managing classes is to have one file per class, with name that matches the class. The name has to be highly descriptive, so even if you're hit by amnesia tomorrow, you should be able to quickly figure out what is going on in matter of minutes.

    Managing many abstractions and large code bases is a skill that is learned through practice. There isn't really a shortcut, and no matter how many books and articles you read, this is something you need to learn through practice after making many mistakes. Practicing takes years.

    The approach I described will work for most games, including "Big". Because every thing starts with a small demo and new features can be added when needed. Also, approach described by Hannibal_Leo will work the same way with thousands of planes.
     
  18. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    You might learn something useful if you start asking very specific questions. "Do I need a Start department that includes all loading methods and delivers them to the components" is not a question to which a useful answer can be given. Speaking of "dataflow" indicates that you likely read a bunch of books and are currently being distracted by ideas you found in them.

    Useful question is "I have six thousand planes in scene, how do I save them into a file and reload them later?"
     
  19. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    upload_2020-6-27_17-36-28.png

    No connection means that those systems don't know about each other.
    Arrows define the "flow", so the UI does not know about the player, but the player knows about the UI and sends data.

    Which looks like this:
    Code (CSharp):
    1. public class GameUI : MonoBehaviour{
    2.     public void GetPlayerSpeed(float speed){
    3.         // draw the current speed
    4.     }
    5. }
    6.  
    7. public class Player : MonoBehaviour{
    8.     [SerializeField ]GameUI gameUI;
    9.  
    10.     void Update(){
    11.         gameUI.GetPlayerSpeed(currentSpeed);
    12.     }
    13. }
     
    Last edited: Jun 27, 2020
  20. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    Give us a LIST of which components are important. Think about that word important. Which components are needed so that you can have a basic version of a flying plane. Do not yet worry about airfoils or wingspans..just a controller set that allows a graphical representation of a plane to fly. This is where you MUST start. Everything else is superfluous..or more simply..not needed at this point in organizing.
     
  21. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    You don't need the diagram. Make things talk to each other when needed. Diagram will restrict you to specific way of thinking and limit you the long term.

    Updating UI is not player's job. Player should not push data to UI, instead UI should pull data from player. By making player manage UI you're creating a "god class".

    This is very bad due to multiple reasons.

    • You have to specify reference manually. YOu will forget to do that, meaning you're creating a point of possible bug.
    • Player has no reason to know that UI exists.
    • Player shouldn't call methods of UI, because it doesn't need anything in the UI. Instead, UI needs to pull data from the player when needed.
    • The class shouldn't be called GameUI, as it only supposed to display player speed. The proper name would be SpeedDisplay. Or something similar.
    • Traditionally, "Get" methods, return the value in their name as a result. So, normally when you have "something.GetSpeed()", the expected behavior of this method is to return "speed" of "something". Having UI method called GetPlayerSpeed which actually updates UI values is not exactly correct as it goes against common convention.
    So. Here's what I'd do. (untested code, likely with a bunch of typos).

    Code (csharp):
    1.  
    2. [RequireComponent(typeof(UI.Text))]
    3. public class SpeedDisplay: MonoBehavior{
    4.     protected UI.Text textUi = null;
    5.     void OnEnable(){
    6.         textUi = GetComponent<UI.Text>();
    7.         if (!textUi){
    8.             Debug.LogErrorFormat("Coudl not get text component on speed display");
    9.             //throw new System.ArgumentExeption("Could not get text comonent");//might as well throuw
    10.             return;
    11.         }
    12.     }
    13.     void Update(){
    14.         if (!textUI)
    15.             return;
    16.         var player = Player.getPlayerInstance();
    17.         if (!player){
    18.             textUi.text = "N/A";
    19.             return;
    20.         }
    21.         textUi.text = string.Format("{0} m/s", player.getSpeed());
    22.     }
    23. }
    24.  
    25. public class Player: MonoBehavior{
    26.     protected static Player cachedInstance = null;
    27.     public static Player getPlayerInstance(){//this could be a property
    28.         return cachedInstance;
    29.     }
    30.     void OnEnable(){
    31.         //register player as singleton
    32.         if (cachedInstance != null){
    33.             throw new System.ArgumentException(
    34.                 string.Format("Duplicate players found: existing: {0}, new: {1}",
    35.                     cachedInstance, this);
    36.             );
    37.             return;
    38.         }
    39.         cachedInstance = this;
    40.     }
    41.     void OnDisable(){
    42.         //deregister player as singleton.
    43.         if (cachedInstance == null){
    44.             throw new System.ArgumentNullException("cached instance is already null");
    45.         }
    46.         if (cachedInstance != this){
    47.             throw new System.ArgumentException(
    48.                 string.Format(
    49.                     "cached instance does not match current player. instance; {0}, player: {1}",
    50.                     cachedInstance, this
    51.                 )
    52.             );
    53.         }
    54.         cachedInstance = null;
    55.     }
    56.     public float getSpeed(){
    57.         ...
    58.         return ...///
    59.     }
    60. }
    61.  
    Most of this is sanity checking.

    So, key differences.

    • Player class does not know that UI exists. It doesn't need to. updating UI is not responsibility of the player.
    • No manual references need to be specified. Player automatically registers itself as global player, when created. If there's no player, UI simply prints nothing useful.
    • UI widget does not know a thing about the rest of the codebase. It doesn't care about managers, or anything. It only knows that it is supposed to have a text component, and it is supposed to paste player speed into that text component.
    Weak points:
    • getPlayerInstance() Would need to be changed in situation when there are multiple players on the screen.
    • Every UI component would need to check if player is null. Then again, null check can be moved into a base class.
    Strong points:
    • Very easy to add more widgets. I can add trillions of gadgets, widgets, progress bars, numerical displays, and each of them wouldn't affect Player class in any way. Each would be a completely separate system, and each would have a very short implementation - less than a hundred lines of code.
    -------

    Regarding "Then again, null check can be moved into a base class.".

    Lets' say that in addition to SpeedDisplay we create a, I don't know fuel display. Thatw ould result in:
    Code (csharp):
    1.  
    2. [RequireComponent(typeof(UI.Text))]
    3. public class FuelDisplay: MonoBehavior{
    4.     protected UI.Text textUi = null;
    5.     void OnEnable(){
    6.         textUi = GetComponent<UI.Text>();
    7.         if (!textUi){
    8.             Debug.LogErrorFormat("Coudl not get text component on speed display");
    9.             //throw new System.ArgumentExeption("Could not get text comonent");//might as well throuw
    10.             return;
    11.         }
    12.     }
    13.     void Update(){
    14.         if (!textUI)
    15.             return;
    16.         var player = Player.getPlayerInstance();
    17.         if (!player){
    18.             textUi.text = "N/A";
    19.             return;
    20.         }
    21.         textUi.text = string.Format("{0} L", player.getFuel());
    22.     }
    23. }
    24.  
    25.  
    This is bad, because we have a duplicate code, and otherwise this thing is equivalent to SpeedDisplay.

    So. What do we do? We extract the similar functionality into a base class. And then we get:

    Code (csharp):
    1.  
    2. [RequireComponent(typeof(UI.Text))]
    3. public class PlayerTextDisplay: MonoBehavior{
    4.     protected UI.Text textUi = null;
    5.     void OnEnable(){
    6.         textUi = GetComponent<UI.Text>();
    7.         if (!textUi){
    8.             Debug.LogErrorFormat("Coudl not get text component on text display");
    9.             return;
    10.         }
    11.     }
    12.     protected abstract string getDefaultDisplayString();
    13.     protected abstract string getDisplayString(Player player);//Note: player is guaranteed to be valid!
    14.     void Update(){
    15.         if (!textUI)
    16.             return;
    17.         var player = Player.getPlayerInstance();
    18.         if (!player){
    19.             textUi.text =getDefaultDisplayString();
    20.             return;
    21.         }
    22.         textUi.text = getDisplayString(player);
    23.     }
    24. }
    25.  
    26. [RequireComponent(typeof(UI.Text))]
    27. public class SpeedDisplay: PlayerTextDisplay{
    28.     protected override string getDefaultDisplayString(){
    29.         return "N/A";
    30.     }
    31.     protected override string getDisplayString(Player player){
    32.         return string.Format("{0} m/s", player.getSpeed());
    33.     }
    34. }
    35.  
    36. [RequireComponent(typeof(UI.Text))]
    37. public class FuelDisplay: PlayerTextDisplay{
    38.     protected override string getDefaultDisplayString(){
    39.         return "N/A";
    40.     }
    41.     protected override string getDisplayString(Player player){
    42.         return string.Format("{0} L", player.getFuel());
    43.     }
    44. }
    45.  
    At this point I can create thousands of components that will display player specific data as text.
    Each implementation will be extremely short. Likely 8..12 lines of code.

    Player will not change, no matter how many widgets I create.
    Readability is very high. No code duplication, no manual reference, nothing.
    -------
    The reason why UI should pull data from player, and Player shouldn't push data into UI, is because when UI pulls data, then any change in UI only results in a change in UI code and nowhere else. To be specific, the change will happen only in one widget.

    HOwever... when Player pushes data into UI, then UI change will result in changes within UI-related code, and player-related code. Meaning you now have more places to babysit, and more work to do.
     
    Last edited: Jun 27, 2020
    Martin_H likes this.
  22. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Speaking of which, this can be simplified further.
    Code (csharp):
    1.  
    2. [RequireComponent(typeof(UI.Text))]
    3. public class PlayerTextDisplay: MonoBehavior{
    4.     protected UI.Text textUi = null;
    5.     void OnEnable(){
    6.         textUi = GetComponent<UI.Text>();
    7.         if (!textUi){
    8.             Debug.LogErrorFormat("Coudl not get text component on text display");
    9.             return;
    10.         }
    11.     }
    12.     protected virtual abstract string getDefaultDisplayString(){
    13.         return "N/A";
    14.     }
    15.     protected abstract string getDisplayString(Player player);//Note: player is guaranteed to be valid!
    16.     void Update(){
    17.         if (!textUI)
    18.             return;
    19.         var player = Player.getPlayerInstance();
    20.         if (!player){
    21.             textUi.text =getDefaultDisplayString();
    22.             return;
    23.         }
    24.         textUi.text = getDisplayString(player);
    25.     }
    26. }
    27.  
    28. [RequireComponent(typeof(UI.Text))]
    29. public class SpeedDisplay: PlayerTextDisplay{
    30.     protected override string getDisplayString(Player player){
    31.         return string.Format("{0} m/s", player.getSpeed());
    32.     }
    33. }
    34.  
    35. [RequireComponent(typeof(UI.Text))]
    36. public class FuelDisplay: MonoBehavior{
    37.     protected override string getDisplayString(Player player){
    38.         return string.Format("{0} L", player.getFuel());
    39.     }
    40. }
    41.  
    Given that "N/A" is likely to be an appropriate enough in case there's no player, we can move it into base class too, and then we get.... six lines of code per new UI component.

    Code (csharp):
    1.  
    2. [RequireComponent(typeof(UI.Text))]
    3. public class SpeedDisplay: PlayerTextDisplay{
    4.     protected override string getDisplayString(Player player){
    5.         return string.Format("{0} m/s", player.getSpeed());
    6.     }
    7. }
    8.  
     
    Last edited: Jun 27, 2020
  23. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    @neginfinity: Thanks, that was quite educational! What's the benefit of making your e.g. 20 ui widgets into 20 different classes, instead of 1 class with an enum for which stat to display, and a long switch statement with all the different cases in one place? That's what I'd have done because it's less lines of total code (If I'm not mistaken, and only slightly less), and I like having that kind of similar code in one place because if I later want to change the way the stats are displayed in the UI, I'll likely want to have all the related lines of code in the same place with less boilerplate code between the lines I want to edit. And I'd get an easy dropdown menu in the inspector and could easily change what component displays what.

    And another question: Would you still approach this the same way if you were to make a complex inventory UI with lots of drag and drop and contextual actions? Lets say something like Jagged Alliance 2:


    Would you keep one data representation of items and different inventories that is completely unaware of UI code, and can perform logic checks for "can this go there?" etc.? And would the UI then use that interface to e.g. highlight valid drag'n'drop targets, compatible items, etc. and notify the inventory data of attempted state changes but not concern intself with the actual logic of what can go where? I'm guessing that's the right answer compared to have the checking code in the UI and let the inventory data just be data without much function, because later you might want other systems like enemy AI attempt to change inventory states and then the common interface would be used by those as well and the logic checks stay in one place? When you even later introduce different rulesets for what can change what in the inventory, would you then start to make the inventory aware of the other actors for which different inventory-logic rules apply, or would that logic move into the different actors? Or would you decide that case by case based on which logical cause a rule has? E.g. an animal would check only for food items to pick up and eat, so that code would be in the animal class, but when it's a limitation that has nothing to do with the actor (e.g. the item is shown in the sector inventory but can't be reached physically), then that code would go elsewhere?
     
  24. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    The example I provided is not the only possible approach, and we're approaching the point where we're talking about matter of preferences. Basically, it is true that "in some cases it makes sense to have a configurable widget". How that widget should be configurable is up to the developer. Regarding enum.

    First, you won't have won't have all the code in one place. You'll have two places where something has to be defined - one spot is where you have an enum, and the other spot where you have a switch for that enum. In "tiny class" scenario, you have one place where something is happening.

    Second, enum in concept of widgets makes most sense if the type of displayed value changes at runtime. For example, at one point you have the widget display speed, and then it switches to "fuel". Does this happen? If it does, it makes sense to have an enum. However, dropbox convenience is a strong argument in favor of enum as well.

    Lastly, there may be a tiny imperceptible difference in case of enum compared to virtual call. It can go either way, depending on implementation and it is not something that is worth worrying about.

    In general, for configurable widget, if there's a lot of different types of data, I could try to turn it into something akin to interpolated string. I.e. a widget would have a "display string" field, and the string could be "{playerSpeed} m/s". This would allow greater flexibility.

    However, "enum vs formatString vs many small classes" appraoches fizzbuzz problem territory. Meaning you can do it either way, and there won't be an obvious significant benefit.

    Basically consider following scenarios:
    • Display type changes at runtime: Probably enum.
    • Huge number of of places that display formatted data but pull it from the same source: Probably custom string formatting.
    • Lots of textfields displayed in bazillion places but in exact same way: probably enum. (Can't make a typo in string format)
    • Display data integrated freeform into a text of another field: Probably custom string formatting.
    Also the number of lines won't significantly differ. My example in the end had six lines (mostly because I don't remember if requirescomponent is taken into account when it is inherited).
    With switch case you'll need one line per enum, and two to four per each switch case branch.
    Dropdown menu is a good argument, though.

    Let's see... you're showing I think pause screen from Ja2.

    We have drag and drop in place, that calls for "can accept/reject drag" responses for multiple gui elements (all inventory slots, etc), so it is going to be more complex and a bit messier.
    Mercneary info panel, likely would have some sort parent which determines which merc we're talking to, and data is being pulled from that panel.instead of directly from the mercenary.
    Read only widgets would act in the way comparable to the "SpeedWidget" example from earlier. Meaning they only poll for some value and do nothing else.
    Inventory slots could go either way, as they participate in drag and drop operations. They could be be subclasses of a common base class which implements drag and drop response, OR they could be instances of the same class, meaning there could actually be an enum in this case determining what kind of slot it is (body equipment, backpack, world item), and if it is slot from the world, which cell it is referring to.

    The grid and backpack screen could be created dynamically, or placed by hand while typing which index for each, could be done either way. It isn't a thousand inventory slots.
    Paged view on the right side actually would be implementing model view approach....

    That's the rough idea. However, that's mostly details and busywork.
    Yes, I would keep inventory, characters, world and items unaware of existence of user interface. So, classes like Soldier, Map, Item would not know that UI exists, and would not access it in any way. They would be providing methods for manipulation, and UI would be calling those.

    I believe this falls under model-view(-controller) architecture (see: https://en.wikipedia.org/wiki/Model–view–controller )
    -----------

    Anyway, the main point of my response was to demonstrate one possible way to avoid the nasty situation where Player is dependent on GameUI. The example I provided is one possible way to reduce the amount of interdependencies and simplify the code.
     
    Last edited: Jun 27, 2020
    Martin_H likes this.
  25. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    You have an update method in your UI, if you have 30 UI fields, every one of them gets updated every frame and maybe does some string combining or other calculations ... thats why in my example the player (or other components) push the information, because they know when the information changed.
    GameUI also does collect data and send it to subsystems like Slider / Text whatever - those can be modular and just listeners to onSpeedValueChanged - those components, the UI included, do not need about the player - they could be getting the speed value from absolutely any system.
    Lets say you make a cutscene, then the speed value will be set from whatever is controlling the cutscene, you can also reuse it inside the hangar or anywhere because the UI itself just draws data.
    Sure it would be better if all of this was way more modular and more disconnected from each other but then, the player would need to know about hundreds of individual UI components instead of just one where he can push data once it is updated.
    The GameUI acts like some kind of API, all communication to the UI works through this one component and gets distributed from there to other UI-Subsystems (UISpeedDrawer, UIHealthDrawer, UIFuelDrawer) .
    In your example, the UI would need to know about the Player, about his Weapons, about the Enemy Spawner to display the number of enemies, about the MiniMap drawer, about the PauseMenu and so on - the UI would be connected to everything and once you change any of those gameplay systems, you would have to change your UI too.

    So what is better?
    The UI knowing about 200 individual systems
    VS
    200 individual systems knowing about one API/Interface to draw their data on the UI.
    Because there is only one UI in the game, it could be static and not a component at all - just providing a bunch of static delegates, so a UIComponent (e.g. UIFuelDrawer) can listen to onFuelChanged(), if no one listens, nothing happens (no error).

    Of course all of this is oversimplified and I don't feel like writing his entire game nor sharing actual code from my project.
    The thing is - you don't want to interconnect your systems. If you have the Hangar and the Player, you don't want to directly connect the HangarWeaponData to the PlayerWeapon - both of them will communicate only via the AircraftData (in the Save File). If all of this would happen to be in one scene, the Hangar could directly communicate to the Player and the Player to the Hangar if needed, but none of the subsystems of one big system would communicate to the subsystems of the other big system.
    Otherwise everything is interconnected and you always have to change code on two parts when you actually want to change things on one part.
    Changing one subsystem should never affect anything outside its own overaching system - e.g. changing PlayerWeapon should only affect PlayerWeapon and the Player, not the UI, not the Hangar, not the Enemies.

    Code (CSharp):
    1. /// PlayerWeapon   -----------------------> static UIHandler -----------------------> UI-Drawer(Compoent)
    2. /// (Does not know                            (does not know                                (does not know
    3. ///  about UI-Drawer)                          about anything)                               about PlayerWeapon)
    4. /// UIHandler.SetSpeed(speed);            static void SetSpeed(float speed){           UIHandler.onSpeedChanged += DrawSpeed;
    5. ///                                         onSpeedChanged?.Invoke(speed);             DrawSpeed(float speed){
    6. ///                                       }                                                speedUIText.text = $"{speed} m/s"
    7. ///                                                                                        speedometer.rotation = Quaternion.Euler(0,0,180*speed);
    8. ///                                                                                    }
     
    Last edited: Jun 28, 2020
  26. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Oh. So you weren't the OP.

    Your computer, in one second, can perform amount of calculation that would take for a single human 15 thousand years to finish.

    If you were targeting Z80 with 7 Mhz processor, you'd have a point about 30 fields being too much for performance.

    The problem with the example you gave was that you weren't using events and subscribers, but rather accessed a monolithic "GameUI" object which should not be a player's responsibility. That requires player to be aware of inner workings of UI. Then there's a matter of broken naming convention.

    In a situation where you're concerned about performance and do not want to recalculate the data every frame, the solution is to mark data as dirty upon change, and/or fire an event indicating that an update is necessary, to which all UI controls would subscribe. This is how desktop applications work, by the way. They mark regions needing repainting as dirty. This is also similar tosignal/slot architecture to UI. "Marking dirty" also works with situation where multiple updates happen within single frame. UI only needs to be updated once, no matter how many times a value has changed.

    However, there is a good reason to forego this system entirely, because in a realtime game data most likely changes every frame anyway.

    In my scenario, the player needs to know NOTHING. That's the point.

    Yes. A major change in player would result cascade change in UI. However.

    The UI does not know about 200 individual subsystems.
    Each UI element knows about only one thing it puills data from. Which is a single function or a single property. Or a single variable.
    For example, you can delete every method from Player except getSpeed() and SpeedWidget will continue to work as if nothing changed. It is not a monolithic object. Player acts as API. And data sources are too small to be a subsystem.

    That's not what your example does. Your example is one system that needs to keeb track of 200 individual objects and be aware of inner working of each and every one of them. Because that's what happens when player has to boss around UI.

    That is false. There's more than one user interface, likely multiople windows, and what's more UI will change more often than underlying data, to match new requirements of design. Your designer can scrap UI tomorrow and makes it animator based, for example. Or add an extra window. Or additional views.
    Speaking of which, with this being a video game, a window might not even exist all the time.

    The thing is, you will end up interconnecting your systems no matter what you do, because they have to communicate. The point of connection will always be there, and when you change connection point, code will break somewhere. It is unavoidable.

    I see approach with manual link to GameUI as bad, because change has to be made while keeping UI in mind. Meaning player, while dealing with logic has to be aware of existence of a button somewhere that can be broken. And that makes Player and UI tightly connected.

    You could make it different via broadcast event, subscribers and so on. But.
    That's not what your example did. Your example had direct access to GameUI specified as inspector reference.

    In order for a change in a subsystem to always affect nothing, the system has to do nothing and interact with nothing. If you add a new parameter to a player weapon, or remove an existing one, that will have to be reflected in UI, in the hangar, may affect enemies and touch multiple subsystems as well.

    No matter what you do the moment systems communicate you create a communication interface, and the moment the communication interface changes, the rest of the code will have to be adjusted to it. If it is a method, then it will be the method signature. If it is an event, it will be event signature. Even if you use some sort of message passing algorithm that uses string keys to pass data around, then choice of the string key would be the communication interface.

    --------------

    Lastly, one more thing to consider.

    Consider scenario where after designing a system where Player pushes data, you add now two more Players. In my approach, I would need to change only one point - the function that returns current player, and make it multi-player aware. Which won't take long. In a player centric scenario you'll be looking at massive sweeping changes in the codebase and bunch of bugs too.

    The way I see it, both UI and Player are optional and may be missing or exist in multiple instances. Player may be missing, it can be more than one or it can quickly switch to a completely different object.

    Additionally, take a look at articles about Model-View-Controller architecture. This thing was created for a reason.
     
    Last edited: Jun 28, 2020
    Martin_H likes this.
  27. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Finally a drawing!
    Yes, I try that layout already but as Neginfinity points out it was not possible to maintain it between boundaries. So it finishes up interconnected.

    Here a video that a friend point out
     
    Last edited: Jun 28, 2020
  28. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    10y and code since U4.2

    I need to find a solution to this. Drawings for example helps me to remamber.
     
    Last edited: Jun 28, 2020
  29. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    And regaridng this example.

    PlayerWeapon has to be aware of UIHandler, "speed" being a concept, and signature of method name.
    UIDrawer has to be aware of UIHandler, speed being a concept, signature of method being broadcast.

    For example, the moment you switch Speed from float to Vector3, all three will break, and every component that displays speed would be affected in exact same way as it would with approach I posted earlier.

    What's more, if speed changes multiple times per frame, then DrawSpeed will be fired multiple times.
    In contrast, in a situation where text is pulled every frame in Update(), Update will only happen once per frame.

    in a situation where an expensive calcualtion is performed, the value can be cached. Meaning the script will check if it did not change since the last frame, and only recalculate it if needed.

    Basically, in essence you add middleman whose utility is.... dubious, as in essence the only thing it does is forwarding calls. There are scenarios where a forwarder would be of use, but player speed is not the one.

    --------

    Also.... let's say the speed didn't change for the last 5 minutes, and we've spawned a new speed display window. If the notification is only fired when a change occurs, then the window will display invalid information. If notification is fired periodically, then it is no different from Update(). If the window appearance triggers player to re-send notification, then the systems are coupled, and window is aware of inner working of player.
     
    Last edited: Jun 28, 2020
  30. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    There's no solution.

    Everybody forgets things. Codebases change all the time. People work on programs bigger than they can remember all the time.

    This is business as usual. It is normal.

    It is not a good idea to try to memorize structure of the program, drawings or not, because the structure is not set in stone and is subject to change. Meaning you will only waste time, as when the requirements change, program structure may change along with it, and things you memorized will become obsolete.

    Instead, write code in a manner that will allow you to quickly figure out what is going on at any given point of code, even if you see it for the first time. That means descriptive names, small classes, small functions and so on.

    -------

    Anyway, I feel like I spent too much time here, so I'm out. The OP received enough advice as far as I can tell.

    Have fun.
     
    Martin_H likes this.
  31. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    Thats not true - you just have to pass the correct parameter to the method --> UIHandler.GetSpeed(v3Speed.magnitude);

    The player decides when to update the speed - only the player knows when the update is important - the logic to avoid multiple calls per frame can be inside the player itself or in the UIDrawer, it is just very unlikely that the speed changes that often - also UIHandler.SetSpeed() can be called from the players update loop or anywhere, you don't have to implement this into the Setter (set{}) of the speedValue Field.

    All this data has to be public (get atleast) to anyone which may not be desired.


    The thing is - when you change the playerWeapon, you want to test it, immediately, you don't want to touch the UI or Enemy Code just to test your change on playerWeapon - it will most likely be reflected on the UI at some point, but then you work on the UI and only on the UI. Splitting an application into modules/parts/systems helps debugging these modules and you don't have to rewrite half of your application just to test one small feature.
    Changing PlayerSpeed to Vector3 for example - you don't have to touch UI Code, following the pattern I shared, you just have to pass the magnitude when you call it from the Player - but actually you don't have to call it at all, the UI can still exist in the game and won't throw an exception, the listener just never gets a signal and everything still works and is still running, so you can debug.

    Also when you want to test the UI, in your example, you need the Player (and probably all other systems too) - in my example, you can just create a component TestMyUI : MonoBehaviour, which calls SetSpeed().
    You can debug the entire UI in an empty scene, you can even benchmark the performance when SetSpeed() is called 50 times per frame.

    Could you elaborate? Or are you talking about my first example - this one was incomplete and not well thought out, thats true.

    Multiple Windows ... uhm it is still the same UI ... the only exception to this would be a splitscreen game, then the UIHandler can't be static for obvious reasons, but it could be "easily" converted to a scriptableObject and all those problems are solved - but for the sake of simplicity, if it is not a splitscreen game, I'd go with a static approach.
    The views/windows/whatever are independent, they just listen to the events, every one of them can subscribe individually to whatever it needs - the UIFuelDrawer won't listen to onSpeedChanged, but only to onFuelChanged.

    If those players are on the same device/computer - see the "splitscreen" thing I talked above.
    If it is just multiplayer - well then data has to be pushed only by the currently active player
    if(networkThingy.id == thisClient.id) UIHandler.SetSpeed(playerSpeed);

    but there are many options to solve this - if it is really a network game, you usually have different logic in player/enemies/everything, players won't listen for input if they are not owned by the client playing the game, enemies will only be controlled by the host and so on - so the logic of pushing data to the UI also only happens, if the player listens for inputs.

    http://rygo6.github.io/2016/08/21/Forget-About-MVC-In-Unity.html
    https://forum.unity.com/threads/mvc-on-unity.683446/
    Just random links I found, I could go on and on about this.
    As I said earlier: the problem with programming in Unity is, that they already made all the complex things for you, you don't have to implement a factory pattern or MVC or any other complex system, because you would create it twice.
    Unity respeced MVC in the UnityEditor (afaik), so you shouldn't implement a second layer of MVC in your code.
    The same is true for the SetDirty thing you mentioned - they already did this, no need to code it a second time.
     
  32. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    "When speed representation changes from scalar to vector"

    That means the player has to be aware of inner working of UI. Bad.

    Do you intend to keep all members of Player private/protected?
    I don't see the point. Some data has to be public.

    This argument is unsound. If weapon has a breaking change, it will break both approaches.
    In situation I presented you'll get a single compile error per component type, and that can be very quickly addressed or commented out. It is not a complex task. It would be few lines of code.

    Your application is more than 10 lines of code, I believe, as that would be roughly amount of fixes.

    No, you DO have to touch UI code if your speed changed to 3-dimensioanl Velocity.
    Additionally, this is no different from adjusting SpeedDisplay from printing float to printing a vector.
    Same fix, in different spot. In two spots in your scenario, because you'll need to update UI and the Forwarder.

    If you insist on TDD, then normal approach is to provide a fake datasource, which is a player...

    This is nothign new when prefabs are a thing.

    In your example, UI is a monolith. You get the whole thing or nothing.
    I can pull individual components out of it. Single label if needed. They're independent from each other. They can be used anywhere too.

    Not seeing a point in using scriptable objects there. They're data storages.

    Which is creates god classes or objects with blurred responsibilities.
    I don't see a reason to have player as a different class compared to enemy, and I don't see a reason to have UI code in player which also checks if it is receiving input. This creates way too many landmines for my liking. (You introduce a spectator mode and get a major screwup, because input is no longer being received...)

    I'm not going to read RANDOM links.

    Also, MVC is not complex, and unity does not implement it in regard of the games.

    For example:
    You aren't dealing with UnityEditor, but with UnityEngine in your project.
    SetDirty only exists in editor namespace and not at play time.

    And besides, I can take your own words and send them back at you.
    If unity implemented complex things for you, then why are you making Facades with broadcasters in your code?
     
  33. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    How does this usually work in all kinds of programming?
    You have a module - some part of your game or application, lets say the Hangar - you can then theoretically create an assembly for this one part of your programm and have everything internal except for a few classes that you really want as interface to your assembly = API.
    So most of the stuff can't even be seen from outside your module and works completely on its own.

    When you create your UI or your Player you always want to think about which data you want to be public and which you want to be private.

    upload_2020-6-28_7-32-32.png

    No Subsystem can directly communicate to any other subsystem but they can communicate to the overarching Interfaces of other systems.
    So the weapon selects a target and the LockOnCrosshair needs to know about it in order to move the crosshair to the enemy thats selected by the weapon.
    weapon can't talk to LockOnCrosshair directly, so it can only tell it the Interface/API of the UI and the UI can then communicate it to the LockOnCrosshair.

    This way, no subsystem can ever affect any other subsystem of another system - they are encapsulated - one of the most important things in OOP. Encapsulation.
    If the UI Subsystems do not exist, the UI Interface ("UI" in the diagram) should still be working, so you can take the player and put it into an empty scene and everything still works.
    Or take the UI into an empty scene and it still works.
    Or take the Enemy into an empty scene and it still works.

    Thats encapsulation - you can take anything, put it into an empty scene and it works, because it does not depend on the existance of other things. So you can create each part of your game in isolation. If the UI works, it works, you never have to touch it again - but when you have to (to display more information, air pressure for example) you can do so in an empty scene without anything else affecting your debugging process.

    The interfaces are usually quite "rigid", you define them once and then stick to them. So when you design an application, the interfaces are defined first and then each team works on their module, communicating via those interfaces without ever breaking or any further connection.
    Otherwise big teams wouldn't be able to work on the same software at all. And for solo-devs the big advantage is that you can always debug, alter or refactor a single module without having to worry about all the other modules.

    Lets say there is an error in your UI - then you only debug your UI, you write a small test script that sends data to the UI and you can then debug it. Once it is done, you load the gameScene and because you are using an interface/API, the bug is now gone in your gameScene too.
    Thats why seperation / encapsulation is important. Disconnect your systems, define how they are allowed to communicate.
     

    Attached Files:

    Last edited: Jun 28, 2020
  34. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,571
    Either way, I'm done here. Have fun.
     
  35. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    Watch 59 seconds of the video at the given timestamp:


    To split my systems - unity can't implement this because they don't know what my code will look like, it could be Flappy Birds or World of Warcraft.

    Not going to answer everything here because as you already mentioned this is quite time consuming.
     
  36. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    I'm reading with big attention! Thanks a lot for your time put here.
     
  37. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    Thanks a lot for your in-depth reply! You raise some very good points and it is very encouraging for me to see that I wasn't too far off.

    Regarding performance - if they haven't improved a lot of things yet - I wouldn't recommend changing lots of UI components every frame, but you could easily add a check for "does this need updating" in the UI widgets. Pulling the values every frame from the player wasn't the problem, the garbage from the string operations created noticable micro-stutters (I fixed that by pre-allocating all possible number strings that I needed o_0), and when you update one UI element, iirc the UI mesh for every element under a UICanvas gets rebuilt and that was surprisingly costly. They may have fixed all of this (I'd hope so), but my experience was that performance considerations for UI code matter.
    Of course, "profile first in a build before you optimize" still applies...

    I can understand that, however you can be sure that a lot of people will have learned something from your posts here. Thank you!
     
  38. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    When the Hierarchy is big, I use a shortcut game object in the Hierarchy. By drag and drop a game object in the inspector.

    USE: you click the game object in the inspector and the linked game object will be highlighted.

    Sometimes instead of splitting logic in two different places in the Hierarchy, I make a shortcut to the game object that contains all the logic. In this way, there is just one central point of control for that functionality.


    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [AddComponentMenu("Hierarchy/Inspector Shortcut")]
    4. public class Shortcut : MonoBehaviour
    5. {
    6.     public bool disableAtStart = false;
    7.  
    8.     [Space][Header("DRAG & DROP game object shortcut")]
    9.     [Space(25)]
    10.     [Space]
    11.  
    12.     // PUBLIC
    13.     [Tooltip("Drag and drop the game object to point as a single source of control")]
    14.     public GameObject gotoSource;
    15.  
    16.  
    17.  
    18.     private void Start()
    19.     {
    20.      
    21.         if (disableAtStart)
    22.             Destroy(gameObject);
    23.         else
    24.             Destroy(this);
    25.     }
    26. }
     
    Last edited: Jun 29, 2020
  39. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    Logic in the Hierarchy/Scene? The scene should only contain the logic relevant to the scene, everything else should be handled in the specific prefabs.
    The scene is basically your game world / one level or the menu scene - you don't want to add anything here thats not relevant. But it seems like no one listens to what I wrote here, because this should already be clear after all I've said (put things where they belong to, connect only what you have to connect, make prefabs that can work in an empty scene ... and so on)
     
    AlanMattano and JoNax97 like this.
  40. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    I listen but still processing in my head how to put it in practice. "make prefabs that can work in an empty scene ... and so on)". It is not simple. I use a few (3 or 4) shortcuts because my hierarchy is very big. And all game objects (hundreds) are important (are there for a reason and all are different). I use prefabs a lot when I can.

    "things where they belong to,"

    This is sometimes difficult in a big project. At first, I build up this scene putting the logic into the button or chart (where they belong to). But it was a nightmare scrolling up and down inside the canvas hierarchy and finding buttons was difficult (including in a 4K monitor). So I end up making a centralized system where I put the logic. Much less scrolling but more connections pop up. The advantage is that connections are closer to each other (one system is closer to another system). And the Hierarchy increase in number just 5%~10% more.
    I'm still reading and trying to put it in practice.
     
  41. John_Leorid

    John_Leorid

    Joined:
    Nov 5, 2012
    Posts:
    651
    Maybe you want to make a screenshot of your UI, so we can see some of the Buttons
     
  42. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    3,853
    The Inspector is the developers and their designer's powerful friend. If you make a public reference in the class to other classes and have a number of growing scenes and need specific base/core prefabs in scenes then drop those into a new Scene those slots will be empty in Edit mode. You then click the radio button next to the empty slot and it hilites the Hierarchy object which you can then drag and drop. This avoids having to write editor scripts to do this which will fire off false linking errors in the Console while you are setting a new scene up, avoids a long list in Start or Awake or OnEnable that will cause burps while the iteration down a Parent with many sub and sub-sub child Hierarchies gets completed with GetComponent and GetComponentInChildren fire off going into Play mode. If I can drop my GyroCycle prefab with 5 classes and one WeaponCannon child class on the child weapon in a new Scene and the UI which has dozens of sub and sub-sub Hierarchies and just go down the empty slots, click the radio button and in short time link up the core of the scene functionality, keep my scripts from becoming overly verbose and allow my designer to do the same thing then I say damn to the arbitrary rules dictated by folks whose coding habits were not developed in the Unity environment but dictated in compsci classes or brought over from Windows application dev..

    I do similar with GameObject, Transform or Text etc. references except in this case you are not presented with a direct hilited reference for drag and drop but the search window with multiple choice. If I name the Textfields or GameObject in the public reference fields to match the object I can then just type in the name, often just the first letter or two and zero in rapidly on exactly which object I am seeking to link with a simple click.
     
    Last edited: Jul 1, 2020
  43. Martin_H

    Martin_H

    Joined:
    Jul 11, 2015
    Posts:
    4,436
    Not sure if this works for the scene hierarchy too, but for the project hierarchy I used to set my Unity Editor UI up so that I have 3 small tabs with the hierarchy next to each other, so that I can scroll them each to different positions. That makes draging stuff from folder to folder a lot easier.
     
    AlanMattano likes this.
  44. SenseEater

    SenseEater

    Joined:
    Nov 28, 2014
    Posts:
    84
    At the risk of grossly generalizing perhaps, a data oriented design based approach in game dev is generally architecturally & performance wise , most efficient.

    Now with DOD , you don't have to push yourself into using all the fancy DOTS stuff yet in preview and all. But ECS in particular is a great way to have architectural simplicity when solving problems at scale in your game. It greatly helps to achieve true modularity of systems in place with absolute separation of concerns enforced to the bare bones of your code & logic.

    That aside , keep it all bare minimum & simple. As someone said, maybe having a flow graph defined beforehand perhaps is not best idea where you would be forcing yourself into those interactions rather when you really need those interactions.
     
    Ryiah and Martin_H like this.
  45. SenseEater

    SenseEater

    Joined:
    Nov 28, 2014
    Posts:
    84
    It's also important to recognize that your atomic modules shouldn't be ideally talking to each other directly. This is what generally paves way to dependency hell. That kind of interaction should be generally composed on higher level using logic graphs like A Finite State Machine , Behavior Tree , etc.

    Someone mentioned in comments something along the lines of 'GetState' pattern to read state of modules and passing those buffers to target modules. That's sane way to go about resolving your dependency. On broader level dependencies are relationship which can be Read or Write or Both. Its important to identify the relationships in your logic and then design your code around it.

    Here is a talk quite insightful talk that dissects unit/module/entity interaction on high level ( its relevant in non ECS context as much ) :
     
    Ryiah, greengremline and AlanMattano like this.
  46. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Last edited: Jul 14, 2020
  47. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    How to Find Reference in Scene?
    Finding one script reference in the scene hierarchy is difficult. Each time the editor shows a lot of references.
    Is there a script or a tool?


    FindScriptInHerarchy-1.jpg
     
    Last edited: Aug 3, 2020
  48. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,190
    AlanMattano likes this.
  49. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Jason Weimann video about Design Patterns for game devs

     
  50. MadeFromPolygons

    MadeFromPolygons

    Joined:
    Oct 5, 2013
    Posts:
    3,982
    pl
    please don't necro a 2-3 year old post just to ask a completely different question

    Make a new thread, and read the rules.

    Also your question makes absolutely no sense. You use get component when you need to get (and ideally cache) a reference to a component. You use events for, well, events. They are not the same thing at all.
     
    Ryiah likes this.
Thread Status:
Not open for further replies.