Search Unity

Dealing with Relational Data

Discussion in 'Entity Component System' started by Vacummus, Oct 23, 2019.

  1. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    I have two entities: a camera entity and a player entity. And the player entity has a Room component, which keeps track of in what room the player currently is in. Depending on what the current room is, I would like to update the zoom value of the CameraZoom component on the camera entity. What's a good DOD way to achieve this?

    Currently, I have several entities with a OnRoomChangedSetCamerZoom component. This component holds the camera and player entity, zoom value that needs to be set, and a dynamic buffer of rooms that is used to compare against the Room component of the player entity to know when to set the zoom value on the camera entity. This solves my problem, but is this a good way to lay out my data?

    Here is a visual example of my data layout:
    Code (CSharp):
    1. Player Entity: [Room]
    2. Camera Entity: [CameraZoom]
    3. SetCamerZoom Entity1: [
    4.   OnRoomChangedSetCamerZoom{cameraEntity, playerEnity, zoomValue: 10},
    5.   DymamicBuffer<RoomElement>[Room.Bedroom, Room.Bathroom]
    6. ]
    7. SetCamerZoom Entity2: [
    8.   OnRoomChangedSetCamerZoom{cameraEntity, playerEnity, zoomValue: 20},
    9.   DymamicBuffer<RoomElement>[Room.LivingRoom, Room.Kitchen]
    10. ]
     
  2. fellowunitydeveloper

    fellowunitydeveloper

    Joined:
    Oct 23, 2019
    Posts:
    7
    Looks good enough, but I'm not sure about these entity references in the OnRoomChangedSetCameraZoom alongside with the camera zoom value. Is it an event? If so, it sure should have references to the camera and player entities.

    But the several entities you've mentioned could keep only the RoomElement buffer alongside with the respective camera zoom value to be set, and then you could match the fired event against these entities.

    One way or another, it should work even with multiple players and cameras :)
     
    nantoaqui and Vacummus like this.
  3. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    Sort of like an event. I have a system that queries all entities with a OnRoomChangedSetCameraZoom component. It then uses the playerEntity reference to get its Room component, and compares that value against the RoomElement buffer values. If there is a match, then it uses the cameraEntity reference to set the zoomValue on its CameraZoom component. From a performance perspective, I am not too happy about this due to cache misses this will incur, but I doubt it needs optimizing since I won't be having too many entities with the OnRoomChangedSetCameraZoom component.
     
  4. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    Alternatively, I thought about changing the OnRoomChangedSetCameraZoom component to a bufferElement that would look like this:

    Code (CSharp):
    1. public struct OnRoomChangedSetCameraZoom : IBufferElementData
    2. {
    3.      public Room room;
    4.      public Entity cameraEntity;
    5.      public float zoomValue;
    6. }
    And then I would put that on the player entity, which would change my data layout to something like this:
    Code (CSharp):
    1. Player Entity: [
    2.   Room,
    3.   DymamicBuffer<OnRoomChangedSetCameraZoom>[
    4.      { room: Room.Bedroom, cameraEntity, zoomValue: 10 },
    5.      { room: Room.Bathroom, cameraEntity, zoomValue: 10 },
    6.      { room: Room.Kitchen, cameraEntity, zoomValue: 20 },
    7.      { room: Room.LivingRoom, cameraEntity, zoomValue: 20 },
    8.   ]
    9. ]
    10. Camera Entity: [CameraZoom]
    Thinking about it some more, it might be good to take the cameraEntity out of OnRoomChangedSetCameraZoom and just put it into its own component that is set it on the playerEntity since the cameraEntity will always be the same.
     
  5. paul-figiel

    paul-figiel

    Joined:
    Feb 9, 2017
    Posts:
    9
    You shouldn't do that with Events, because at some point, you're gonna need some smooth transition between your rooms I guess.
    I'll do it this way :
    Attach a CurrentRoom component tag to the current room entity (this room entity has a Room component and a LocalToWorld component).
    Then, just have a system that lerps your Camera position to the LocalToWorld component of the only Entity matching [Room] , [LocalToWorld] , [CurrentRoom].
     
  6. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    I would also recommend doing this with events (a separate entity with a single RoomChanged component, which is created when the change happens and is deleted within one frame).

    One of the main reasons to recommend this is that it will also let you avoid treacherous archetype fragmentation: adding a new OnRoomChanged component to the entry which just changed rooms will make Unity move it to a new chunk. Not only is there a cost associated with this move, but It can also slow down your job code (as it may now need to jump between more chunks).

    When possible, you may find it best to keep your entities with the same group of components throughout its lifetime. Things like room changes can be handled through events. And you can still use the same type of system logic to iterate over every event entity with this new component type. :)
     
    Vacummus likes this.
  7. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    I would do it this way
    - each room is an entity and has Zoom component on it with appropriate zoom level value for this room
    - the player entity has a reference to the current room entity,
    - when the player enters the room - create an event-entity EnterRoomEvt{Entity:Room}
    - camera system reacts on event entity, reads zoom value from the room and animates the camera properties

    no buffers - it is a wired idea to store zoom values for all rooms on the player entity, what if at some point you decide to have 100000 rooms... or for example later on you decide to make this game multiplayer, and the camera should follow a given player...
     
    Last edited: Oct 24, 2019
  8. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    Great feedback! I wasn't aware of the idea behind an event entity. Def seems like the way to go here. Now is there an official way to mark the event entity so it is removed at the end of the frame? Or is this clean up something I would need to write myself? Not a big deal if I do.

    Love it. That's a great way to layout the data.
     
  9. fellowunitydeveloper

    fellowunitydeveloper

    Joined:
    Oct 23, 2019
    Posts:
    7
    Entity as event is a nice pattern, an entity which has the duration of one frame.

    There is no official way to mark an entity as event as far as I know.

    It's a good practice to have the system which creates the event also destroy it, this ensures that every other system can listen to this event independent of execution order.

    You may destroy the event query in the beggining of the event system execution and create new ones that are stored in a queue which is accessed from other systems that must fire the event, for example.
     
  10. Vacummus

    Vacummus

    Joined:
    Dec 18, 2013
    Posts:
    191
    What the reason it's good practice to destroy the event from the same system that created it? Would it be better if I had a tag component called EntityEvent that I tag onto my event entity when it's created, and then just have a system that queries for all entities with `EntityEvent` and destroys them at the end of the frame? That would allow for all my systems that are creating entity events to not worry about destroying them.
     
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,264
    As long as you have a good handle of your system order, destroying the entities at a unified sync point is better than having a sync point after every system. I personally don't like using entities for events because I can store the same event data in some other NativeContainer and not introduce a sync point.
     
  12. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    For what it’s worth, I usually don’t use the “have system which created the event destroy old ones” pattern, since sometimes it can be valid for multiple systems can create the same type of event.

    instead, I usually schedule the destruction of the event entity with an entity command buffer that I know will run after every system that needs to process the event. If you want, this destruction can even be scheduled at the same time that you create the event entity.

    You can also use the EntityManager batch functions, to just blanket destroy all chunks containing all event entities, after you’re sure you’re done with them. That will likely be the most performant.
     
  13. fellowunitydeveloper

    fellowunitydeveloper

    Joined:
    Oct 23, 2019
    Posts:
    7
    Well, there are so many ways to implement an event system, I usually create a centralized event system using queues and the creation and destruction happens there, in batch. So I don't have this problem of other systems creating the same type of event and don't have to worry where the destruction happens and execution order...

    Entity as event is good but have to be used wisely, where it makes sense. One of its main benefits is avoiding frequent archetype changes. It's main drawback is to have to access data from entity indirectly.
     
  14. SubPixelPerfect

    SubPixelPerfect

    Joined:
    Oct 14, 2015
    Posts:
    224
    for events there are many options:
    1) by event component location:
    - a. put event-component on the event source (player or room in this case).
    - b. put event-component on the event target (camera in this case).
    - c. put event-component on the new entity.
    2) by event destruction time/place:
    - a. the same system that invoked the event responsible to destroy it.
    - b. event consumer system responsible to destroy event.
    - c. destroy at the end of the frame
    3) by event creation time/place:
    - a. attach event in the system that invoking it.
    - b. attach event at the end of the frame using a dedicated event system.

    my choice is: 1c 2c 3b (put event component on the new entity, create and destroy it at the end of the frame using the event-manger system)
    there is a great ready solution by @tertle https://github.com/tertle/com.bovinelabs.entities
    that uses exactly this approach