Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question How to handle Game Logic needed from diffrent Systems

Discussion in 'Entity Component System' started by Santonian, Jun 23, 2021.

  1. Santonian

    Santonian

    Joined:
    Sep 23, 2020
    Posts:
    20
    Hi all,
    I have a question about structuring my code. This might be related to the fact, that I still have a hard time to grasp the Data Driven Concept instead of OOP.

    However, this is the situation:

    I am making a top down building/manangement game. Very early development. Think of Prison Archtiect or RimWorld. I mention them, just for you to understand what I am trying to do.

    I have my map hat consists of tiles. Each Tile is an entity.

    When the user builds something on the map he has to select what he wants to build and then moves the mouse over the map. The mouse cursor shows the "thing" he wants to build either in red or in green depending on the tile under the cursor.

    I did this with a cursor system that renders the cursor color depending on a Validation Logic (keep this in mind)


    After the mouse button is released the actual building process starts. I have an other system for this, that modifies the tile entities, creates a child entity and in the end the "thing" that was build gets rendered on the map. During build time I run the Validation Logic again to see if all "things" can be build or not. So that I only build the ones that are correct. (I can drag the build, like building a floor, and only the floor tiles that are valid should be build, the rest is ignored)

    My question is now. What's a good approach to store the validation logic?
    What I did is, that I made a static method in separated c# file. This method is called from the cursor system and from the build system. The method gets provided with the Entity I want to build on and the thing I want to build. It utilize the EntityManager in the static method to work with the entity and access different Components of the entity.

    Is that ok? Or should I do it i differently?

    thanks for the help

    Cheers
    S.
     
  2. Santonian

    Santonian

    Joined:
    Sep 23, 2020
    Posts:
    20
    This is an example of one of the validator methods I call from the Systems. This one is pretty generic though. I wonder if it's ok to utilize the EntityManager like that and work with the entities.


    Code (CSharp):
    1.     public static bool HasChildWithComponent<T>(Entity entity) where T : struct, IComponentData {
    2.         EntityManager em = World.DefaultGameObjectInjectionWorld.EntityManager;
    3.  
    4.         if(em.HasComponent<Child>(entity)) {
    5.             DynamicBuffer<Child> children = em.GetBuffer<Child>(entity);
    6.             for(int i = 0; i < children.Length; i++) {
    7.                 if(em.HasComponent<T>(children[i].Value))
    8.                     return true;
    9.             }
    10.         }
    11.         return false;
    12.     }
    13.  
     
  3. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    Your approach sounds good

    Static methods are really something you'll be using very often when working with DOTS. Whenever you need logic that must be reused in different places, you'll need those
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,626
    It's interesting because I pretty much never use static methods across systems for entity operations.

    To each their own, but I much prefer helper structs.
     
  5. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    True, my "you'll need those" statement was a bit of an oversimplification
     
  6. Santonian

    Santonian

    Joined:
    Sep 23, 2020
    Posts:
    20
    Could you give a simple example and maybe a short explanations why to use them instead of staic methods?

    Anyway thx for the input!
     
  7. PhilSA

    PhilSA

    Joined:
    Jul 11, 2013
    Posts:
    1,926
    "Helper Structs" could mean many different things, but here are two examples (both of these suggestions assume you'd be using jobs and not just the main thread EntityManager):

    Code (CSharp):
    1. public struct TileHandler
    2. {
    3.     public BufferFromEntity<Child> childBufferFromEntity;
    4.  
    5.     public bool HasChildWithComponent<T>(Entity entity) where T : struct, IComponentData
    6.     {
    7.         if (childBufferFromEntity.HasComponent(entity))
    8.         {
    9.             DynamicBuffer<Child> children = childBufferFromEntity[entity];
    10.             for (int i = 0; i < children.Length; i++)
    11.             {
    12.                 if (children[i].Value == entity)
    13.                 {
    14.                     return true;
    15.                 }
    16.             }
    17.         }
    18.         return false;
    19.     }
    20. }
    "TileHandler" is a struct that you create in your system's update and you pass it to a job that needs to check "HasChildWithComponent". That job will then just have to call TileHandler.HasChildWithComponent(entity) when it needs to do the check

    The caveat with this is that you need to make sure that nothing else in the job will use the a "BufferFromEntity<Child>". Otherwise you'll get burst aliasing errors (you're only allowed to have one "ComponentDataFromEntity" or "BufferFromEntity" of each type per job

    Code (CSharp):
    1. public struct Tile : IComponentData
    2. {
    3.     // your data here
    4.  
    5.     public bool HasChildWithComponent<T>(Entity entity, in BufferFromEntity<Child> childBufferFromEntity) where T : struct, IComponentData
    6.     {
    7.         if (childBufferFromEntity.HasComponent(entity))
    8.         {
    9.             DynamicBuffer<Child> children = childBufferFromEntity[entity];
    10.             for (int i = 0; i < children.Length; i++)
    11.             {
    12.                 if (children[i].Value == entity)
    13.                 {
    14.                     return true;
    15.                 }
    16.             }
    17.         }
    18.         return false;
    19.     }
    20. }
    This approach is very similar to having static functions, but the function lives directly in your Tile component instead.

    The caveat of this is that if your "HasChildWithComponent" modified data in the Tile component, it would be easy to not be aware of that and to forget to write back the Tile data into the ECS

    Personally I often just stick to the static method approach, because of the caveats I mentioned in both of those approaches. But there are times when these approaches still make sense despite the caveats. For example, Approach 1 is great in situations where there would be tons of "ComponentaDataFromEntity" or "BufferFromEntity" required for the operations you need to do
     
    Last edited: Jun 25, 2021
    Santonian likes this.
  8. Santonian

    Santonian

    Joined:
    Sep 23, 2020
    Posts:
    20
    Thank you for the clarification. Apreciate it!