Search Unity

Official State Machine

Discussion in 'Open Projects' started by Dustin_00, Sep 29, 2020.

  1. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Profiling:
    Doing GetInitialState() 100'000 times in the nested StateMachine took me 1605ms. Reduced to 1ms with the caching (only assignation).
    Using the unity profiler and focusing on the StateMachine, the performance were similar with/without the nested StateMachine, so I suppose it's good like this.

    Sure ! Well, actually it really is the same as before. The only difference is the new StateMachineActionSO with the same UI as the StateMachine monobehaviour (table reference + debug).

    Speaking of the debug, do you mind if it's not internal anymore ? I can't find a way to keep it without making it public...
     
  2. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Actually, why not simply passing in the same SM you're getting from Awake? That'd avoid the whole interface.

    Nice.

    You're adding the changes to the same assembly, you should be able to reference it? It's just in a different namespace and you have to use #if UNITY_EDITOR.
     
  3. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I hope it's the same, I read this sentence many many times and I didn't get it.
    Are you saying that an Action can be a StateMachine of its own now?
     
  4. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    The interface is kind of mandatory. The TransitionTableSO and the ActionSO were referencing the StateMachine. As the idea was to be able to use actions and tables indiscriminately for the main or the nested FSM, I made the switch to the interface.
    For the internal : A public interface can't contain an internal property (C# limitation), that's why I was asking.
     
  5. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Oh, no ! I know I should have posted a screenshot in the first place, sorry :
    SMActionSO.png
    What I mean is that the StateMachineActionSO is acting like the StateMachine monobehaviour component (see below for reference). It references a field for the table and that's it.

    Screenshot from 2021-02-01 15-51-33.png
     
  6. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    What I mean is, can't you simply do this inside the StateMachineAction?
    Code (CSharp):
    1. Awake(StateMachine stateMachine)
    2. {
    3.     _initialState = _transitionTable.GetInitialState(stateMachine);
    4. }
     
  7. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Oh sorry, so you mean keeping the interface elsewhere but giving the object in the Awake ? Well yeah, I suppose we can also do this. I have to admit that I'd prefer keeping the interface tough (no particular reasons, except that it may be more generic for possible future features).
     
  8. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    I mean not having the interface at all, didn't you make it so you had something to pass into GetInitialState ? But that something can be the original StateMachine, or am I missing something?
     
  9. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hmmm.
    Main fear: the transition. How do you transition "inside" this inner state machine, and what happens to the outmost?

    Also, what do you imagine we could use this for, in our simple game?
     
    deivsky likes this.
  10. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    The inner SM wouldn't really be my main concern, it's basically doing in OnUpdate the same thing that the regular SM does in Update, it's gonna perform all the Actions of the current State, and then check for Transitions.

    My fear would actually be the outer SM, which could in theory have more Actions (including more StateMachineActions) running alongside the inner SM. And on top of that, my biggest fear is that the Transitions to leave the State that's holding the StateMachineAction would need to somehow be aware of the current State of the inner SM, otherwise, it might misbehave.

    It's a cool concept, you could have some super deep State Machin-ception sort of thing going on. But then, if something breaks down there you'll probably get stuck in limbo for years trying to fix it :D
     
  11. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Sorry, this is a mouthful.
     
  12. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yes that's what I meant. I'm sure you guys will figure out the tech easily. I'm more worried about it from the UX point of view. It might be configured wrong, and then... you debug it!! :D
     
    Eyap likes this.
  13. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Sorry for the late reply, I double-checked to be sure. Yes, you are right !
    I still have some concerns with passing the StateMachine though : If I have a twice-nested SM, I will pass the parent (main) StateMachine component to the grandchild, thus making the first child "shunted". It doesn't do anything and works now, but I fear that it's gonna make problems later... Well, maybe it's not worth the hassle now.
    I have to admit that I liked the cleanliness of the interface, but I may be biased ;).

    I think it's gonna improve the UX a lot, and make things easier for designers.

    I got the idea when playing around with the slime critter : I wanted to add some action when it was roaming. But what I call "Roaming" is in fact two states in the current SM : The idle state and the roaming state (which is more like a "MoveToAPointState").
    So I would need to add my custom action on both those states. It's kind of redundant and error-prone.
    It's the same thing for conditions : currently both of theses states have the same transition : If the player enter the alert zone, it enter the fight state. Redundant and make the table bigger and bigger.
    If I want to add a "Sleep" state (just an example) triggered at some time, I would need to add two transitions : From the current IdleState and from the current RoamingState. Same thing, may be error-prone.

    Making a nested SM with both the Idle and current Roaming states would make the overall schema more clear.
     
  14. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Unless the implementation changes, it shouldn't really cause any issue. The only reason to pass in the SM Component was to provide access to GetComponent. I could've simply passed in the GameObject, but I assumed most actions and conditions would want to reference the same few scripts, that's why I implemented the GetComponent with caching. So in terms of something breaking because of sending the main SM to the great-great-grandchild, I really don't see it possible. Unless, again, something in the implementation changes. Or for some reason you want the child to control the parent in some way.

    It was anything but clean to me, mimicking all the methods of a regular component... that's why I insisted on removing it!

    Ahh, knowing what you were trying to accomplish does make it make a lot more sense. I was having the same issue with the main character SM. Idle and Walking are exactly the same in terms of Transitions so I really wanted to merge them into one State, but that would've required a lot of rework in their specific Actions so I didn't move forward with it.

    Having a nested SM would definitely be of use in those situations.
     
    Eyap likes this.
  15. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Yeah, I understand what you mean. I made a PR with the new StateMachineActionSO (without the interface ;) ). I tried to describe a little bit further what the changes were, and included some screenshots.
    And yes, I only took a quick glance at the Protagonist SM, but I'm sure it can have some merges as well (for Idle+Working, or for Sliding+MidSliding).

    Speaking of that, I don't think that caching is necessary. If we look at the current implementations of the StateSO, they are all also caching theirs references. That means every component is cached twice, or am I missing something ?
     
  16. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    It only helps during Awake where, for instance, most Actions and Conditions in the main character get the Protagonist script, so thanks to the caching in the SM we only need to fetch it from the backend the first time GetComponent<Protagonist> is called, subsequent calls then just perform a small lookup to get the reference.

    But yeah I'm not too sure about it either, I've thought about removing it as well but just never did, and considering the small amount of SMs we have running at the same time, we probably don't need it
     
    Eyap likes this.
  17. Nsuidara

    Nsuidara

    Joined:
    Apr 22, 2016
    Posts:
    36
    meybe easy manage Transitions with graph ?

    upload_2021-2-12_20-14-28.png
     
    Amel-Unity, Sarai, Eyap and 1 other person like this.
  18. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    How do made a graph thing in unity? I don't know. (Any good tutorial).
    But it's a good thing. Graph is best for transitions.
     
  19. Nsuidara

    Nsuidara

    Joined:
    Apr 22, 2016
    Posts:
    36
    You can use UnityEditor.Experimental.GraphView;
    like Shader Graph using and other new tools Unity.

    For my last screen, i made state machine similar, about State / Action / Transition idea.
    but i don't like concept example ConditionSO... abstract ScriptableObject.

    bc. you must create IsMovingConditionSO from ConditionSO, IsMovingCondition and next create object from IsMovingConditionSO in editor.

    I create one ConditonSO and next IsMovingCondition, IsHoldingJumpCondition... and next create SO from ConditonSO
    and just select script. (I just use Type & Assembly)

    Example.
    upload_2021-2-13_18-47-49.png
    Create object from ActionSO and select "AerialMovementAction", problem is ScriptableObject can't serialize Type or object... so here always string....
    when OnEnable in SO then i convert string to Type... when i State -> State create instance type etc.

    Like about paramters - i have generic ActionSO, so can't define all paramters for all Scripts
    then i made array Parameters struct like Name/Value and next Action in play mode, have ManagerParamters and when Awake (once time) then i have example method " GetFloat(string name, float defaultValue)"

    Code (CSharp):
    1.         public override void Awake()
    2.         {
    3.             movement = Core.GetComponent<Movement>();
    4.             _speed = Parameters.GetFloat("Speed", 7f);
    5.         }
     
    Sarai likes this.
  20. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    That looks pretty cool, @Nsuidara. I'll merge this thread with the StateMachine thread, so the people that are interested in that will get notified.
     
  21. nunodelgado

    nunodelgado

    Joined:
    Nov 26, 2019
    Posts:
    2
    Hello everyone!

    After reading the two first pages of this thread I quickly noticed I did not have the skills to understand (straight away) how the state machine was implemented. For this reason I decided to do reverse engineering and learn how it works. I ended up creating a flow chart in Milanote of this system with notes about how it works. Since this system seems quite settled, I would like to share the flow chart here so that anyone that feels the same as I did can easly have an overview of the system:

    https://app.milanote.com/1LpxWN1DSCAI73?p=60mTNFnUNqS

    You can also make comments in the flow chart about how to improve the flow chart or point out errors that it may have. Maybe this flow chart can also help other people making decisions in this project, who knows.

    I am still a little bit overwhelmed and not confident enough to use it in one of my own projects because i am not 100% aware of its limitations. But I really enjoyed to learn all this amazing stuff and read through all the 7 pages in this thread :D

    So thank you everyone for taking your time to invest in this thread. I hope i was able to give back with this flow chart :)
     
  22. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Sorry, saw the char a while ago but never replied. It's pretty cool!
     
    nunodelgado likes this.
  23. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Reviving this thread to say that I feel like the character controller needs some work. Specifically, I feel like we went overboard with the checks around sliding and preventing the player from climbing steep slopes, and as a result moving around now feels wonky. Even the smallest of steps stops the player from moving and, for some reason, they don't slide along it but just stop. Add to it that when this happens, it's impossible to jump forward by you can only jump in place.
    Play the game a little in TestingGround and jump around and on the rocks, and you'll probably see what I mean.

    So, long story short, if somebody wants to take a look at the basic movement actions and states, it would be great. (Walk, Idle, Sliding, and that other weird Sliding state that I never fully got o_O)
     
    Eyap likes this.
  24. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    I won't have time this week, but if nobody want to do it before I can improve this next week :)
     
    cirocontinisio likes this.
  25. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Hi ! Sorry I really didn't have any time recently, but I finally have some time this week-end, and will work on this.

    I took a look at the current state of the Protagonist StateMachine. It really is big !

    First and to have a clear view of what we have currently, I tried to summarize below the current main states :

    upload_2021-7-2_17-3-38.png

    - InMovement section include Idle, Walking, and all the jump/slide states
    - InConversation (which should be InDialogue actually) include Talking and Walking.
    - Pick up and Dying Should be self-explanatory.
    - Attacking is a bit of a weird state, as it can be associated with every movements.
    - Getting hit is also a weird state, I still don't really know how to handle it.

    In Movement could be divided like so :

    upload_2021-7-2_17-3-44.png


    I just double-checked : It really is this current state in the SM.
    Idle and Walking have the transitions to InConversation and Getting hit, but that's it. You are not directly hit while jumping, etc...

    It is this second graph (in movement), that need to be reworked.

    What I plan to do ?

    I think that it will be easier to rework and test the movements by making the distinction of these two graphs. So I plan on using the nested StateMachine I have proposed some months ago.
    Next, rework the sliding & mid-sliding stuff.
    Lastly, I suppose some acceleration values need to be tweaked to remove this "wonkyness", and also change some of the colliders (I saw that a PR was submitted about that recently, maybe it is possible to cherry-pick some stuff from there).
     
    Amel-Unity and cirocontinisio like this.
  26. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I'm not sure if it's a good idea. Are you planning to remake the visual tool too?
    I agree that the current SM is big, but at least since there's no hierarchy, the tool is enough. If you introduce nesting, then you need a better visualisation in the tool to make that evident and handy to work with.
    Up to you if you want to deal with all that comes with it, but I'd say that at this stage it's a bit late to make such a big change.

    This said, I agree that the sliding stuff needs a bit of a rework. Me and Amel took a good look and we concluded that there's something wrong in the way we check slopes. The checks we do are not sufficient, and in fact we are never using the native ability to walk up steps from the CharacterController component, since it conflicts with our slope-checking routine. That's why the character can't walk even small steps.
    That's literally the only thing that I think is worth fixing, if we can do that, it would be great. But it might require some rethinking of states (especially, as I said, around Sliding and MidSliding - which by the way do almost the same thing).
     
    Amel-Unity likes this.
  27. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Well, to be honest no I didn't any plans to.
    I understand your point. I'm not sure about what is best indeed - A complex SM with all the Transitions at the same places, or a few more SM but with less Transitions...
    Well for now I am modifying the current big one, but I may try to replicate it with the aforementioned nested SM, just to have a way to compare them (it shouldn't take too long to do).

    Anyway, so I tested a lot of different configurations this evening, starting with removing the mid-sliding.
    With only the sliding and the protagonist collider values tweaked, I had some stuff that worked quite well, but also a few weird behavior. Finally I tried without any Sliding states, and that was REALLY the best.
    It is as you said : I think that relying on the CharacterController component feels better.
    The sliding state was introduced to prevent someweird bugs where the player was floating mid-air (reminder: https://github.com/UnityTechnologies/open-project-1/issues/309), but I couldn't reproduce it. Either it's gone, or is not a easily-reproducible bug. Anyway, we could also just have a new transition (OR) to Idle when the player is not falling anymore.

    The main "wonkyness" now gone, I feel that the attack response is very slow and the movements are weird when using the attack. So I will do some testing tonight/tomorrow about that. The jump may also be improved (but not much :) )

    PS: You can try the current state there : https://github.com/Eyap53/open-project-1/commits/feature/improve_character_controls
     
    cirocontinisio and Amel-Unity like this.
  28. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hmm, no, the sliding was introduced to make the player slide when on steep surfaces, so that they can't climb mountains by jumping repeatedly because as soon as they touch the ground they would go into sliding.

    The movement is intentionally slowed down when attacking. I feel like making the movement go regular speed just makes the attack harder to control.

    I tested your solution briefly, but removing the sliding means that you can now land on any slanted surface and stay like that (see below). I feel like this is less desirable than the issues we have with the steps now (?)

    upload_2021-7-5_13-45-49.png
     
    Smurjo, Eyap and Amel-Unity like this.
  29. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Oh Right, my memory was messy, sorry... I didn't try on the hills earlier, but it is indeed easily reproducible. I will do more testing then :)


    What I mean is that it is not responsive at all. You have to plan and launch your attack 1 second before actually doing it. That's just too much, IMHO. But the solution is really simple, we just have to increase the speed of the first part of the animation, when the player is raising its cane.
     
    Last edited: Jul 6, 2021
    cirocontinisio likes this.
  30. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Okay, I just pushed a new update to the same branch.

    Just a quick note before explaining, I think it is too bad that the CharacterController doesn't handle the sliding... We should be able to rely on it and the "Slope Limit".
    We decided to use a State to fix that, but it does feel less legitimate than other states (it doesn't have an animation, etc...)

    Anyway, here is the few things I did :
    - Added a new "IsFallingConditionSO", which check if the player is effectively falling.
    - Changes in the transitions.
    -...

    I'd like your feedback on one of the transitions I removed : The "Walking" -> "Sliding". While testing in the scene and trying to step on rocks and bushes, sliding was always trying to make the player go backwards, which were really annoying. However, by removing it it is possible as of now to climb mountains by jumping repeatedly without releasing moving controls... A bit harder than before nonetheless.

    I think it is a good trade-off, but what do you think about it ?

    Thanks :)
     
  31. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Unfortunately there doesn't seem to be a slope value to check, and I think it's because the CC cannot assume how you want to check the slope under you, and what happens when sliding.
    Checking the slope for walking purposes is much easier, if the slope is too much the movement is just stopped (which is exactly what happens now).

    Isn't this the same as
    IsSlidingCondition
    ?

    I tried the latest changes, the behaviour is still a bit weird. I can get stuck on some surfaces where I'd expect to slide, and not be able to move. But if I jump, upon landing, the character moves in that direction (but not up the slope).

    If you don't mind I'd also test something, I think we have been detecting slopes the wrong way, so I'm experimenting with both raycasts and spherecasts to get a better sense of what the character is walking on.
     
    Eyap likes this.
  32. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Random little quality of life improvement: now all StateActionSOs have a description field, so the StateMachine's Transition Table editor can display them to facilitate understanding what's going on under the hood.
    Obviously the descriptions need to be written one by one, so we'll get to that :D

    Screenshot 2021-09-13 at 10.36.48.png

    You'll see these changes in the next push :)
     
    Eyap and Amel-Unity like this.
  33. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Great for new comers. I can help in writing the descriptions too.
     
    cirocontinisio likes this.
  34. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Thanks @Harsh-099, in the end I powered through them :D
    Once I reorganised them in folders, there were a lot of similar ones (Play particles, Set Animator parameter, Play sound) so it was just a matter of copy and paste.

    (I can't push straight away because I introduced a small bug on the health :D)
     
  35. sanathkukkillaya

    sanathkukkillaya

    Joined:
    Nov 14, 2019
    Posts:
    37
    Hi everyone,

    I am working on a game where I have a bunch of NPCs who perform a bunch of actions, similar to a sims game. I am using a similar StateMachine to control their movements and actions, and it generally works well in the beginning. I am facing one particular issue though related to saving and restoring a State Machine that is bothering me and I could not find any proper answer anywhere. Since this thread was related to State Machine, I thought I would post it here. I even tried looking into the machine used here, but I could not figure out this issue fully. I do apologize if this has already been handled, and I have missed this somehow.

    So in my game, there is a possibility for the user to leave the scene and go to another scene, and then come back to the original scene with the NPCs. So at this point I would like the NPCs to restore their states. For example, if one of the NPCs was simply sitting at a table, and another NPC was wandering around nearby before the scene changed, I would like to restore both of these NPCs to the same state when I reload the scene. So the first NPC still has to sit at a table and the other NPC need to wander around.

    Initially the NPC enters an initial state and then moves along the state machine based on the conditions. In some states, a few particular variables (such as door object reference to the room that that the table is in) are also set in a particular state. When I exit the scene, I save the current state of the NPC along with a few details like the particular table id in a simple serializable class and restore these when I reload the scene. The issue is that while this works, I face some problem when I now change states. Since the initial state of the NPC was never entered when the scene reloaded, and I directly entered in some random middle state, a few of the dependent variables which would have been set in the previous states are now never set. For example, here I now don't have the door object reference since I didn't enter the state where this was being set. This causes all sorts of various issues with null references and weird exceptions.

    I would like to know what is the best way to handle such scenarios. Should I ensure that each state should be able to run independently irrespective of which order it is entered? If so, can someone guide me to some sort of codebase that I can use as a reference?
     
  36. Gmora7

    Gmora7

    Joined:
    Apr 17, 2013
    Posts:
    12
    Hello all!
    I am on 2021.3.2f1 OSX M1

    When I go to open the State Machine window I get the error:

    NullReferenceException: Object reference not set to an instance of an object
    UOP1.StateMachine.Editor.TransitionTableEditorWindow.OnEnable () (at Assets/Scripts/StateMachine/StateMachine/Editor/TransitionTableEditorWindow.cs:35)
    UnityEditor.EditorWindow:GetWindow(String)
    UOP1.StateMachine.Editor.TransitionTableEditorWindow:Display() (at Assets/Scripts/StateMachine/StateMachine/Editor/TransitionTableEditorWindow.cs:22)


    visualTree seems to be null

    var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(_uxmlPath);


    Any thoughts why it's crashing?
     
  37. WagnerGFX

    WagnerGFX

    Joined:
    Aug 26, 2013
    Posts:
    28
    If this is the same Issue I had then it's because the uss and uxml files' path are hardcoded into the script and you moved or renamed them.

    To solve this I made 2 serialized fields of type VisualTreeAsset and StyleSheet and set them as default references on the script inspector. With this you can remove the code with the hardcoded path.