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

Resolved Help understanding how to decouple systems and components properly.

Discussion in 'DOTS Dev Blitz Day 2022 - Q&A' started by aetharra, Dec 7, 2022.

  1. aetharra

    aetharra

    Joined:
    Oct 10, 2021
    Posts:
    7
    Let's say I am building an RTS game and the currency of a player is constantly updated by the rate of production that they mine their resources.

    Then, after a while, such player earns enough currency and wants to build a Barracks. Which needs to make a transaction on their bank balance. Ensuring, in part, that they have enough funds but also then deducting the funds to build their barracks, if fund-able.

    I assume the best way is to define update groups and ensure that the order is proper for the desired currency effects. Such as updating the production revenue before making a transaction.

    But I wonder if there is a way to use ECS in an API/transactional way across systems?

    What if I wanted to have a transaction API on the Currency system, where the ConstructionSystem calls a purchase function. Such as "Purchase(string itemName, int amount)". Because let's say that in the future, I want to now have a ledger system in my game that keeps track of all the purchases made. (So that players can review their game play after to find the places where their choices hurt their path to victory.) A ledger is the role of the currency system, not the Construction system.

    I understand that I can define purchases on the ConstructionSystem that accesses and updates the data component for the player's CurrencyComponent. Then also create a separate function for the LedgerComponent. But that then requires that the ConstructionSystem updates currency and the ledger, increasing the complexity of the ConstructionSystem.
    I can't just have the ledger system check for changes on the CurrencyComponent because that doesn't record what was purchased and when.

    So, I guess my question is, can functions on the CurrencySystem be called from the ConstructionSystem? Can functions on another system be called from another system? How does that work?

    At the heart of my question is needing help in understanding better how to decoupled systems, and how update order plays a role.
     
  2. StephanieRct_

    StephanieRct_

    Unity Technologies

    Joined:
    Jun 2, 2017
    Posts:
    7
    We've recently introduced "Aspect" to our API that let you do just that, decouple your logic from your data and make it available in any systems or jobs.
    Here's what you could do to solve your case using Aspect:

    Code (CSharp):
    1.  
    2.     public struct CurrencyComponent : IComponentData
    3.     {
    4.         public int Value;
    5.     }
    6.     public struct BankTransaction : IBufferElementData
    7.     {
    8.         public int Time;
    9.         public FixedString64Bytes Item;
    10.         public int Price;
    11.     }
    12.     public struct Time : IComponentData
    13.     {
    14.         public int Value;
    15.     }
    16.  
    17.     // Declare an aspect that will decouple "Bank" logic from any system or job.
    18.     // It will encapsulate all the component data it needs to perform its responsibilities.
    19.     public readonly partial struct Bank : IAspect
    20.     {
    21.         private readonly RefRW<Time> m_Time;
    22.         private readonly RefRW<CurrencyComponent> m_CurrencyComponent;
    23.         private readonly DynamicBuffer<BankTransaction> m_BankTransactions;
    24.  
    25.         public void Deposit(int amount)
    26.         {
    27.             m_CurrencyComponent.ValueRW.Value += amount;
    28.             m_BankTransactions.Add(new BankTransaction
    29.             {
    30.                 Time = m_Time.ValueRO.Value,
    31.                 Item = "Deposite",
    32.                 Price = amount
    33.             });
    34.         }
    35.  
    36.         public bool Purchase(string item, int price)
    37.         {
    38.             if (m_CurrencyComponent.ValueRO.Value >= price)
    39.             {
    40.                 m_CurrencyComponent.ValueRW.Value -= price;
    41.                 m_BankTransactions.Add(new BankTransaction
    42.                 {
    43.                     Time = m_Time.ValueRO.Value,
    44.                     Item = item,
    45.                     Price = price
    46.                 });
    47.                 return true;
    48.             }
    49.  
    50.             return false;
    51.         }
    52.  
    53.         public void Tick()
    54.         {
    55.             ++m_Time.ValueRW.Value;
    56.         }
    57.  
    58.         public static void AddOnEntity(EntityManager entityManager, Entity entity)
    59.         {
    60.             entityManager.AddComponentData(entity, new Time());
    61.             entityManager.AddComponentData(entity, new CurrencyComponent());
    62.             entityManager.AddBuffer<BankTransaction>(entity);
    63.         }
    64.     }
    65.  
    66.     public partial struct BankSystem : ISystem
    67.     {
    68.         void OnCreate(ref SystemState state)
    69.         {
    70.             // create one bank entity
    71.             Bank.AddOnEntity(state.EntityManager, state.EntityManager.CreateEntity());
    72.         }
    73.         public void OnUpdate(ref SystemState state)
    74.         {
    75.             foreach (var bank in SystemAPI.Query<Bank>())
    76.                 bank.Tick();
    77.         }
    78.     }
    79.  
    80.     public partial struct ConstructionSystem : ISystem
    81.     {
    82.         public void OnUpdate(ref SystemState state)
    83.         {
    84.             foreach (var bank in SystemAPI.Query<Bank>())
    85.             {
    86.                 if (bank.Purchase("Barrack", 100))
    87.                 {
    88.                     // ...
    89.                 }
    90.             }
    91.         }
    92.     }
    93.  
    94.     public partial struct MinerSystem : ISystem
    95.     {
    96.         public void OnUpdate(ref SystemState state)
    97.         {
    98.             foreach (var bank in SystemAPI.Query<Bank>())
    99.                 bank.Deposit(1);
    100.         }
    101.     }
    All systems that require to do transactions only need to query for the Bank aspect and get access to your decoupled logic / component.
     
    CodeSmile, ChrisVMC and aetharra like this.
  3. aetharra

    aetharra

    Joined:
    Oct 10, 2021
    Posts:
    7
    Thank you, so much! It all makes sense now.