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

How to change entity's color by code in ECS

Discussion in 'Entity Component System' started by Rouddem, Jan 25, 2021.

  1. Rouddem

    Rouddem

    Joined:
    Dec 15, 2015
    Posts:
    14
    Hi everyone!

    I'm looking for a simple thing which is "Changing the color of one entity" by code.

    After a bunch of research, I've found the following:

    RenderMesh is an ISharedComponentData. If we modify the RenderMesh.material.color of one entity, it modifies all shared RenderMesh...
    Code (CSharp):
    1. RenderMesh renderMesh = EntityManager.GetSharedComponentData<RenderMesh>(m_entity);
    2. renderMesh.material.color = UnityEngine.Color.blue;
    3. EntityManager.SetSharedComponentData(m_entity, renderMesh);
    Rendering Hybrid allows us to add a "Material Override" component, which is what we want : overriding the color of the attached instance. However, MaterialOverride is a MonoBehaviour class, so we can't access from :
    - EntityManager.GetComponentData<MaterialOverride>()
    - Entities.ForEach((MaterialOverride _materialOverride) => { ... })

    I've seen this way : https://forum.unity.com/threads/per-instance-material-params-support-in-entities-0-2.782207/ but I would like to keep it simple for now (not using ShaderGraph).

    I think I missed something: it should be super-simple to do this by code. Any ideas/suggestions?

    Thanks ;)

    Spec:
    - Unity 2020.2.2f1
    - Package "com.unity.entities": "0.17.0-preview.41"
    - Package "com.unity.rendering.hybrid" : "0.11.0-preview.42"
     
    Naotagrey likes this.
  2. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    851
    tjumma and Rouddem like this.
  3. Rouddem

    Rouddem

    Joined:
    Dec 15, 2015
    Posts:
    14
    Thank you for your reply :)
    Well, I was not using any HDRP/URP materials, so I was quite far from the solution. Then the HybridRender documentation was not entirely up-to-date, so I missed some information.

    Because it's not straighforward, here are some useful steps for next adventurers :
    • Package needed:
      • Entities 0.17.0-preview.41 : provides ECS
      • HybridRenderer 0.11.0-preview.42 : draws meshes using DOTS
      • [Important] Universal RP 10.2.2 : for built-in shaders (or High Definition RP)
      • [Optionnal] ShaderGraph 10.2.2 : for custom shaders
    • Enable the Scriptable Render Pipeline rendering:
      • Click on "Assets > Create > Rendering > Universal Render Pipeline > Pipeline Asset"
        • This will create an asset and an asset_Renderer
      • Drag&drop your asset into "Project Settings > Graphics > Scriptable Render Pipeline Settings"
    • To change the color of built-in shaders:
      • Create a material
      • Assign it an URP shader like "Universal Render Pipeline/Lit"
      • Attach it to your gameobject and the job is done.
      • If you want to modify the color by code, you can simply do this to change the "BaseColor" in this example (thx thelebaron):
    Code (CSharp):
    1. using Unity.Rendering;
    2.  
    3. EntityManager.AddComponentData(e, new URPMaterialPropertyBaseColor {Value = new float4(0, 0, 1, 1)});
    • To change the color of custom shaders:
      • [Important] You have to use the V2 of Hybrid Renderer. To do so, add "ENABLE_HYBRID_RENDERER_V2" to your symbols under "Project Settings > Player > Scripting Define Symbols"
      • Make sure you have the package ShaderGraph
      • Create and set-up your custom shader:
        • Click on "Assets > Create > Shader > Universal Render Pipeline > Lit Shader graph". This will create a basic Lit shader
        • Access to the graph of your shader (select it and click on "Open Shader Editor" in the inspector, or double-click on the shader)
        • Create a new color variable:
          • Click on "New Shader Graph > + > Color"
          • Name it "_MyColor"
          • Click on the variable, and open the "Graph Inspector > Node Settings" tab
          • [Important] The reference field is recommanded to be equal to the name, so set it to "_MyColor" to find it easily by code
          • [Very important (7 hours of my life)]
            • If you have Unity 2020.2.2f1 or more, check "Override Property Declaration" and set the "Shader Declaration" value to "Hybrid Per Instance", this will allow the override of the color by code.
            • Else, you have the instruction under "Setting up shaders for Hybrid Renderer V1" here
        • Drag&drop the variable in the graph and bind it to "Base Color"
        • [Super important] There is no auto-save, and Ctrl+S doesn't save your modification (UX designers please...) so you have to click on "Save Asset" (top-left window)
      • Create and set-up your material:
        • Create a material
        • Assign it your custom shader. It should be found under "Shader Graphs"
        • [Important] Check "Enable GPU Instancing" if you instantiate your entity (former gameobject) by code
        • Assign your material to your gameobject
      • Create a struct implementing IComponentData with the following attribute:
    Code (CSharp):
    1. using Unity.Mathematics;
    2. using Unity.Rendering;
    3.  
    4. // "_MyColor" refers to the reference we overrode previously
    5. [MaterialProperty("_MyColor", MaterialPropertyFormat.Float4)]
    6. public struct MyColorComponent : IComponentData
    7. {
    8.     public float4 Value;
    9. }
    • Once you have the reference of your mesh's entity, "simply" affect it the new component with the desired color
    Code (CSharp):
    1. EntityManager.AddComponentData(meshEntity, new MyColorComponent { Value = new float4(1, 0, 0, 1) });

    And if I forgot nothing, this should work :D
    Do not hesitate to have a check of the documentation thelebaron linked.

    Enjoy !
     
    Last edited: Apr 17, 2021
  4. Lapsapnow

    Lapsapnow

    Joined:
    Jul 4, 2019
    Posts:
    51
    Thanks Rouddem!
    But, what Usings do you have to get at URPMaterialPropertyBaseColor? I can't seem to find it.

    Edit : using Unity.Rendering;

    I'm trying to dynamically recolour units according to a team.colour in a DOTS based RTS.
     
    Last edited: Feb 19, 2021
    MNNoxMortem, mattbrandmm and Rouddem like this.
  5. Rouddem

    Rouddem

    Joined:
    Dec 15, 2015
    Posts:
    14
    Thanks for this precision! The post has been edited.
    If one day, you are in the same situation, with Visual Studio, click on the unfound class and press Alt+Enter, it will recommand you available imports ;)
     
    Last edited: Apr 17, 2021
    Lapsapnow likes this.
  6. mattbrandmm

    mattbrandmm

    Joined:
    Jun 5, 2018
    Posts:
    90
    Rouddem likes this.
  7. BarShiftGames

    BarShiftGames

    Joined:
    Jul 31, 2017
    Posts:
    12
    Having trouble with this, does it have to be added with the IConvert stuff? Or can these components be added with [GenerateAuthoringComponent]? Because I have tried every way I can think of without IConvert (since I can't find any solid documentation on it), and it hasn't worked, I have my material with the correct names and booleans ticked in the shader graph node properties, all that, but it doesn't work when I change the color value.
     
  8. BarShiftGames

    BarShiftGames

    Joined:
    Jul 31, 2017
    Posts:
    12
    I've done this:

    Code (CSharp):
    1. [GenerateAuthoringComponent]
    2. public struct RainbowMeshcolor : IComponentData
    3. {
    4.     public float totalTime;
    5.     public Entity toEffect;
    6. }
    7.  
    8. [BurstCompile]
    9. public class RainbowMeshSystem : SystemBase
    10. {
    11.     protected override void OnUpdate() {
    12.         float dt = Time.DeltaTime;
    13.  
    14.         Entities
    15.             .WithoutBurst()
    16.             .WithNone<MyColorComponent>()
    17.             .WithStructuralChanges()
    18.             .ForEach((ref RainbowMeshcolor mesh) => {
    19.                 EntityManager.AddComponentData(mesh.toEffect, new MyColorComponent { Value = new float4(1, 0, 0, 1) });
    20.             }).Run();
    21.  
    22.         Entities.ForEach((ref RainbowMeshcolor mesh, ref MyColorComponent color) => {
    23.             mesh.totalTime += dt;
    24.  
    25.             color.Value = new float4(
    26.                    math.abs(math.cos(mesh.totalTime)),
    27.                    math.abs(math.cos(mesh.totalTime)),
    28.                    math.abs(math.cos(mesh.totalTime)),
    29.                    1.0f);
    30.         }).Schedule();
    31.     }
    32. }
    33.  
    But it doesn't work? I made sure the material and shader graph, and environment variables are all set up correctly

    I can see the values changing in the entity debugger, so the system is running, but nothing changes on the mesh.
     
  9. BarShiftGames

    BarShiftGames

    Joined:
    Jul 31, 2017
    Posts:
    12
    Update: It's because I was trying to do it on a skinned mesh entity, which apparently gets split into two entities, and I have zero idea on the "proper" way of getting it's other half, since the only component that holds that reference is made private via unity's immeasurable "wisdom".
     
  10. mk1987

    mk1987

    Joined:
    Mar 18, 2019
    Posts:
    53
    Not sure how helpful it is but i had something similar where you have multiple submesh and materials unity makes each material and submesh a seperate entity that is a child of the source entity. My solution used entity manager to go through the child buffer and if it had a certain component i would use entity manager to update that entitiys colour since i did not need colour changed on both internal and external surfaces (think i used submesh number as a identifier?). Its not particularly fast but gets the job done.
     
  11. atmuc

    atmuc

    Joined:
    Feb 28, 2011
    Posts:
    1,151
    How can I change color using Entities 1.0?

    I think MaterialPropertyFormat is removed.
     
    Last edited: Jan 29, 2023
  12. desper0s

    desper0s

    Joined:
    Aug 4, 2021
    Posts:
    15
    As far as I know MaterialPropertyFormat is not needed, engine will detect value type automatically
     
  13. atmuc

    atmuc

    Joined:
    Feb 28, 2011
    Posts:
    1,151
    This works when the mesh renderer is on the entity root. I have a mesh renderer as a child of my entity. How can I change Entity's child game object's color?
     
  14. desper0s

    desper0s

    Joined:
    Aug 4, 2021
    Posts:
    15
    You need to traverse root's entity child entities on your own. And there are few options.

    1. Use 'LinkedEntityGroup'
    Code (CSharp):
    1. var elements = entityManager.GetBuffer<LinkedEntityGroup>(parentEntity);
    2.  
    3. foreac (var element in elements)
    4. {
    5.      if (element.Value != parentEntity)
    6.     {
    7.            //Apply color
    8.     }
    9. }
    Things to consider:
    • It will automatically go through all levels, not only direct children
    • 'LinkedEntityGroup' should be instantly available after calling 'entityManager.Instantiate(prefab)'
    2. Use 'Child' - also using GetBuffer<Child>(parentEntity)
    • It will iterate only through direct children
    • It is not available instantly after calling 'entityManager.Instantiate(prefab)', because 'Children' is calculated by internal Unity systems
    If you want to check who is parent of given entity you can access 'Parent' component
     
  15. atmuc

    atmuc

    Joined:
    Feb 28, 2011
    Posts:
    1,151
    I use ISystem and Aspect for the parent entities.

    For the line "entityManager.SetComponentData(element.Value, myColorComponent);" I get following error;

    InvalidOperationException: Managing jobs from within jobs is not allowed

    Code (CSharp):
    1. public void SetColor(Color color)
    2.         {
    3.             var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
    4.  
    5.             var elements = entityManager.GetBuffer<LinkedEntityGroup>(entity);
    6.  
    7.             foreach (var element in elements)
    8.             {
    9.                 if (element.Value != entity)
    10.                 {
    11.                     var myColorComponent = new MyColorComponent
    12.                     {
    13.                         Value = new float4(color.r, color.g, color.b, color.a)
    14.                     };
    15.  
    16.                     entityManager.SetComponentData(element.Value, myColorComponent);
    17.                 }
    18.             }
    19.         }
    20.  
    21. [BurstCompile]
    22.     public partial struct MoveJob : IJobEntity
    23.     {
    24.         public float DeltaTime;
    25.         public Color Color;
    26.         [BurstCompile]
    27.         public void Execute(MoveToPositionAspect moveToPositionAspect)
    28.         {
    29.             moveToPositionAspect.SetColor(Color);
    30.             moveToPositionAspect.Move(DeltaTime);
    31.         }
    32.     }
    33.  
    34.  
    35.  
    36.  
     
  16. desper0s

    desper0s

    Joined:
    Aug 4, 2021
    Posts:
    15
    I am afraid that you cannot use entity manager directly in IJobEntity... What you can do is to use "ComponentLookup" and "BufferLookup" (you need also to adjust "SetColor" implementation or move this implementation outside Aspect at all - be avare that Aspects are designed to work with one entity)

    Code (CSharp):
    1.  
    2. [BurstCompile]
    3.     public partial struct MoveJob : IJobEntity
    4.     {
    5.         public float DeltaTime;
    6.         public Color Color;
    7.         public ComponentLookup<MyColorComponent> ColorLookup;
    8.         [ReadOnly]
    9.         public BufferLookup<LinkedEntityGroup> LinkedEntityLookup;
    10.         [BurstCompile]
    11.         public void Execute(MoveToPositionAspect moveToPositionAspect)
    12.         {
    13.             moveToPositionAspect.SetColor(Color, ref ColorLookup, ref LinkedEntityLookup);
    14.             moveToPositionAspect.Move(DeltaTime);
    15.         }
    16.     }
     
    atmuc likes this.