Search Unity

SystemStateComponent use case question

Discussion in 'Entity Component System' started by nicolasgramlich, May 13, 2020.

  1. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Hey everyone,

    I'm trying to wrap my head around a useful scenario where I could apply using SystemStateComponents. All examples I've come across are very abstract, so I figured I put my thoughts out here in case a real world example

    For my game I'm trying to experiment with blob shadows instead of realtime shadows for low end Android devices.

    Current state:
    upload_2020-5-12_23-37-15.png

    So I was thinking that I'd set up a ShadowSystem something like this:

    1. Every unit prefab from above gets a
      Shadow
      component, which might be a tag component for simplicity.
    2. The
      ShadowSystem
      looks for unit entities with
      Shadow
      components, but without
      ShadowState
      components.
      1. Add a new "shadow quad" entity that's a simple quad representing the blob shadow and have it linked to from the
        ShadowState
        component on the unit entity.
    3. The
      ShadowSystem
      looks for entities with both
      Shadow
      and
      ShadowState
      component and makes sure the blob shadow translation gets moved along the unity entity pinned to the ground.
    4. The
      ShadowSystem
      looks for entities with only a
      ShadowState
      component and then proceeds to destroy the "shadow quad" entity.
    Is this a useful usage of SystemStateComponents and if not, why is that and what would be a smarter way of doing it?

    Thanks in advance,
    Nico

    Edit:
    ShadowState might look something like this:

    Code (CSharp):
    1. public struct ShadowState : ISystemStateComponentData {
    2.    public Entity shadowQuadEntity;
    3. }
     
  2. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Hi,

    In this case you want the sahdow to follow the model and be destroyed when the model entity is destroyed.
    While I think your system will work, I think you could parent the shadow entity to have the shadow follow the model and add a linkgroup to destroy then together.

    An exemple I can think of is a saptial acceleration struture like a quadtree:
    1- create an entity that need to be referenced in the struture (tag component maybe ? like a unit component tag ?)
    2- have a system that take all entities with the tag component but no state component and add the entity to the quadtree and the system state component
    3- have the same system detect the entities with the system state component but not the tag component and remove the entity from the quadtree and remove the system state component.

    Another note, from a comment of Unity the system state component should be defined as a private component within the system. (only the system as control/knoledge over it).
     
  3. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    Hm, I'm wondering if parenting or manual repositioning would perform better, since I need to apply per frame translation anyway to make sure the shadow is pinned to the ground (say if a unit get tossed into the air).

    Also I keep reading that the flatter the entities are, the better :rolleyes:

    Let's keep the ideas coming :)
     
  4. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    992
    Ha, yes I did not consider unity flying around, so parenting is not an option...
     
  5. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    That was so easy. What a fantastic/elegant pattern :)

    This is the code I ended up with:
    Code (CSharp):
    1. public struct Shadow : IComponentData {
    2.  
    3. }
    4.  
    5. struct ShadowSystemState : ISystemStateComponentData {
    6.    public Entity shadowEntity;
    7. }
    8.  
    9. public class ShadowSystem : JobComponentSystem {
    10.    private EndSimulationEntityCommandBufferSystem endSimulationEntityCommandBufferSystem;
    11.  
    12.    protected override void OnCreate() {
    13.       endSimulationEntityCommandBufferSystem = World.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    14.    }
    15.  
    16.    protected override JobHandle OnUpdate(JobHandle inputDependencies) {
    17.       var prefabEntity = PrefabAssetRegistry.GetPrefabEntity(EntityManager, PrefabAssetRegistry.Identifier.Shadow);
    18.       if (prefabEntity == Entity.Null) {
    19.          Debug.LogWarning($"PrefabAssetRegistry.GetPrefabEntity({PrefabAssetRegistry.Identifier.Shadow}) => null. Early exit in ShadowSystem :'(");
    20.          return inputDependencies;
    21.       }
    22.  
    23.       var endSimulationEntityCommandBuffer = endSimulationEntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent();
    24.       /* Create Shadows: */
    25.       var createJobHandle = Entities.WithNone<ShadowSystemState>().ForEach((Entity entity, int entityInQueryIndex, in Shadow shadow) => {
    26.          var shadowEntity = endSimulationEntityCommandBuffer.Instantiate(entityInQueryIndex, prefabEntity);
    27.          endSimulationEntityCommandBuffer.AddComponent(entityInQueryIndex, entity, new ShadowSystemState { shadowEntity = shadowEntity });
    28.          endSimulationEntityCommandBuffer.SetComponent(entityInQueryIndex, entity, new Translation { Value = new float3(0, 1, 0) });
    29.       }).Schedule(inputDependencies);
    30.       endSimulationEntityCommandBufferSystem.AddJobHandleForProducer(createJobHandle);
    31.  
    32.       /* Update Shadows: */
    33.       var translationFromEntity = GetComponentDataFromEntity<Translation>(false);
    34.       var updateJobHandle = Entities.WithAll<Shadow>().WithNativeDisableParallelForRestriction(translationFromEntity).ForEach((Entity entity, int entityInQueryIndex, ref ShadowSystemState shadowSystemState) => {
    35.          var entityTranslation = translationFromEntity[entity];
    36.          var shadowTranslation = entityTranslation;
    37.          shadowTranslation.Value.y = 0.01f; // Just above 0 to avoid z fighting with the terrain!
    38.          translationFromEntity[shadowSystemState.shadowEntity] = shadowTranslation;
    39.       }).Schedule(createJobHandle);
    40.       endSimulationEntityCommandBufferSystem.AddJobHandleForProducer(updateJobHandle);
    41.  
    42.       /* Destroy Shadows: */
    43.       var destroyJobHandle = Entities.WithNone<Shadow>().ForEach((Entity entity, int entityInQueryIndex, in ShadowSystemState shadowSystemState) => {
    44.          if (shadowSystemState.shadowEntity != Entity.Null) {
    45.             endSimulationEntityCommandBuffer.DestroyEntity(entityInQueryIndex, shadowSystemState.shadowEntity);
    46.          }
    47.          endSimulationEntityCommandBuffer.RemoveComponent<ShadowSystemState>(entityInQueryIndex, entity);
    48.       }).Schedule(updateJobHandle);
    49.       endSimulationEntityCommandBufferSystem.AddJobHandleForProducer(destroyJobHandle);
    50.  
    51.       return destroyJobHandle;
    52.    }
    53. }
     
    JohnnyTurbo, Egad_McDad and WAYNGames like this.
  6. nicolasgramlich

    nicolasgramlich

    Joined:
    Sep 21, 2017
    Posts:
    231
    The result if anyone is interested. Performance compared to "HQ" shadows is pretty significant 50% more FPS for massive amounts of units.
    Screen Shot 2020-05-14 at 1.58.51 AM.png
     
  7. JohnnyTurbo

    JohnnyTurbo

    Joined:
    May 17, 2017
    Posts:
    42
    Just wanted to post on this old thread because I though this was a really interesting use case for SystemStateComponents. Like @nicolasgramlich mentioned, most of the use-cases in the docs are a bit abstract and as I was creating my tutorial video on this I wanted to find a good way to show off how these are used in a way people could easily wrap their heads around - even if this isn't necessarily how these were originally "intended" to be used.

    Anyways, just wanted to say thanks for the good idea and post my tutorial video here for anyone who may come across this thread in the future and see how this would be setup with the updated SystemBase code, enjoy!

     
    bb8_1, DevViktoria and Lukas_Kastern like this.