Search Unity

ECS Chess MegaThread

Discussion in 'Data Oriented Technology Stack' started by FrankvHoof, Apr 29, 2019.

  1. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    104
    ECS Chess MegaThread


    Main view of the Game

    What is ECS Chess?
    ECS Chess is an implementation of Chess using the new ECS-paradigm in Unity.
    It is an example of a full implementation of a game in ECS, to serve as an example for future reference, and as a test to determine the (current) viability of ECS when used to create a full game.

    Why this thread?
    This thread exists to prevent myself from spamming the forum with questions.
    By creating a single thread, and updating it, I can create a single place for all of my struggles while implementing. It also allows others to see the problems I run/ran into, and how these can be solved.

    Source
    The full source for the project can be found at https://github.com/frankvHoof93/ECSChess
    I currently do not take in any pull requests/submissions, as I'm still trying to wrap my head around the implementation of DOD that Unity uses, but I will open the repo up at a later point, to allow for people to implement 'best practices', visual additions etc.

    Current Progress:
    Currently the project contains systems to process raycasting (hover & click), and a system to process movement.


    (most of the) Archetypes currently in the Game

    Current Tasks:
    - Set up main game logic system (turns, win-conditions, etc.)
    - Fix visual elements (hover-visual, selection-visual, board-tiles, etc.)

    Later Tasks:
    - Animation (death, etc.)
    - Audio
    - Particle Effects
    - Move RayCast to Unity Physics (Instead of Bounds.IntersectRay())


    Current Questions/Problems:
    The main problem I'm currently running into is:
    - Where do I put my 'main loop'-logic? (i.e. my GameWorld.Update())
    By this I mean the code which either determines which Systems should/shouldn't run, or which holds/updates the 'main state-machine' for the game (and thus controls things like StartGame, GameOver, Next Turn, etc.)
    Current reasoning for implementation:
    I'm thinking I'll want a system which runs at the end of the frame, and tracks which actions were performed this frame, then updates a SingletonEntity, which holds the 'GameData' (such as state). However, this system would need to be able to get a list/array of all the components added/removed this frame, in order to work out what actually happened.
    Other things I'm currently trying to figure out:
    - How do I prevent a system from running for a single frame?
    The user can (generally) perform one of two actions in a single frame;
    a. Select a ChessPiece
    b. Move a ChessPiece (if movement is valid)
    Current reasoning for implementation:
    The way I'm currently looking to handle a mouse-click is to run the Movement-System first, and to prevent the SelectionSystem from running if a movement was performed. However, I can't disable a system from a Job (which is what the SelectionSystem ends up in), so I'm not sure how to (spawn an entity with a tag I guess?), and I also need to re-enable the system at the end of the frame, so that it can run again in the next frame (I'm guessing I want some sort of EndFrameSystem for this?)
    - How do I track if a component has been removed?
    I wish to visually indicate the moves a piece can make whenever a user hovers over or selects a piece. However, when doing this, I also need to remove any previous visuals when the hover/selection ends.
    Using SetFilterChanged on a query appears to work when a Component is added/changed, but not when it is removed. How do I track the removal of Components (such as Hovered)?
    - Does the current implementation for rendering have support for per-entity visual adaptations (i.e. MaterialPropertyBlocks)? (and if so, how do I use it?)
    It currently looks like I need to write my own rendersystem to get any support for MaterialPropertyBlocks and/or GPU-Instancing at this point in time. Is this correct? (I'm mainly looking at highlighting certain BoardTiles in certain colours here)


    I'm hoping people can help me answer these questions and/or increase my general understanding of how to actually create games using DOD (and not just tech-demos).
     
  2. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    104
    Reserved

    This post is reserved for FAQ & past questions/resolutions, to prevent people from having to read all the way through the thread in order to find answers to previous questions/problems.
     
  3. digitaliliad

    digitaliliad

    Joined:
    Jul 1, 2018
    Posts:
    63
    Thanks for making the forum community part of this process, Frank!

    I've also been thinking about where/how to implement gameplay loops, and I believe we're mistaken in thinking we need to come up with some special way to process game logic compared to more "mundane" ecs logic: the same tag-heavy component-based system is definitely the way to go. I don't think you need a Singleton to track state, just some tables.

    Richard Fabian's Data-Oriented Design recommends using table-based finite state machines, which goes perfectly well with the structure of ECS and our IJobForEach workflow. To oversimplify the points he makes in the chapter on finite state machines (which is sadly not available as part of the free online version of his book), the best practice would be to create table entries (components/entities) in "condition" tables that our gameplay systems parse over, triggering changes to one-or-more finite state tables (depending on whether you want a plurality-of-states or a single-state machine). This is probably obvious to everyone here, but the nature of a table-based structure is already a significant improvement over most OOP finite state machines.
     
  4. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    104
    What's the (intended) best way to get those tables into my different systems?
    Just make them public, and grab a reference to the system they're coming from through EntityManager.GetExistingSystem()?
    Or hook them up to a Entity, and query for it?

    Also; This version ( http://www.dataorienteddesign.com/dodmain.pdf ) seems to include the chapter on finite state machines, so I'll be reading it this weekend, when I get the time..
     
    Last edited: Apr 30, 2019
  5. digitaliliad

    digitaliliad

    Joined:
    Jul 1, 2018
    Posts:
    63
    There's no need for any fancy reference system: the tables are built into ecs, so an "entity" is merely a consistent index across multiple component tables. EntityQuery already provides an adequate way to grab entities for your systems to process, and IJobForEach/IJobForEachWithEntity already provide the perfect way to process said query.

    One example of table-based state might be a CheckSystem that looks at each piece of one side in relation to that side's king; if a piece is within striking distance of the king, a Check (the chess term for a king in mortal danger) entry could be created. There could even be a MateSystem that looks for Check entries, and if there's more Checks against one side than that player can deal with on their turn, generates a CheckMate entry. A further GameOverSystem could look for entries like CheckMate that signal the end of the game, or in the case of Speed Chess, a player running out of time could also signal the end of the game. Like I said, the real benefit of table-based state is the plurality of conditions and consequences that can be independently processed.

    Anyway, I'm quite glad there's an available version of Fabian's DOD that includes the finite state machines chapter: he'll be able to explain this stuff much better than I can.
     
  6. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    104
    I get this part, but what I don't understand is how to (ideally) tell this system which side's currently at play (i.e. who's move it is), because these systems only need to check one side at once.
    I've also already written a "GatherBoardSystem", which gathers all the pieces into a nativehashmap (by their position), so that I can easily set up a 2D-Array for checking & processing.
    However, this system would only need to run once after a move has been made, and shouldn't be running every frame.
    What I'm trying to wrap my head around is how to envelop my systems to determine which need to run and which don't (especially in circumstances where this is more dependent on say an enum or boolean (e.g. IsWhitesMove(), MoveSuccesFull(), etc.) than the existence of an entity with a component).

    Some further detail:
    When a player clicks, a Raycast-Job is done. Depending on the result of this Raycast, I either want to run nothing, selection, or movement (depending on the current state of selection & turns). If movement is run, I want to run my GatherBoardSystem when this movement finishes (which is when the Destination-Component is removed), but only once, as the boad doesn't change that often.
     
  7. digitaliliad

    digitaliliad

    Joined:
    Jul 1, 2018
    Posts:
    63
    Why not use the same kind of finite state machines to decide which other systems to run each frame? For example, the raycast will either hit a piece, or not, and if it does, you could create a selection entity that will be found by any system looking for selections. If that selected piece is then moved, you could create a movement entity. There could be a combat solver that evaluates whether a movement will destroy another piece. With this being chess, you'll want to record all meaningful events in the game so you can build a history of the game for analysis and replay purposes, anyway. It makes sense to record game events as tagged entities rather than enumerators.

    The point is: a system runs when its entity query returns entities to process. To get a system to run, or not, you just need to give them entities to process, or not. There are probably better patterns of usage out there than a simple setup with producers and consumers of tagged entities, but it seems like an intuitive place to start.
     
  8. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    255
    You need to read how ECS works first. Basically, your logic is in your systems, every system does is specialized in doing one task. For example, you can have a system that checks if there is checkmate and it can be set up so that it only runs when a position of any chess piece changes.
    Systems run when exists entity with data that this system operates on.
    Read about SystemStateComponents:
    https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/system_state_components.html

    No MaterialPropertyBlock is not supported yet. GPU instancing is supported but in the game of chess, it is not needed as much.
     
    hippocoder likes this.
  9. FrankvHoof

    FrankvHoof

    Joined:
    Nov 3, 2014
    Posts:
    104
    Two things though;
    1. If I'm in the main menu, but want to render out my ChessBoard in the background, that would mean I'd either need to Remove all the components used for the game (and add them when the game starts), or use 2 seperate prefabs?
    2. In some circumstances this seems highly counter-intuitive.
    Take for example my Movement-, & SelectionSystem:
    If the result of my movement-system is FALSE, then my selectionsystem should run (same ray, same click, etc.).
    However, if my movementsystem is TRUE (i.e. a move should be performed), I add a heading and destination to the piece in order to move it. In this case, I want to prevent my selectionsystem from performing a selection in that same frame, but only for that single frame (the frame in which the heading & destination were added), as I still want the player to be able to select other Pieces while this one is moving.
    I'm guessing I should add a query (with changedfilter) to my movementsystem to look for Heading/Destination, then just return inputDeps if that query finds an Entity (at which point it seems counterintuitive to even run the system)?
    The other option would be to spawn an Entity with a tag when my job FAILS, to signal the SelectionSystem to run, then destroy it again.. Creating & Destroying an entity every frame also seems pretty counter-intuitive..

    See above. What if I need (entity.Exists && bool)? I'd feel like I could do so much more if I could for instance set a (additive) delegate to ShouldRunSystem(). Again, I can of course return inputDeps at the start of OnUpdate, but that seems highly counter-intuitive.

    I had been looking at those, but they seem to me to be made primarily for resource-cleanup (i.e. disposing textures & what not)? That might just be mis-interpreting them, but I got the feeling they were only made to catch Destroy's, not RemoveComponents. Haven't had time to actually test it yet.

    That's fine for me for now. I've already written out a simple render-system with MPB-support for a different project, so I can use that instead. I was mainly looking at doing tile-highlights etc. through MPB's (as that seems to me to be the easiest way to do it).
    Is there an ETA on support for MPB's by default that you know of?



    I also want to try to make this example as generic as possible, to serve as a basic (but scaleable) example to look back at. Adding and removing components (especially when dealing with large groups) so far seems pretty heavy, and thus not something you want to do every frame. (I had a system adding/removing "IsInViewComponent"-s to do frustum-culling for a different project, but setting a bool-value inside the component and looping over everything was still faster than adding/removing the component)...
     
  10. Micz84

    Micz84

    Joined:
    Jul 21, 2012
    Posts:
    255
    You can add a group as required for updated in OnCreate
     
  11. hippocoder

    hippocoder

    Digital Ape Moderator

    Joined:
    Apr 11, 2010
    Posts:
    26,075
    Really fun project, following!
     
    FrankvHoof likes this.