Search Unity

Advice practice for scripting

Discussion in 'Scripting' started by jrans, Oct 21, 2016.

  1. jrans

    jrans

    Joined:
    Oct 11, 2016
    Posts:
    1
    Hi,
    I'm a new developer in the world of Unity and i want to ask you advice in practice of scripting.
    In Flash or Haxe, i have habits to write everything... button creation, listeners, animations... Lot of pain but a total control on how my code work and how is made.
    With Unity we have a great IDE but if i write one script to control all gameobjets in current scene, it's that a correct way ?
    Little example : i have a Canvas for my Menu UI. Great, i've created all of stuff, butto, background, slider in IDE and that's cool. But now i must script this to work correctly. Do i create a script for each button and put it like a component or one single script to rules them all is a good practice ?
    And if i choose the second solution, how i can access to a button ?
    like that: http://pastebin.com/CBPQuA1h

    Thanks for your help !
     
  2. Kalladystine

    Kalladystine

    Joined:
    Jan 12, 2015
    Posts:
    227
    The question is really open ended and there will be a lot of differing opinions.
    If you're used to controlling everything but want to use Unity, I'd say you might want to look into ECS patterns (Entity-Component-System). There are a couple of them made specifically for Unity (Entitas is the leading example as far as I know).

    If you just want your UI to work, look into Unity UI tutorials (from the Learn section, but also from Unite talks - check Unity channel on youtube). You could just hook the events to your functions using Unity Events, but your question is not specific enough (at least for me) on what you want - general solution or an answer to specific problem.
     
  3. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    EDIT: This is totally overkill, but once I got about halfway through I decided to just take it all the way so I'll have something to point to later when questions like these come up.

    Something related to this subject comes up about once a week. Most recently, this post by Baste was pretty great, and this video on composition by BoredMormon. For general Unity information, definitely check out the Unity Learn tutorials, which are all awesome.

    Here's my take on the situation- a lot of this you might already know, or have different opinions on, but I'm including it all for completeness' sake. First, GameObjects are only containers, so the components inside are really what matter. Unity divides things up into Behaviours, and every unique behaviour that you may want to re-use and apply in different ways depending on the object in question needs to be its own component script. A "Transform" component is in charge of only the positional information like coordinates, rotation, and scale, while an "Image" component is in charge of only referencing an image file and choosing the options for how that image is displayed. GameObjects are only containers which you fill with components.

    If you want a 3D Model displayed, you add a MeshFilter component, if you want it to move you add an Animator component, if you want the physics engine to keep track of it and respond to it you add a Rigidbody component, and if you want it to occupy physical space in the physics engine you add a Collider component. Keep all of this in mind as you make your own scripts. Give each script only the information necessary to do its job, and try to keep things at the lowest level of dependency and management scope as you can without having to repeat yourself. Inheritance can be helpful, but usually only in the context of multiple components that have the same behaviour type and might need to be swapped out with eachother- like two different scripts that can handle input for moving a character around, but handle it differently.

    If you require a manager type object, put it only at the level that it needs to be in order to do its job successfully. A character object doesn't need to maintain references to all of the characters in the scene, that's a job for a kind of "character manager", but keep in mind that the character manager shouldn't directly control movements for any of the character types, because they should handle that themselves. At best, it should just keep a list of all of the characters it manages and handle construction, deconstruction, and broadcasting "requests" to the relevant ones when something happens.

    A way that I prefer is that, for example, in order for an enemy to send a kind of broadcast to its allies, you should make an event for that in the enemy unit's script like "ShoutForHelp" and have the manager tie into that event when it creates the unit. That way, it can choose how to interpret and handle that event without leaving any sort of management control in the hands of individual units, because the alternative is for the unit to have direct knowledge of the manager (or at least its interface, which is just as bad sometimes), or for the unit to interact directly with other units and everyone keeping complex lists of their own allies, etc... Craziness.

    There are many ways you can handle managers and getting references to managers when needed. As an example in my own project, I have a script called UILocalization that is attached to Text objects and overwrites their text data with the localization equivalent, depending on the current language and whether localization is turned on. Assuming that I have a manager object in the scene that handles all of the localizations as a kind of database (I actually have five, but let's pretend it's one for a moment), I somehow need to get a request from each UILocalization script back to the manager object, so it knows what it needs to fill the text data with, right?

    This isn't the kind of manager that talked about for unit creation/destruction, but rather a kind of database manager that exists to be referenced, so we need to find ways to reference it and get the information we need.

    PUBLIC COMPONENT REFERENCE
    First, I can make a public field for the "LocalizationManager" component in my UI script, which puts a spot there in the inspector for that script where I can drag the LocalizationManager object into it. In the UI script's code, I just use that object reference to access functions and properties in the LocalizationManager, no muss and no fuss, but I'll need to drag that LocalizationManager object into that reference slot every single time I attach my UILocalization component to a Text object, and that gets old fast!

    FIND
    Second, you can use Find, FindWithTag, or FindGameObjectWithTag to locate a specific GameObject (the container) with a given name or tag on it. This is incredibly slow because it iterates over every object in the entire scene, and it should really be avoided.

    GETCOMPONENT
    Third, you can use some form of GetComponent, GetComponentInChildren, GetComponentInParent, which searches in the current object's hierarchy for components matching the given type. This is a faster and safer bet than the Find variations, but you'll need to know that the component you're looking for is in the same hierarchy- is attached to the same GameObject, is attached to a child of the current GameObject, or is attached to a parent of the current GameObject. If I put the LocalizationManager in my UI canvas root (not a bad idea), then calling GetComponentInParent in the Start function on my UILocalization script will work wonderfully- it'll recursively check the parent and then parent of parent and then parent of parent of parent until it finds the component it's looking for attached to one of them.

    SINGLETON
    Fourth, you can use a singleton pattern, which means that the LocalizationManager maintains a static reference to itself. Typically, when it's created, a singleton will assign its own component to a static reference, and then if any other components of the same type are created later, they see that the reference has already been assigned and then destroy themselves rather than overwriting it. There can be only one.

    The benefits of this are ease of implementation and that you can reference the LocalizationManager object from anywhere, at any time, without doing any of the previous methods to find it first- just use LocalizationManager.Instance and you have it. The drawbacks are subtle at first, but compared to most of the alternatives using C# and Unity, essentially boil down to ease breeds laziness. People have a tendency to go overboard and use singletons in situations where they aren't really necessary, and because there can only be one singleton for a component type this can make extensibility more difficult. A singleton is, in a lot of ways, not really a component anymore, so every time you use one you essentially put a dent in the composition design.

    EVENT/MESSAGE SYSTEM
    My personal favourite, this makes coupling a non-issue, because instead of being coupled to eachother, components become coupled only to the event manager and the various events being used. In my current setup, I use a variation on Prime31s MessageKit repo in order to create and broadcast custom classes as "messages". Any number of different components can listen for a given message, meaning they react to them independently and don't need to really understand anything except what they're supposed to do when they receive that type pf message.

    Back to the Localization example, this gets slightly tricky, because typically messages are only one-way. "I want to change the volume" can be broadcast from the options screen, which is then picked up by whatever listeners exists- hopefully the audio manager. The audio manager would change the volume in reply, but there's no need to shout back that the volume has actually been changed.

    If you do need a response, like we do with our LocalizationManager (a string value), then the message object can contain a delegate and require that delegate in the constructor. For instance "UILocalizationRequest request = new UILocalizationRequest(UpdateText);" where UpdateText is a function that takes a string paremeter (the new localization data) and the constructor requires a delegate to a function that takes a string parameter. The UILocalization scripts send out a message each time they're enabled that say "we need an update to our text", and the LocalizationManager is set to listen for these events and responds with the text they're looking for by invoking and sending it to the included delegate.

    Now, you don't really need to decide on one of these, but rather just use whatever feels appropriate to the task at hand. This is key. A character manager and an audio manager and a localization manager all have different requirements for how much the other objects need to know about them and what their sphere of influence is. I don't use the event system in situations where I can handle things in a local manager, or where there might exist multiple simultaneous managers that need to parse the message to see if it applies to them- just in the same situations where I'd normally use a singleton.

    That said, you can do what you like. There's really no right or wrong ways, just balances between skill required, ease of setup, ease of use, extensibility, reusability, etc...
     
    Last edited: Oct 21, 2016
    Vedrit likes this.