Search Unity

Combo sequence in ECS

Discussion in 'Entity Component System' started by dan-kostin, Feb 16, 2019.

  1. dan-kostin

    dan-kostin

    Joined:
    Oct 19, 2018
    Posts:
    22
    I've stuck with grouping combo operations in ECS. In OOP way I have a linked list, that contains array of buttons on each node those need to be pressed to go to the next node.

    For example W+D -> A -> S+D

    1) Is it appropriate to do it in ECS?
    2) Is it possible to do it with using of burst compiler?
    3) Is SharedComponetData the best for this case?

    I see the solution like following, but I doubt in it, because I can manage it only in ComponentSystem and cannot in Jobs
    Button - ComponentData
    Group - SharedComponentData (contains nativeArray of Button Entities, Entity of the next Group)
    Cursor - ComponentData(current Group Entity)

    My problems are:
    a) I can't check in Job does Button have some component or not.
    b) I can't use JobSystem for it.
    c) My Buttons data is divided by SharedComponentData to different chunks. Maybe it is not so terrible because I have no more then hundred buttons
    d) I'm a maniac of the best performance
     
  2. starikcetin

    starikcetin

    Joined:
    Dec 7, 2017
    Posts:
    340
    Perhaps something like this would work:

    Struct
    1. KeyPressGroup { List of KeyCode }

    Component
    1. KeyPressChain { List of KeyPressGroup }
    2. ComboFoo {}
    3. ComboBar {}
    4. ComboBaz {}

    System
    1. KeyPressChainToComboConverter
    2. ComboConsumerFoo
    3. ComboConsumerBar
    4. ComboConsumerBaz
     
  3. dan-kostin

    dan-kostin

    Joined:
    Oct 19, 2018
    Posts:
    22
    Thank you for reply.
    I understand from your post that ComboFoo is one of a combo tags that exists together with KeyPressChain on one Entity.
    I have different combo combinations and there are much more than 3.
    KeyPressChain should be SharedComponentData because contains a List. My third question is "Is SharedComponetData the best for this case?"
    So as I understand ECS this solution will work only in ComponentSystem and won't in JobSystem
     
  4. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    I think the big problem new users of ECS face is they get hung up on SharedComponentData.
    To me the solution to your problem isn't that difficult, if I find some time tomorrow I'll write something up but for now let me ask you this.

    If SharedComponentData did not exist, how would you solve this problem with ECS? Because apart from the rare occasions where you need to group entities by a managed object (eg. material, mesh) and work on them on the main thread, no solution needs SharedComponentData and you should not consider it except for optimization in the future.
     
    dan-kostin likes this.
  5. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    I'm working on something similar, I'm just storing the current buffer information in IBufferElementData, and the same for the combination definition, albeit with timing window information.
     
  6. dan-kostin

    dan-kostin

    Joined:
    Oct 19, 2018
    Posts:
    22
    I've thought to attach a tag component with an Integer index of a Group. But this follows to iterate all Entities each frame, to filter by this index.

    The best solution that I know now is to have SCD with groups, but on other Entities than Buttons, so Buttons are in one chunk and have pointers from groups. It allows to take Group by Cursor Entity and check 2-3 Buttons by pointers on the main thread. Also it allows to work with Buttons+tags as events on JobSystem.

    But it seems that there should be some better solution.
     
  7. dan-kostin

    dan-kostin

    Joined:
    Oct 19, 2018
    Posts:
    22
    Isn't it performance expensive to filter all Entities with data each frame?
    Do you have additional tag-components to highlight current "frame" (Group)?
    How do you switch from one Group to another?

    I also have time data in group for waiting, because it's impossible to press two Buttons in one frame simultaneously.
     
  8. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    I have different per-frame
    DynamicBuffer<T>
    s for Movement/Jump/Attack/Etc events, since you can get multiple events for the same action per frame. Internal buffer capacity for events is 2 for buttons and 4 for moves, which is about the max a normal human can pull off for 1/60th of a frame. Since I've never seen it exceed those numbers, the buffers are almost always guaranteed to stay in in the archetype and not allocate an outside buffer. After the raw input is collected with a ComponentSystem, I denoise it with jobs before doing the rest of the pipeline.

    I'm still working on the combo processing bufffers (RN focusing on visually representing the per-player buffers for debugging purposes) but the input buffers will be read-only for the combos.

    The combos will just be another set of buffers, allocated on their own. Character states will reference them and have their own list of allowed combos per-state.
     
  9. AndesSunset

    AndesSunset

    Joined:
    Jan 28, 2019
    Posts:
    60
    Here’s another approach. This is straightforward, would work with jobs and Burst, and be performant:

    Split your combo detection into phases: one for each set of simultaneous button presses:

    - Phase One: W+D
    - Phase Two: A
    - Phase Three: S+D

    Each phase would have a corresponding component. In your case, that would look like this:

    1. AttackComboPhaseOneComponent
    2. AttackComboPhaseTwoComponent
    3. AttackComboPhaseThreeComponent

    Before any input has been entered, an entity representing the player would have only the first phase Conponent (in this case, “AttackComboPhaseOneComponent”).

    Each phase would also have a corresponding System, which filters for that phase’s Component type.

    These systems would test to see if that phase’s buttons had been pressed on that frame. If they had, the System would remove that phase’s Component, and add the next phase’s Component (thus moving detection of the combo onto the next phase).

    For your example, that would look like this:

    1. AttackComboPhaseOneSystem: checks for W+D input.

    2. AttackComboPhaseTwoSystem: checks for A input.

    3. AttackComboPhaseThreeSystem: checks for S+D input.

    If AttackComboPhaseThreeSystem detects the correct input, it can reasonably assume the player has correctly completed the combo. It would then:

    1. Remove the AttackComboPhaseThreeComponent from the player entity.

    2. Add an AttackComboPhaseOneComponent to it again (restarting detection of the combo from phase one).

    3. Add a new “AttackComboEntered” Component, which will record that the player entered that combo.

    On the next frame, a separate “PerformAttackComboSystem” System would filter for this new Component type, and perform the actual combo animations, damage, and etc. logic.

    This is a specific implementation, but hopefully it’s easy to see how this approach could be modified to support things like canceling combos if the user waits too long, canceling detection of other combos while one is in progress, performing a combo on the same frame that it is successfully entered, waiting for a combo to complete before resuming detection of the combo, etc.

    Best of luck with what you’re doing! Let me know if I’ve missed the mark.
     
    Last edited: Feb 18, 2019
    starikcetin likes this.
  10. dan-kostin

    dan-kostin

    Joined:
    Oct 19, 2018
    Posts:
    22
    Thank you, your question. It've moved me a bit forward. Bacause I don't have only three combos, but hundreds. My new sollution:
    Code (CSharp):
    1. NextGroup: IComponentData { Entity Next; } // Horizontal list
    2. NextSibling: IComponentData { Entity Next; } // Vertical list
    3. ActiveGroup: IComponentData {}

    It does not solve all the problems but more convenient than SCD.
    It is still need main thread to activate/deactivate Group.