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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Change mesh of single entity

Discussion in 'Entity Component System' started by Claite, Jul 2, 2018.

  1. Claite

    Claite

    Joined:
    Sep 26, 2015
    Posts:
    8
    Hello!

    I'm trying to wrap my head around how you could update or change the mesh/color/rendering of a single entity for certain conditions.

    For example, if you render a 10x10 grid with entities with a MeshInstanceRenderer rendering white cubes. When you move a player or object onto one of those cubes, you want just that cube to render red. I've tried having a system that checks my player position and cube position, and when they line up, update the MeshInstanceRenderer, but that works as expected, turning all cubes red. Removing the component with the entity manager seems to remove them from all entities.

    In ECS, is the correct behavior to simply delete the entity and replace it with a new one that has the highlight? How does that work on a scale like the demo they showed of ECS with hundreds of units getting hit by a fireball. Am I missing something as a way to update a single Entity acted upon?

    A code dump of the system I tried. Not sure why the remove component call removes it from all entities, despite only triggering once.

    Thanks!

    Code (CSharp):
    1. public class HighlightSystem : ComponentSystem {
    2.     public struct ArrowData {
    3.       public int Length;
    4.       public ComponentDataArray<Position> Position;
    5.       public ComponentDataArray<MovementArrow> Arrow;
    6.     }
    7.     [Inject] public ArrowData arrows;
    8.  
    9.     public struct TileData {
    10.       public int Length;
    11.       public ComponentDataArray<LocalPosition> Position;
    12.       public ComponentDataArray<BaseTile> Tile;
    13.       public EntityArray Entities;
    14.       [ReadOnly]
    15.       public SharedComponentDataArray<MeshInstanceRenderer> Mesh;
    16.     }
    17.     [Inject] public TileData tiles;
    18.  
    19.     protected override void OnUpdate() {
    20.       var entityManager = World.Active.GetOrCreateManager<EntityManager>();
    21.  
    22.       for (int i = 0; i < arrows.Length; i++) {
    23.         var arrowPos = arrows.Position[i].Value;
    24.         for (int j = 0; j < tiles.Length; j++) {
    25.           var tilePos = tiles.Position[i].Value;
    26.           if (arrowPos.x == tilePos.x && arrowPos.z == tilePos.z) {
    27.             entityManager.RemoveComponent<MeshInstanceRenderer>(tiles.Entities[j]);
    28.           }
    29.         }
    30.       }
    31.     }
    32.   }
     
  2. Spy-Shifty

    Spy-Shifty

    Joined:
    May 5, 2011
    Posts:
    546
    You can't remove the component directly. This will invalidate your dataset of tiles.

    Use PostUpdateCommand.RemoveComponent<MeshInstanceRenderer>(tiles.Entities[j]) instead.

    Maybe this will already solve your problem!
     
  3. dartriminis

    dartriminis

    Joined:
    Feb 3, 2017
    Posts:
    157
    MeshInstanceRenderer is a shared component, so if you change the material on one, it'll change it for all.

    Now, you could have two instances of the MeshInstanceRenderer, one red, one white. When a player steps on a tile you just set the entities MeshInstanceRenderer component to the red instance.

    Alternatively, you could use the hybrid approach with individual MeahRenderer MonoBehaviours. Then you can change each material independently.
     
  4. Claite

    Claite

    Joined:
    Sep 26, 2015
    Posts:
    8
    Thanks for the replies!

    I tried a few things, but still no luck. I tried using PostUpdateCommand, still removes the component from all tiles. I tried setting the shared component to a new MeshInstanceRenderer, still updates all tiles. Adding fails due to not being able to have more than one MIR on a single entity (expected).

    All test were done using PostupdateCommand so as to not mess with the ComponentGroup as it's iterating.

    Any ideas?

    Code (CSharp):
    1. MeshInstanceRenderer renderer = new MeshInstanceRenderer();
    2. renderer.mesh = tiles.Mesh[j].mesh;
    3. Material m = new Material(Shader.Find("Specular"));
    4. m.color = Color.red;
    5. m.enableInstancing = true;
    6. renderer.material = m;  
    7. PostUpdateCommands.RemoveComponent<MeshInstanceRenderer>(tiles.Entities[j]);
    8. PostUpdateCommands.AddSharedComponent<MeshInstanceRenderer>(tiles.Entities[j], renderer);
    9.  
    10. /**
    11.  * Alternatively:
    12. PostUpdateCommands.SetSharedComponent<MeshInstanceRenderer>(tiles.Entities[j], renderer);
    13. */
    14.  
     
    Pelor likes this.
  5. dartriminis

    dartriminis

    Joined:
    Feb 3, 2017
    Posts:
    157
    What is tiles.Mesh[]? Is it possible that you are getting the same instance of Mesh that the other tiles are using? If this is the case, then setting the material on that mesh will affect all tiles.
     
  6. Claite

    Claite

    Joined:
    Sep 26, 2015
    Posts:
    8
    tiles.Mesh[] is a shared component data array. You can see my first post for how I get it. I thought that meant that I had a component group, where iterating would get me the MIR for each entity, correct?

    Great thought, though! I wonder if for shared components you always get the same "instance" of that component, instead of a sort of uncollapsed version of it? That might be why it's behaving this way... Would that imply I need to delete the component every time I highlight? Is there a way for me to break a single entity away from the group so I can give it a new MIR without affecting the others?
     
  7. Claite

    Claite

    Joined:
    Sep 26, 2015
    Posts:
    8
    Thinking more on my last post, something might have just clicked. I think this is an issue with my creation of the tiles. I have a single instance of the MIR I pull from a prototype in the scene. I didn't think about how that would affect the references.

    Let me fiddle with it when I get a chance and see if that's the issue. Probably is.
     
  8. dartriminis

    dartriminis

    Joined:
    Feb 3, 2017
    Posts:
    157
    doh! forget my last comment. For some reason I was thinking renderer was a MeshRenderer.
     
  9. Claite

    Claite

    Joined:
    Sep 26, 2015
    Posts:
    8
    Awww, I'm ashamed, but I figured it out. If you look at the code I put above, when I got tilePos, I did tiles.Position[i] instead of tiles.Position[j]. This meant that for the first position that actually lined up, I was overwriting every tile's mesh rendered because the position didn't change for the entire inner loop.

    Fixed it to iterate properly and now it's working.

    Thanks for the help all.
     
    avvie likes this.
  10. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    116
    Additionally to your approach, there have been a few custom Mesh/Sprite renderers discussed on this forum. Including ones with coloring components to change individual entity colors. It's ultimately not that hard to make one that can do a lot of additional rendering features in comparison to the default one.
    https://forum.unity.com/threads/instanced-sprite-renderer-example.525235/
     
  11. AlejandroAlternova

    AlejandroAlternova

    Joined:
    Oct 30, 2019
    Posts:
    6
    I'm also trying to create some entities and add it a RenderMesh, then with a system I try to change the texture of each entity, but I end up changing all entities to the same texture.

    Here's what I have for the system:

    Code (CSharp):
    1. public class LayerSystem : ComponentSystem
    2.     {
    3.         private float _seconds = 0;
    4.         protected override void OnUpdate()
    5.         {
    6.             var deltaTime = Time.DeltaTime;
    7.             _seconds += deltaTime;
    8.             if (!(_seconds > 1)) return;
    9.  
    10.             var rnd = new Unity.Mathematics.Random((uint)System.DateTime.UtcNow.Ticks);
    11.             _seconds = 0;
    12.             Entities.ForEach((Entity entity, ref Layer layer) =>
    13.             {
    14.                 var newMaterial = LayerEntityCreator.Instance.LayerMaterialSource;
    15.                 newMaterial.SetTexture("_BaseMap", LayerEntityCreator.Instance.LayerData[layer.layerNumber]._layerTextures[rnd.NextInt(layer.numberOfOptions)]);
    16.                 var render = EntityManager.GetSharedComponentData<RenderMesh>(entity);
    17.                 EntityManager.SetSharedComponentData(entity, new RenderMesh() { mesh = render.mesh, material = newMaterial });
    18.             });
    19.         }
    20.     }
    Anyone knows What should I do?
     
  12. vildauget

    vildauget

    Joined:
    Mar 10, 2014
    Posts:
    120
    I think you problem might be that you send the same random seed into the ForEach, so all the entities get the same random value from the rnd.NextInt.

    There are some threads discussing this, e.g. this thread.
     
  13. AlejandroAlternova

    AlejandroAlternova

    Joined:
    Oct 30, 2019
    Posts:
    6
    Thanks vildauget,
    Actually the problem was that I was using the same instance of the material.
     
  14. angel1st

    angel1st

    Joined:
    Nov 16, 2019
    Posts:
    35
    @AlejandroAlternova - how did you solve your issue? Would you be so kind and share a code snippet with us? Thanks in advance!
     
  15. AlejandroAlternova

    AlejandroAlternova

    Joined:
    Oct 30, 2019
    Posts:
    6
    Hey angel1st, sorry I don't remember as I abandoned the project once I determined I wasn't able to manage z rendering in the shader, What I'm able to see from my code is that I was using LayerEntityCreator.Instance.LayerMaterialSource which is a unique material and that made changes to all the instances using it.