Search Unity

Official State Machine

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

  1. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    What do you mean by "for the struct"? Which struct? Isn't this going to create the boilerplate again?

    Can you mock an example of this class in particular, the way you imagine it?

    What do you mean by "paste struct data"?
     
  2. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    There's an image in my post
     
  3. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Sorry to jump in the middle of an intense debate.
    On my side, I am playing with the current state machine to wire my little but hungry plant.
    What do you think about this approach to manage entering zone detection?

    My approach is the following: Use EventSystem to isolate StateMachine conditions from the Colliders

    Add two empty GameObject to the critter with each a Sphere Collider with a new MonoBehavior Script called EnemyColliderZoneController on each:
    - One collider (with a large radius) for the alert zone detection
    - One collider (with a small radius) for the attack zone detection

    This simple script will emit a VoidEventChannelSO when the Protagonist will enter in the collider and another one when he will exit the zone.
    upload_2020-11-23_10-42-36.png
    upload_2020-11-23_10-54-48.png

    Next I have created a new Condition script HasReceivedEventSO that will trurn true when the specified event has been recieved.

    upload_2020-11-23_10-44-40.png

    Here is a small demo when applied to the Plant Critter:



    BTW: is it the right place to discuss that?
     
    Last edited: Nov 23, 2020
  4. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yeah but I didn't understand it. It highlights 3 random bits. Why do you leave parameterName out, for instance?
     
  5. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    It highlights the main changes:
    - struct instead of class
    - thus, StateAction in now a IStateAction interface
    - Added [Serializable], so we can expose whole State as a "data" field in Authoring SO.
    - Fields that needs to be edited in the Inspector are made public (or [SerializeField], if you prefer).

    Essentially, it gets rids of all this stuff:
    upload_2020-11-23_19-11-21.png

    "parameterName" is leaved out because it is required only during Authoring (converting to "parameterHash" in this case).

    I also forgot to remove "_whenToRun" from SO and make it serializable in State.
     
  6. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Ah, I see. I didn't even notice that you changed it to struct, because the script looked largely the same.
    So to understand if we're on the same page:
    • The
      StateAction
      stays a ScriptableObject, and is the "authoring" block.
    • This SO contains a reference to a struct, which is the runtime data.
    • The struct contains the runtime logic too? I guess so.
    Isn't this more or less what we're already doing? The only difference is that the runtime data would be embedded into the SO as a struct. But you still need to always define those two classes/structs, for each action. And since apparently we make tons of actions which do very little, isn't it just easier to keep the same object structure at both edit time and runtime?

    The AnimatorParameterAction is a really good example, half of the lines is us passing the values from the SO to the runtime object.

    ---

    A question: You mention that in your solution, the struct would be runtime and the SO would stay authoring, thus giving us the ability to modify the SOs and basically tweak all actions at runtime. How do you suggest to do that? An SO would have no connection to the structs because they'd have to be copied to become runtime, or do you plan for each of these structs to "watch" their original SO for modifications?
    Doesn't that create more code again?
     
  7. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    So just to clarify (and simplify): we both agree that it's a bit pointless to replicate the same data in a different format at runtime.

    I personally think it might be worth it to just duplicate it as it is. It would reduce code A LOT.

    You think it's worth instantiating 90% of it (the runtime struct), but my argument against that is that to save from duplicating 10% of data in memory, we produce 50-70% more code.

    Right?
     
    Last edited: Nov 23, 2020
  8. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yes, or you could bring it to the plant thread.

    I'm just holding off in light of the StateMachine discussion as of above, but in general I think the way you're going is correct. The only question is whether you don't want to expose just a regular event in your component which the StateAction hooks into, rather than passing the event on one of the channel SOs. After all, the two objects (event producer and listener) belong to the same instance.

    The question I guess is whether you expect other objects to be able to listen to that event. In that case, your solution would prove to be more flexible.
     
  9. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    SO is used for Authoring and for Shared data.

    If we'll have SO as the only data format - we'll lose the separation between shared, non-shared and temporary data (components references, authoring data, etc), everything would be convoluted in a single blob!
    We'll also lose "runtime tweaking" by making copies of SOs.
    And when dealing with many states and instances, Instantiating new ScriptableObjects might be much slower than just passing struct copy.

    They don't have to "watch" for modification.
    I've mentioned that AnimatorParameterAction has only shared data, so instead of copying the same values for each instance, we could just reference SO like this:
    upload_2020-11-23_22-0-21.png

    If that's what you mean by 50-70% more code - there is only like, 7 more lines added?
     
    cirocontinisio likes this.
  10. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    You like to overdramatize things, eh? :D

    I see what you mean now. I see what you are thinking of.

    This is true, but think of this: we couldn't tweak things anyway, since 90% of the data is in the runtime struct. In that example,
    Hash
    ,
    Type
    ,
    BoolValue
    ,
    IntValue
    and
    FloatValue
    should all be part of the struct. Why? Because they belong to the instance, you don't want to tweak the value of the speed of the character, and have all other
    StateMachines
    that are sharing that
    StateAction
    go at the same speed. So in that sense, 90% of the data would go into the struct, 0-10% would go on the SO and be actually shared and modifiable at runtime.

    So not sure if it's worth the refactor, but it would save some lines of code (though you're forgetting the extra class declaration in your line count :p)

    Second thing is: we'd have to hide the struct from the Inspector of the SO, otherwise in Play mode it looks like you can tweak that, but you can't (because it's been copied elsewhere). Could be confusing.
    Or at least put a warning on it? "It's pointless to tweak the values below"?
    Or - and this might be a good solution - we can have a custom attribute to hide or even better disable the struct when in PlayMode.
     
  11. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    What do others think of this potential refactor? It wouldn't modify much, just give the ability to have less code when a StateAction contains a lot of parameters, and the ability to have some shared parameters on the SO which could be tweaked at runtime.

    Conditions could go through the same refactor?

    The custom Inspector and Editor window should keep working without need to be touched.
     
  12. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    I am quite new in the state machine/event system, but where are the cons of using the ChannelSOs events here? In addition to be more flexible/future proof, as you said, actually I have the feeling that using them makes the HasReceivedEventConditionSO quite generic to handle any state machine condition based on Event triggering, am I wrong?
     
  13. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Oh yes, totally.

    If you were to do it the other way it would be generic to the StateMachine too, in the sense that multiple state machines could reuse the condition provided they have an
    EnemyColliderZoneController
    and the appropriate collider. But having the Condition listen to a
    VoidEventChannelSO
    means that it could be triggered by anything, including a button in the Inspector (which is something we should create, for easier debugging!).
     
  14. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Yeah, I totally see your point.

    I get what you're saying, but I just don't see the point in instantiating SOs during runtime. It's something I usually try to avoid. They use a lot more memory and have no advantages (besides, in this case, reducing some boilerplate) to regular classes. Also, if I'm not mistaken, we'd have to Destroy each SO instantiated when we're done using it, which is also costly.

    @Neonage has a good solution, I think. If we use structs then we can just serialize it in the SO and easily make copies. The problem with structs is boxing, but I'm not sure if it would cause any noticeable performance hit.

    Another option is, currently we have a reference to the origin SO in the StateAction/Condition, but it's set to internal because it was only being used by the debugger; if we make it protected, we could just access SO data directly, removing the need to copy over all the read only fields. This way we would also get that "playmode editability" that I know has been brought up a few times.

    What do you think?
     
    Neonage likes this.
  15. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Whoops, I totally missed the 6th page before posting.

    I doubt this is accurate (not to say it's completely inaccurate :D), it totally depends on the system. As @Neonage said, AnimatorParameterAction has only shared data, so 100% would be in the SO.

    This part is true, and that would be a good solution.

    We could make a template for this.

    Definitely!
     
  16. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hmm, yeah, I guess I'm thinking of actions where the value of the data is mutable, but maybe I'm overthinking in this case. You're right in that the name of the parameter name wouldn't be unique per instance, and most probably the same for the potential int/bool/float values.

    Maybe that would be even better because then you also save yourself the duty to have to pass the SO reference to the struct that gets created?
     
  17. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Yup, totally. And the change would be minimal.
     
    cirocontinisio likes this.
  18. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    I disagree, it would be confusing if we disallow user for editing these states.
    Do you know how ECS Conversion works? It's Editor "watches" for user modifications of Authoring components, and if any - it converts this gameObject again using new values. This is a very convinient way, I loved it, and I think we could do the same.

    The problem is that, this "origin" field cannot be generic, so it's kinda useless.
    Maybe better add
    GetSharedAsset<SO>()
    method that I had in my first iteration? The authoring asset would be automatically referenced in cache for this instance, so we only need to declare a local variable that calls this method.
     
  19. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    We would only disallow editing of "initial value" fields. Fields that would then be modified per instance at runtime. I'm not sure what you mean, though, ECS doesn't even allow component modifications from the inspector at runtime.

    I don't see it being generic a huge issue. You just need to cast it to your specific type. If you want to hold the casted reference in a separate field, that's fine, but you can also just cast it every time you use it. I think if we start adding type parameters they're gonna propagate through the entire implementation to get type safety and it's just gonna overcomplicate things even more.
     
    Last edited: Nov 24, 2020
  20. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    It allows when using sub-scenes and Live conversion, just like this:
    GIF 25.11.2020 10-35-07.gif
    And it does not required any additional code, because authoring conversion is already there.
     
  21. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Is that new? :eek:
    Haven't messed around with ECS in a while, now I'm itching to :D.

    Either way, you have a component per instance there, we don't. To get that same behavior we'd need to hook up an editor for every runtime component, in which case we may as well just do what @cirocontinisio said and simply instantiate copies of the SOs.

    ---------------------------------------------------------------------------------------------------------
    Edit: I'm thinking more about this. Are you selecting an Entity or the Prefab of that Entity?
    If it's a prefab then disregard what I just said, what you're saying would indeed mimic that behavior.
    ---------------------------------------------------------------------------------------------------------

    What I meant was that it wouldn't make sense to modify values that will get overridden during playtime. For example, say there's no Transform component so we have a TrackPositionActionSO with a field vector3 Position. Every instance with the same TrackPositionActionSO asset starts at the same position (the one set on that field), and the runtime component then keeps track of their position; each entity moves in a different direction, at a different speed, and whatnot, so that value will get overridden per instance.

    Following that, and to recap, our options would be:
    1. Use a CustomAttribute to hide the Position field during play mode, since changes to it won't be visible (@cirocontinisio).
    2. Have modifications to that field, at runtime, not affect anything at all (current behavior).
    3. Propagate the modification to every instance to change their current Position (@Neonage).
    Note that modifications at runtime do affect new instances, and they also stay after exiting play mode, taking effect the next time we enter; taking that into account, option 2 makes more sense, to me, than option 1. Option 3 would be harder to implement, and I think it might cause some funky or undesired behavior, as well as confuse whoever is modifying the SO if they didn't expect it to alter everything that was using it.

    One final reminder that this is only for shared data that will get overridden per instance at runtime. In practice, these are just read only fields, except they're only read during Awake to set the initial value of their runtime counterpart; it's normal that changes to them don't affect already created instances.
     
    Last edited: Nov 25, 2020
  22. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    I see, overriding every instance on any change would be very confusing. Position example is a very good one.
    I'd like to go with the current behaviour + a little help box during playmode.

    What if we inline the editors into StateMachine component, but for the attached runtime states instead of SOs?
    If they're serializable, we can easily show them!
    It would maybe look something like this:
    upload_2020-11-26_14-46-19.png
    (screenshot from my playground project)
     
    Last edited: Nov 26, 2020
  23. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Sounds good!

    I'm not saying I'm against it, in fact, this would also allow us to make a much better debugger; but then we'd have to enforce serialisation on every Action/Condition, and every field! Maybe we could go with reflection?

    Image is broken!
     
  24. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    I get that it's a small price for a possibly huge benefit, but since we were discussing about reducing boilerplate, having to add Serializable and SerializeField attributes everywhere would be counterproductive towards that goal.
     
  25. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Hahah, I've copied it from my Inbox conversation and it was visible only to me :p

    Not on every fields - only on those that we want to expose.

    We're already adding them on every field, but inside SO :D

    And since these structs are using public fields for exposed values - they're already serializable, without [SerializeField] attribute.

    It could be a bad idea, since there would be no:
    - PropertyField
    - Property Drawers
    - Multi-target support
    - All the other stuff based on serialization..
    We would have to write most of the Editor stuff ourselves, and reflecting inherited types may also introduce tons of unexpected errors and performance overhead.
     
  26. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Okay, based on this discussion I've opened a new PR. It adds access to the origin SO from the runtime components, a new InitOnlyAttribute to display a HelpBox during play mode to state that changes to the field won't take effect, and script templates to avoid having to write a lot of boilerplate.
    It's all very detailed in the PR. Tell me your thoughts :)
     
    Aeonitis and cirocontinisio like this.
  27. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Great! I just left a couple of points for discussion, very minor things!
     
  28. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I merged the latest refactor, both on main and in the other branches.
    Now one great thing we should do is write some documentation, so that other people can use it!
     
  29. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Alright! I had started writing some documentation, but I was really busy the last two weeks. I'll get back to it now! ;)
     
    cirocontinisio likes this.
  30. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Thanks! I am really keen on beefing up the wiki. At the same time don't feel like you have to do it ASAP, your personal/work stuff comes first :)
     
    deivsky likes this.
  31. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Hello,
    I was busy with my school exams and not giving any time here. And this thread has gone very long and i can see very much discussions and photos here.
    Anyone please give me a quick recap what had added to project.
     
  32. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    There's been only one small/big change, which is the ability for the runtime StateActions and StateConditions to keep a reference to the ScriptableObject they are spawn out of. This reduces the amount of code needed in each definition of a new Action or Condition.
    I'd recommend reading from this post on, to understand the reasoning.
     
  33. Sebastiran

    Sebastiran

    Joined:
    Feb 23, 2017
    Posts:
    13
    Hi everyone, it's been a while since there was any talk on this thread (which is a good thing) but I was just wondering how everyone is liking the current state machine. This is a really important core part of the game, so it would be good to know what everyone thinks. We are relatively far into development and are already using this system in multiple areas. But it's not too late to make any adjustments if we really feel there is a need to (right?). Maybe it would be helpful to evaluate this approach?

    If I'm completely honest and a bit selfish, another reason why I bring it up is because I've been looking at different ways of doing state machines for my own game and the different characters. I've looked at this one, but I would have to decide against using it as, though it is beautifully made, it lacks some important features as being able to trigger an action midway through an attack animation, or being able to play sounds on footsteps landing. I'm sure those things are implementable with the ActionSO's using IEnumerators and such, but the disconnection with the animations (though at first glance clean) causes a lot of confusing back and forth between Animations, Interactions, and States. I talked a little bit about the problem I think it causes for interactions in Amel's World Interaction System thread.

    So far my favourite State Machine I've found has been the one used to control Ellen and the Monsters she fights in Unity's Prototype Series. It uses the Animator to add behaviour to different states. I believe Ciro said something in a live stream about the use of the Animator as a state machine too, but I forgot if there was any reasoning for not using it? Anyway, just some thoughts I had that I wanted to share. But feel free to share and teach me more about State Machines and such. And what some of the downsides are of using the Animator. And of course, I would love to know how it has been using this State Machine. Has it been easy to use for both coders and designers? @treivize is using it for the Critters correct? How has that been? Was it easy to get used to? Ran into any problems?
     
  34. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Hi @Sebastiran, I have no past experience with any other FSM engine, but having played with this one to build the plant critter initial behavior, what I can say is that I found it, really simple to understand and to set up. The core part is quite small and everything is delegated to how you write smart conditions and actions on top of it. Again, we are really at the beginning of using this core feature in the game, so use cases are still quite simple, but I think it will not be a problem to extend it along the way since the engine is not over complicated right now.
     
    Sebastiran likes this.
  35. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Generally the reason for not doing it is that it's a bit of a hack, since the Animator can be in two states (or more!) at the same time. Sure, you can build it so it's not, with instant transitions, but it feels a bit like a hack. For that reason, you'd end up needing to have 2 Animators, one for the animations that have to blend and one for the logic. Not super ideal, also because the Animator actually has a cost in terms of performance.
    But there are developers who say they successfully used it this way!

    For our case though, we ended up going with the community-made solution you see. Sure in terms of UX it's not as refined as a full tool, but it does the job and I believe - as @treivize said - that it's easy enough to understand. So potentially it can also be stolen and used in other games.

    Unfortunately not many state machines are built for this, and I believe it would be tricky to implement with an Animator-based one too (we'd probably build it the same way we will with ours, either with a delay/Coroutine, or with an event directly on the Animation's timeline). I'll continue the conversation on this aspect in the World Interaction thread you linked.
     
    Sebastiran likes this.
  36. Trufiadok

    Trufiadok

    Joined:
    May 25, 2019
    Posts:
    3
    Hi everyone,
    I tried to implement a simple state machine, using "UOP1.StateMachine".

    State1( var++ ) ---TransitionA( var>10 )---> State2( var-- ) ---TransitionB(var<0)---> State3( "endstate" Debug.Break )

    I have found that it is not possible to define an ENDSTATE (a state that has only an input TRANSITION) with the "Transition Table editor".
    I know, that the state machines needed in the game, only need to be closed loops.
    Can it be solved to assign an ACTION to an ENDSTATE?
    (I could only solve it by creating a TRANSITION that never happened, starting from the ENDSTATE.)
     
    Last edited: Jan 5, 2021
  37. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Hi @Trufiadok, indeed I have also discovered the same: only the states with out transition are listed in the table. But if your problem is to be able to add actions to an end state you can do it by inspecting your state SO directly, there is a custom editor to set actions to the state.
     
    cirocontinisio likes this.
  38. Trufiadok

    Trufiadok

    Joined:
    May 25, 2019
    Posts:
    3
    Hi @treivize, thanks for your help. It works.
    I didn't study in sufficient detail the assets that i generated.
    Thank you very much again.
     
  39. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Hey all, it's been a while. Hope you enjoyed your holidays!

    I've opened issue #295 to link to the documentation of the state machine I made, following the workflow suggested in the Documentation and Wiki thread. Feel free to review it and tell me your thoughts!

    This is certainly a flaw that I will be fixing soon. That endstate scenario totally slipped my mind so thank you for bringing it up!
     
    Eyap likes this.
  40. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    I'll take a look today !
     
    cirocontinisio likes this.
  41. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Hi everyone! I don't want to seem spammy but I made some changes that might interest a lot of people so I'm posting on the threads related to them (wondering now if I should have instead created a new thread...).

    First, I have this PR that was made to solve some issues mainly with sliding, and it contains changes to the character Transition Table. Now, I've opened another PR that points to the first PR. This new PR contains a lot of changes to the code behind sliding and mid-air movement, almost completely rewriting SlideActionSO.cs and including the new AerialMovementActionSO.cs. Testing and reviewing are very appreciated!

    And on a separate, more StateMachine-related topic, I also have another PR, this one containing changes to the TransitionTable Editor and Window. Very subtle changes and improvements (for example, I added buttons to edit 'Target States' just as we currently have with 'From States'. @Trufiadok @treivize), but they could also use some testing and reviewing.

    Finally, keep the suggestions coming if you have any!
     
    Amel-Unity, cirocontinisio and Eyap like this.
  42. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hmm, after a few tests I really don't like the new jump controls :/
    Now it feels like it's super hard to control the character. For a few reasons:
    - If you jump on the spot, you can't move in the air at all. This is correct in reality, but feels very weird in the game since to make a short hop, it's totally fine to jump first and then (maybe even the next frame) tilt the movement stick. I can understand not being able to move to the full speed when at the top of the jump, but if you start moving the next frame you should be able to go almost the same speed as if you were tilting already before pressing jump.
    - Jumps while running feel like they add even more speed to the movement. This makes it super hard to land a jump because you're using the run to calculate the distance, and then the jump takes you further. And since you don't have control in the air, you end up overshooting the target.
    - For this reason, if you tilt the stick and jump right after, you can do incredible jumps from being still. This feels also very weird, the jump should be shorter if the character wasn't moving at all the previous frame.

    All of these behaviours shown here:


    (as for the first item, jump on the spot, it's also there but hard to see: I was trying to move in the air after the jump)

    So I believe we should go back a bit, and simplify: the character should conserve some of the inertia on the ground while in the air, but just a little bit. For the rest, it should be able to control the jump - maybe just half of the speed while the other half is the inertia from when it jumped. (I know it does some of this now, but it just doesn't feel great...)

    Plus, if you are against a rock (or even a small step) and you jump, since you were not moving on the ground you are not able to jump on the rock/step because once in the air you can't move, so you will land again in the same spot. You basically need to step back, accumulate some speed, and then perform a moving jump. This is already not good.

    I also noticed these changes make the sliding behaviour worse. If you are on a spot between two slopes (like it's possible in TestingGround on the top of the rocks) what happens is that because you're sliding from two directions, you are still and can't move. Before, you could just jump and then move away, which allowed you to climb on top of the adjacent rocks.

    With the new jump mechanic you can't jump forward and you can't jump back (or in any direction), so you are stuck there jumping on the spot, until by miracle the physics/the character controller "fail" a little, and if you jump exactly at the right time you can get a jump with some momentum, and unblock yourself from this situation.
     
    Last edited: Jan 24, 2021
  43. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Warning, long post.

    @cirocontinisio did you try messing with the Speed and Acceleration values in AerialMovement.asset? I think the Speed might be wrong because at some point I changed the name of the field but didn't save the asset again, so it's serialized with the old name and you might be getting the default value (10) instead of one that matches the ground movement speed (7). That would explain why it seems (is) faster to jump than to run.

    upload_2021-1-25_16-25-32.png

    Besides that, tweaking the value of Acceleration should fix most of your points, I think. If we want more control, increasing acceleration will allow just that, up to making acceleration pointless. I actually have Acceleration set to 30 locally, so yeah, again my bad for not saving.

    This is the one thing I really want to look into (spoiler: already am). You're saying that "if you tilt the stick and jump right after...", and then you say "...the character wasn't moving...", then one of those two sentences is wrong :p. However, my point here is to highlight the fact that grounded movement is, just as mid-air movement was, entirely linear. And that's the real reason why you can make those super jumps while being (apparently) still, because as soon as you start moving, you'll already have reached max speed by the time you jump, even if it's in the same frame. That last bit is a detail of the implementation, all Actions (moving) happen before any Transition (jumping) takes place.

    I'm already refactoring AerialMovement to make it a generic HorizontalMovement to handle both grounded and mid-air movement with one script, like we used to, but with additional features to make movement feel better. Besides Speed and Acceleration, I'm adding Deceleration, Snap, and Flip.
    • Deceleration, like acceleration, is a float, and it's basically the resistance against inertia, whether that's air resistance or how fast a pair of chubby pork legs can stop their own movement.
    • Snap and Flip are booleans and they're both there to help with changing directions. With both set to false, what will happen when you change directions is that the character will have to slow down to zero and then accelerate. This is the most realistic behavior, especially when moving at high speeds or mid-air, but I don't think we want that much realism.
    • With Snap set to true, when you change directions your speed will immediately go to zero, and then start accelerating from there. This is an in-between that allows more control without taking it to the extreme, and I think it's appropriate for mid-air movement in our game.
    • When Flip is true, it takes precedence over Snap. While being the least realistic, I think this might be what we want for grounded movement. What it does is that, when changing directions, the character will preserve its current speed. This means that you'll only notice acceleration when beginning to move, and then onwards it's going to feel like it currently does.

    I'm still testing and tweaking all this but what do you think? Do you agree that the problem is grounded movement and do you like this solution?

    Finally, I'm also working on merging the Idle and Walking states. I've been kind of wanting to for a while, and if this change goes through it'd be the perfect time because in order to implement deceleration I'm having to add GroundedMovement and Rotate actions to Idle, so basically the only differences between the two states are the actions that control Animator parameters and particles.
     
  44. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I meant, from the player's point of view. If the character isn't moving, and you tilt the stick and jump. But I see what you mean with the full speed, yes, the character is basically already at full speed.

    Yes! I tweaked the values and it feels much better. I think for me 6 on the speed and 40 on acceleration feel good. Or 7 and 40, to match ground movement speed.
    But the acceleration is important, I know it's unrealistic, but it allows for better jump control. Also for climbing on big steps that are right next to you.

    I honestly don't think ground movement has any issue right now. I feel like we don't need acceleration and deceleration. Plus, we might find that the animations are not really done for this...? I fear that having a big acceleration phase will introduce foot sliding, same with decelerating. We'd have to have some kind of foot IK, which I don't think we need.

    But mostly I'm worried the movement will feel unresponsive.

    Right now we have a delay in turning, which I think adds just a tad of realism without making the character unresponsive (because the position is still being updated at full speed).

    Wait, why? If there are differences between the states, why would you merge them? We might have more (like, for instance switching to a random idle at random times, we have 2 clips).
     
  45. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Ah, good! Yeah, 40 seems fine. And 6 for speed is good too, I don't think it's even noticeable if you're already at max speed because the deceleration isn't that strong

    I haven't really noticed any problem with animations, I changed the calculation of the speed parameter in the Animator controller to make it based on the current velocity instead of the input, and it looks fine with the current blend tree.

    I've made quite a lot of changes already though, and some odd behaviors keep arising, so yeah I'll probably drop this heh.

    I guess it's debatable but, if the differences were simply setting a couple of values in Animator, and enabling or disabling particles, I'm not sure if having a completely separate state for that is worth it, for maintainability of the TransitionTable (having to replicate all the transitions in both states) and performance. But alas, there are more differences than that, merging them would require a lot of rework and wouldn't make much sense. So don't worry, I won't be moving forward with that either. :D
     
    cirocontinisio likes this.
  46. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Hi ! I was playing with the NPCs and the StateMachine(SM) this weekend, and I realized something was still missing... A Nested State Machine !
    It can be useful when the SM becomes too big, or when you want to separate the logic.

    I implemented a new StateMachineActionSO, which can reference a TransitionTable, which starts the nested SM when entering the Action.
    You can find the working code in my own repo: https://github.com/Eyap53/open-project-1/tree/nested_state_machine. (I had to add a new IStateMachine interface, which could be empty but for keeping the current code, I added the Component methods).

    If you think that it's a nice addition, I will open a PR !
     
  47. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    @Eyap Hey! That's actually pretty clever. I've been thinking about doing something slightly similar, but haven't really even started. Anyway, I find it really odd having to implement that whole interface for that, but I guess it works... My main concern is, did you test/profile it? GetInitialState is very demanding as it re-instantiates all the objects (States, Transitions, Actions, Conditions) in the table, so calling it in OnStateEnter would be a bad idea, we'd have to implement some way to call it once in Awake and cache them.

    Perhaps it'd be possible to achieve what you want by making an Action that disables the current SM and enables a separate one so that you can switch between them? I'm not sure if it'd work, just throwing ideas here!
     
    Eyap likes this.
  48. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Actually this would be pretty simple, you just need to cache the initial state on Awake and then set the current state to the initial state in OnStateEnter:
    Code (CSharp):
    1. State _initialState;
    2.  
    3. Awake(...)
    4. {
    5.     // ...
    6.     _initialState = _cachedTransitionTableSO.GetInitialState(this);
    7.     // ...
    8. }
    9.  
    10. OnSateEnter()
    11. {
    12.     // ...
    13.     _currentState = _initialState;
    14.     // ...
    15. }
     
    Eyap likes this.
  49. Eyap

    Eyap

    Joined:
    Nov 17, 2017
    Posts:
    40
    Yeah I feel the same, but I didn't want to lose any current possibilities... So I just added all the component methods.
    But now that I think about it, maybe only adding the transform and/or gameObject properties would be enough (all other methods could be called through it). What do you think ?

    Not yet, but I will do it tomorrow, with the caching :).

    That's also a possibility. But wouldn't that mean that only the nested action could be present inside the State ?
    Overall, I fear that it's not gonna be very flexible and introduce a lot of edge-cases that way...
     
  50. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Regarding this nested StateMachine, I'd like to invite you to reflect on its UX :D
    The UX of the current SM is not bad, but also not stellar. Not referring just to the speed of using it, but also to the ability to be understandable for new users.

    Keep it in mind, please!
     
    deivsky and Eyap like this.