Search Unity

Question Review my first project built with DOTS and help me improve

Discussion in 'Entity Component System' started by Envilon, Feb 21, 2021.

  1. Envilon

    Envilon

    Joined:
    Aug 8, 2020
    Posts:
    55
    Hello everyone!

    Last semester I worked on two games as school projects. It was actually my first real experience with Unity, and with one of the games, I jumped straight to DOTS. It might have been a mistake, but I think I learned a lot. I created a small 2D space combat sandbox (more like a simulation than an actual game) with some boid simulation and so on. I want to continue with the project, but I feel like I should take a step back, rethink the current state and improve it. That's where I could use some of your feedback on what I did right, what I did wrong, or what I seem not to understand properly.

    rts_1.png

    If you'd like to look at the code (I would really appreciate it), you can find the project here https://github.com/EnviloN/SpaceRTS-ECS. I tried to comment and document everything, so it should be fairly easy to understand. Also, the comments reflect my understanding of the ECS, so there might also be something to give feedback on.

    rts_2.png

    About the project
    I think the project is well described in the README.md of the aforementioned repository. Here I would like to point you to more specific parts of the project (if you don't feel like digging through the code) I think might be problematic. I will also list some systems implemented in the game so that you get an overview.

    Implemented Systems
    • Input System
    • Camera Movement
    • Starship Spawning
    • Unit Selection
    • Unit Highlighting
    • Starship Movement System
    • Flocking System (boids, steering)
    • Targeting and Combat Systems
    • Quadrant System (spatial partitioning for flocking and hit detection)

    Potential problems
    Input system
    I have one central input system that is (I believe) listening to all input callbacks and handling the user inputs accordingly. What now feels a bit inappropriate to me is that I do all the handling in the OnUpdate method, where I basically do several Entities.ForEach calls for each component specifically created for this input data and set the input values to it. These components are usually on only one of the entities (e.g., camera or cursor). I feel like some message passing would be a better solution?

    Camera
    Basically, when I was working on the camera movement system, I didn't find a way how to work with the camera in the ECS. I created one entity called CameraRig that I work with in the ECS (I move it with a camera movement system), and another MonoBehavior script on the camera takes the value from the CameraRig's Translation component and sets the camera's transform.position accordingly. I also use perspective projection on the camera to perform the zoom by moving the camera along the Z ax. Is there now a better way to go about this?

    Unit Highlighting
    This was a big pain. I have no idea how to do this, and I have no idea what I have done to get it working. I was trying to do several different things—one of them being creating a custom renderer. In the end, I was able to get this done by creating a ShaderGraph that is using a color property exposed in a component, which I can change in the systems (if I understand correctly). I had no idea what I was doing, and I was desperate. I feel this solution is by no means scalable and extensible.

    To be completely clear, I did not aim to do the highlighting by changing the units' color. I wanted to display a circle or a glow around the selected starships. I also wanted to create a selection rectangle displayed when you are trying to select some units. I had no luck there.

    Shared Components
    There is a BoidComponent attached to every starship entity, that contains the configuration of the boid behavior. Logically, this should be a shared component, but I was unable to use the AddSharedCompponent in the ShipSpawnerSystem because apparently, I cannot use it with CommandBuffer and or Burst? No idea how to go about this.


    Thank you for reading through this and for any potential insights and feedback! :)
     
  2. jdtec

    jdtec

    Joined:
    Oct 25, 2017
    Posts:
    302
    Looks cool, got a video anywhere to view?
     
    Envilon likes this.
  3. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Few suggestions:

    Haven't dived deep into code, but a simplest solution would be to "draw" entities (or create / sustain entities) that are positioned at spaceship position which are selected. That's what I did a back while ago when testing out DOTS.
    Code (CSharp):
    1. protected override void OnUpdate() {
    2.             // Query entities for those that have SelectedUnitTag component
    3.             var allSelected = GetEntityQuery(ComponentType.ReadOnly<SelectedUnitTag>())
    4.                 .ToEntityArray(Allocator.TempJob);
    5.  
    6.             // Update all spaceships
    7.             Entities.WithAll<SpaceshipTag>()
    8.                 .ForEach(
    9.                     (Entity entity, ref MaterialColor color, in TeamComponent team, in TargetingComponent target) => {
    10.                         color.Value = allSelected.Contains(entity) ? (Vector4) Color.white : team.TeamColor;
    11.                     }).Schedule();
    12.  
    13.             allSelected.Dispose(Dependency);
    14.         }
    This can be collapsed into two .ForEaches instead of one (.WithAll<SelectedUnitTag>, .WithNone<SelectedUnitTag>, removing branch and query fetch. Spawning / despawning entities with circle renderer should be pretty trivial to add based on these two queries.

    Selection box can be drawn by resizing UI box on the screen (although its not really DOTS approach). Can be a bit tricky converting viewport -> screen space, but it should work.

    Alternatively, plane can be resized and drawn, if you prefer more direct approach.

    Shared components attach to the chunk instead of entities, meaning all entities within that chunk have that component. Plus, they're considered as managed type, even if doesn't contain one. CommandBuffer doesn't support managed types, that's why you can't use it for playback.


    Also think selection should be player based, instead of every ship.

    Other than that, neat little prototype, I like it.
     
    MNNoxMortem and Envilon like this.
  4. Envilon

    Envilon

    Joined:
    Aug 8, 2020
    Posts:
    55
    Sure, I have some short footage but I didn't think of putting it here. I just uploaded it here to view:



    It's not much but I plan to do some devlogs later on (if school and work allow it timewise).
     
    Ghat-Smith, jdtec and Antypodish like this.
  5. Envilon

    Envilon

    Joined:
    Aug 8, 2020
    Posts:
    55
    I can't believe I didn't think of this. Seems a lot better than what I did. :)

    I can't say I didn't think about this, but there were two problems I could not solve:
    • It seemed like a bad idea to delete all highlight entities (let's imagine circles around the ships) and recreate them at the updated positions every frame. Therefore I wanted to create them when the ships are selected and sustain them until the selection is canceled (as you are suggesting). But...
    • I could not figure out how to create a Highlight entity with a "parent" Ship entity (or something along those lines). Because I imagined that there needs to be some relationship between the ship and its highlight to figure out the ships' position and if it is selected.
    But I am probably thinking about this with an Object-oriented mindset... How could I design this?

    Back when I was trying to do this, I searched for a DOTS solution and couldn't find anything. Is it even possible to stay "pure DOTS" with the current state of the DOTS? For example, I don't know if it is possible to create and handle game UI in DOTS, so I might solve the selection rectangle in a non-DOTS way as well.

    It makes sense; thanks for the explanation. Does it mean that there is currently no way to fix it, or should I rethink the spawning system? Or is it not that memory wasteful as I think it is to have it as a non-shared component?

    Not sure what you have in mind. :/

    Most of all, thank you for the feedback! :)
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Actually, creating lots of entities works just fine if all entities created at the same time (not one by one). This can be done either by EntityManager with NativeArray overload, or via ECB. +Archetype.

    As for the second part, algorithm is pretty straight-forward:
    - Query selected entities, without "selection" reference data. It could simply contain Entity "reference".
    - Instantiate entities with ECB or supply native collection with pre-created "selection" entities;
    - Patch entity references in "selection" (this is spaceship side);
    - On the instantiated entity, add similar component that links to the ship (Entity + float3).
    - Write a system that runs on component attached to the selection circle - copy existing Position from the ships either via ComponentDataFromEntity (GetComponent<T>) or via native collection.

    This depends on how you read / write position (CDFE cannot be used on same components that is written). Its possible to copy position and store it inside selection data on "selection" entity. Or it could be copied via native collection and query. Usually CDFE + storing on the entity is faster (at least it was in my cases).

    - Apply values to Position component of selection in different system after copying.

    "selection" data can be ISystemStateComponentData, which would allow querying .WithAll<"selection">.WithNone<"spaceship"> when spaceship is destroyed. Entities with ISystemStateX will be kept alive even when Destroyed, with all data attached available to cleanup selection.

    De-selection can also be queried by .WithAll<"selection">.WithNone<"Selected"> allowing to cleanup state as well.

    Nope, and you don't need to.

    As .AddComponentData<T> exists, that allows to directly manipulate Object's from ECS side. Although its main thread access only (.WithoutBurst().Run()).

    Haven't looked into it, but in theory you should only keep data that is used in runtime. Data that is "settings" can be either stored in system, blob, or even ScriptableObject.

    Both teams can be controlled at the same time. That's not exactly how strategies work.
    Although that's just me being nitpicky, for the purpose of sandbox its just fine :)
     
    Last edited: Feb 21, 2021
    Envilon and jasons-novaleaf like this.
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I don't think that was a mistake. For the level of quality I'm seeing in this "first-time" project, it is clear you have a natural intuition for DOTS. Welcome to the community! Can't wait to see how far you go!

    Input System
    Can't comment much on this because I use the raw Input System API currently. However, there's a DOTS branch stickied in the Input Systems subforum that you might be interested in trying. Been meaning to try it out myself.

    Camera
    It used to be you could use the scripting define HYBRID_ENTITIES_CAMERA_CONVERSION to make an entity camera when combined with ConvertToEntity (doesn't work with subscenes). However, I recently discovered a Windows Update was capable of breaking such functionality. So I also switched to a "rig".

    So what I do is I have my main camera as a GameObject, and then I made a child GameObject with ConvertToEntity and this component: https://github.com/Dreaming381/lsss-wip/blob/master/Assets/_Code/Components/CameraManager.cs
    The result of conversion is that there is a Camera GameObject with no child, and an entity with a hybrid CameraManager component that references the camera. Then because I was relying on being able to update the camera transform asynchronously (syncing a transform component to the main thread is expensive in my project), I used this system to update the camera transform in a job: https://github.com/Dreaming381/lsss...Graphics/CameraManagerCameraSyncHackSystem.cs
    Side note: SubSystem is a SystemBase subclass, and Fluent is just a utility I wrote for streamlining building EntityQuery instances. Everything else is standard Unity and DOTS.

    Highlighting
    Hybrid Instanced Properties is usually the way to go for this sort of thing. There's a couple of other alternatives, but what you have now (with the recent update) is fine. You can get the glow with a texture for your units that has a fringe region that is transparent until highlighted, and then glows. You can also have a child entity for the highlight and use the DisableRendering component until the ship is highlighted.

    There's no standard way to do UI in DOTS right now. But for simple UIs, it is common to have some hybrid component reference things on the UI that a system then navigates and manipulates.

    I did notice you aren't using ENABLE_HYBRID_RENDERER_V2. Try adding that scripting define and see what it does to your main thread times.

    Shared Components
    This is ugly right now. And avoiding shared components temporarily was the right decision. 8 floats is only half the size of LocalToWorld, so memory usage may not be that bad. But if you want to optimize it, an Entity reference is only 2 ints, which would be a 75% savings. While that does introduce a random access and an ALU cost, the referenced entity's data would stay in cache since a lot of entities use it.
     
    Envilon and jasons-novaleaf like this.
  8. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Forgot to mention, that its possible to read / write on the same component type in a single job. However, you'd need to disable safety checks (e.g. via [NativeDisableParallelForRestriction]). In this case you must be certain that those entity queries never overlap to avoid race condition on your own. This way its even faster (no copying data around), but no safety either.

    Here's also a presentation on data relationships (entity interactions) which is very relevant:
     
    Last edited: Feb 22, 2021
  9. jasons-novaleaf

    jasons-novaleaf

    Joined:
    Sep 13, 2012
    Posts:
    181
    I skimmed your code, and great job on this. Everything is very easy to understand, especially with docs saying what you are "high level" doing. I'd dare say that you have near created a perfect intro tutorial for dots. When I get a chance I'll look through the code deeper to see if I can give better feedback.
     
    Envilon likes this.
  10. Envilon

    Envilon

    Joined:
    Aug 8, 2020
    Posts:
    55
    Sure :D It was only a prototype. Since I had no time to introduce some AI, I needed to control both teams in order to lead the teams to combat.

    I'll look into that, thanks!

    I might test this and see what impact it has. I would also expect it would be cached all the time.

    It sounds like a step forward I could do. I'll explore your code and get inspired.

    Thanks! I also think that when finished, it might be a helpful resource for beginners even though it might not be a particularly original or interesting game to play.

    Hey @xVergilx and @DreamingImLatios, thanks for the feedback and proposed solutions for the unit highlighting! I'll have to try these things and probably do some additional research because I feel I'm missing some background to understand fully. Also, thanks for the presentation; I will dive right into it!
     
    xVergilx and DreamingImLatios like this.