Search Unity

Dependency for a single unit, best practice?

Discussion in 'Scripting' started by Crouching-Tuna, Oct 14, 2019.

  1. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    Hi, so after some reading on dependency in general, i'm still not sure what's the best practice specifically for within a single unit.

    https://docs.unity3d.com/Manual/Mes...230.220293515.1570857471-995692645.1537962594
    https://forum.unity.com/threads/is-this-an-example-of-good-abstraction-or-terrible-coupling.484310/
    https://forum.unity.com/threads/any-tips-and-tricks-for-organizing-code.331879/

    I can't find the answer/opinion from the above threads. In conclusion they:
    1. Talk about Singleton and what to use instead for Global Managers/Services
    2. Messaging/events

    Within a single unit, at least in my game, there's the:
    UnitRefs, contains all the references below to be used freely among the systems that needs it. Also the entry point for outside calls. Outside system does not deal with below classes at all. Most of these public methods takes in packets such as InputData, PositionUpdateData, so when clients receive these packets, it's easily received and processed. The methods will pass/apply these packets to corresponding classes, or raise events
    Stats, levels, attributes, movement speed. Mostly data and methods to change the data. Kept decoupled as much as possible, and other systems use this as reference
    UnitSkills, learned skills, passives, OnHit effect. Just data. Other system refers to this when they apply/do actions/damage calculations
    Vitality (hp, mp, buffs, exp?), also data. Maybe raise event when unit dies and level up
    Inventory, just data
    UnitInput, probably a bit more complicated. LocalInput class for client to send input to server, server processes and broadcasts the "official input" for this unit. If that input is invalid, server will trim it out. So, this in the end also holds data for other system to act upon
    Character Controller, processes and drives based on input, Stats.MSpd, and Vitality.buffs, and whether Interacter is in the middle of a conversation?
    Interacter, also listens to Input and triggers Interactables within range
    Intent, like CharacterController but for actions. Keeps track of ongoing action, re-casting an action if the player interrupts, etc

    What i do, and questions/asking for critics:
    1. When server processes the official UnitInput, it also filters any invalid input. I want each client to actually also simulate from this official Input. So when the server-side processes UnitInput, it checks with other "controller" system if it's valid, but the actual apply of the action is done in later step after the Input is broadcasted, so all Clients will also simulate the same actions.
    If the Input contains a "pickup", it'll check if the Inventory has an empty slot. If it has a "cast X spell", it'll check with Intent if it's already casting the same spell. If not, interrupt that spell
    I can't use "messaging/event" here since there's a lot of bool CheckMethod, right?
    The "next step" which is the actual apply of the action, of course event system or other ways can work. I'm just letting the server do all the sanity checks, and even polish the resulting details of the action

    2. Is UnitRefs recommended? This is for a single unit, so i expect them to be somewhat coupled

    3. I'm also in the middle of defining the Skills, the base class, the further Targetting system, valid targets, ApplyMethod, etc.
    My other thread from a long time ago
    https://forum.unity.com/threads/skill-casting-method-and-secondary-effects.279190/
    I'm now gonna use some static helper that the derived classes will define, and again the whole process (probably instigated by Intent, referring a lot to UnitSkills and Stats) will have to check a lot with pretty much all of the systems, like a Skill that damages based on current missing health. I guess this is unavoidable, but Stats and Vitality can have GetStat(enum StatType, enum NumberType(current, missing, percentage, etc)) for the helpers to fetch its data.

    Thanks for reading!
     
  2. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    I myself kind of like singleton MonoBehaviours. The disadvantage is that in order to always be accessible, they need to have some way to add themself into a scene. (In some fixed place that is defined in the MonoBehaviour itself.) The advantage is that they are always available and can be viewed in the hierarchy just like any other MonoBehaviour.

    I would recommend trying to minimize the amount of information that is actually stored in the singleton itself. So place the actual skills in some MonoBehaviour or ScriptableObject and have the singleton point to it, so you can always find it. This way it's easier to expand the logic once you decide later there can be multiple skill sets/players.

    I generally use (MonoBehaviour) singletons for things like:
    • Storage in PlayerPrefs
    • Input handling as you mentioned
    • General state checks, like the availability of an internet connection
    • A connection to a central server
    So, things that really have no need to be done in multiple places. Also, things that are used by many other classes, so it would require a lot of links without a singleton lookup. Finally, they need to have some status that is useful to view in the editor. (Else a static construction would also do.)
     
  3. Crouching-Tuna

    Crouching-Tuna

    Joined:
    Apr 4, 2014
    Posts:
    82
    Hi thanks.
    I'm pretty familiar and can handle dependencies revolving the global managers, but it's the dependencies in a single unit that i'm not sure how others do it.
    I guess within a single unit, tightly coupled dependencies (they don't all have to be MB, in fact), is fine, as long as it's layered enough and one directional
     
  4. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    Interfaces and UnityEvents are good tools to help keep the different components of your units decoupled from each other, so that they can be more easily replaced by different components later on if needed.

    While Unity can't serialize your interface references, you can still use GetComponent during Awake to fetch components by the interface they implement.

    So instead of your components directly referencing a CharacterController class, they could find a component that implements ICharacterController.

    Instead of your CharacterController class referencing many components directly, it could just contain events that your other components can subscribe to and vice versa.

    For example your Vitality component could implement IAttackable, and when it is attacked, it could broadcast events OnAttacked / OnDamaged / OnKilled when applicable. Your CharacterController could subscribe to these events and handle playing different animations in reaction to them etc.