Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feedback Scriptable objects as dependency injection tool

Discussion in 'Scripting' started by Iwo_Rosiak, Feb 2, 2023.

  1. Iwo_Rosiak

    Iwo_Rosiak

    Joined:
    Oct 21, 2020
    Posts:
    1
    Hi folks,

    I have had a debate in my head about a particular system I have created for the company I work at. We are working on a small-scale RTS/RPG game (very early development stages). Such games usually game a decent amount of enemies that wildly differ from each other (in terms of some details like statistics and behaviours but not the components on a prefab). When I first joined half a year ago, each one of those enemies was represented using a prefab. Although prefabs seemed to be a reasonable solution, they proved to be a bit messy when trying to create new NPCs as we would end up with a few dozen of prefabs and micromanaging the inheritance on them ended up being uncomfortable.

    I have prototyped a new solution that I will refer to as “definitions”. Essentially, everything was abstracted to the maximum possible extent and if there were elements that were impossible to abstract, they were placed in a definition. A definition is a scriptable object that looks more or less like this:

    Code (CSharp):
    1.  
    2. public class CharacterDefinition : ScriptableObject
    3. {
    4.         [field: SerializeField] public string Name { get; private set; }
    5.         [field: SerializeField] public List<Statistic> Statistics { get; private set; }
    6.         [field: SerializeReference] public List<IAIDecisionModif> AIDecisions { get; private set;}
    7.         //etc…
    8. }
    There are two problems that I am facing right now:

    First, each time I expand the system, I have to edit this class. Not sure if I am right but that seems to violate the open/closed principle. I am willing to live with that, to be honest.

    Second, let’s say that we would like to add a new system that is definable through our definitions system. This new system is responsible for switching behaviour trees when receiving an ID (for the sake of simplicity let’s say that ID is a string). So, when the skeleton mage receives an “attack” ID it wants to switch its behaviour tree to RunInCircleAndAttackBT. Orc on the other hand would execute ChargeBT.

    For that purpose we create a new class called ImplementationPair to simulate a dictionary which looks like that:

    Code (CSharp):
    1.  
    2. public sealed class ImplementationPair
    3. {
    4.       [field: SerializeField] public string ID { get; private set; }
    5.       [field: SerializeField] public BehaviorTree Behaviour { get; private set; }
    6. }
    Okay, now a second class which will be sitting on our prefab that will receive the CharacterDefinition and copy data from it.

    Code (CSharp):
    1.  
    2. public sealed class BehaviourInjector
    3. {
    4.       private List<ImplementationPair> _implementations;
    5.       public void Define(CharacterDefinition def)
    6.       {
    7.              _implementations = def.Implementations;
    8.       }
    9.  
    10.  
    11.       public void InjectBehaviour(string id)
    12.       {
    13.             //implementation of the method that switches the behaviour
    14.       }
    15. }
    It all looks really good so far (at least in my opinion) but here is the problem. Both CharacterDefinition and ImplementationPair (with BehaviourInjector) are located in separate assemblies called Characters and Controller. Controller is already referencing Characters because of other classes but now Characters cannot reference Controller to be able to have ImplementationPair field.

    So here are my questions:

    Do you think I have over-engineered this system?

    I am a RimWorld modder and this game seems to use a very similar approach, although they are using XML definitions. Maybe I am just following the golden hammer rule because of my familiarity with this approach.

    How could I improve this system? Or do you think I should look for other solutions/go back to using prefabs?

    I will appreciate any feedback :) thanks a lot <3
     
  2. SF_FrankvHoof

    SF_FrankvHoof

    Joined:
    Apr 1, 2022
    Posts:
    780
    You could also create subclasses for variants of your characters (e.g. one for infantry, one for vehicles, etc)

    if your aim is to set up a global/abstract method for dependency-injection, shouldn't those classes (the dependency-injection ones) be in their own separate assembly?

    I think you'll find out once you start fully implementing it. Start small.