-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FrayCombatStateMachine documentation doesn't adequately describe how to receive its data #44
Comments
Thanks for bringing this to my attention. This is actually a left over from a much earlier design decision. I see no issue with accessing the public interface of the root state object for any reason. However, I did intend there to be a signal on the state machine node as that would be more intuitive; this is also something that should be mentioned in the 'getting started'. |
Also, what's the intended means of updating the values of And how do you define when a given state ends? ( |
More things I definitely need to make clear through documentation. |
So |
Yeah, essentially. This is a hierarchical state machine, so it's designed to support states within states. The end switch allows you to control transitions based on the "state" of a sub-state. Small elaboration on my part though... there is a |
This comment was marked as outdated.
This comment was marked as outdated.
Gotcha. That's a reasonable decision. However, it doesn't read to me like that due to how (it appears at first glance, at least) half of the functionality is already there ([again,] Were someone to implement their own approach, are |
Alright, finally got my hands dirty with this.
I tested it and found out the states, in fact, do not resolve that way. I could go into more detail but
after taking this approach I find it much cleaner and so I won't bother. |
Also, another question about The class itself claims to be an abstract class but the documentation shows it being instantiated instead of extended. Which is the intended way to use it? |
This is actually left over from an earlier version where they're were multiple condition types. The documentation will be corrected, thanks for pointing it out. |
All states can "end". A state is considered to have "ended" when It's set up this way in order to make the state machine system more extendible. You could in principle extend the base State class and code it so your own custom state which ends when... for example, it counts to 5 seconds.
Having now hopefully explained what I'm going for this this whole state ending thing, maybe you can better see what I meant by "Fray does not provide a state out-of-the-box which would 'end' when your attack ends". It's not that I cant, or that it would be difficult, but rather I would have to make a lot of assumptions of how the user is handling their animation states. Which would, in my opinion, lead to a class without much flexibility. Though if you disagree feel free to open a new issue relating to that, I'm open to discussion. |
I've made some comments above this so maybe by the time you read this things will change... But could you help me understand what you find confusing? Maybe I just don't do a good enough job clarifying the difference between the root state, state machine node, and situations. The transitions options, and more broadly transitions, only exist within root states. Some options take into account any state's "state", for a lack of a better term, but they all still live in a root state. The state machine node is only responsible for processing the root state, if Godot's processing wasn't all done from the scene tree, and if it weren't based around nodes this class probably wouldn't exist. Situations are a conceptual split up where you create a root state for each "situation" your fighter can be in. How you define a situation is arbitrary and entirely up to you. If you really wanted to theres nothing stopping you from only having 1 situation. It is a hierarchical state machine So you can on paper describe some complex systems with nested states.
There is a If by "implement their own approach" you're instead referring to my comment about creating custom states then yes, state machine transitions would still be involved.. But none of that directly relates to situations. |
I believe this is likely the root cause of most of my confusion.
By "implement their own approach" I did indeed mean handle changing situations. The connection arose due to a pattern I thought was present between user-defined states and (hypothetical) user-defined transitions. As you said, its intended for a user to be permitted to extend the base Again, though, I believe this was only due to the few remaining holes I have in understanding how all of Fray works together. |
I now have a relatively simple character controller that jump around. I think I figure it out how combat_state_machine works, which is a fancy names for a State machine that manage button, sequences and situation, the latter being a FrayRootState that can have starting and ending states. My approach is quite similar to 20milliliter drawing, with two situations. One for ground and the other for air. var _cond_on_floor := FrayCondition.new("on_floor") # = body.is_on_floor()
var _cond_in_air := FrayCondition.new("in_air ") # = not body.is_on_floor()
var locomotion :FrayRootState= (FrayRootState.builder()
.add_state("idle",MyGroundMovementClassFrayState.new())
.add_state('jump',MyJumpingClassFrayState.new())
.add_state('attack_1',MyAttackClassFrayState.new())
.tag_multi(['idle','falling'],['start']) # starting tag
.tag_multi(["attack_1",'jump'],['end']) # ending tag
.add_rule('end','start') # everything tagged end can transition to a starting tag at any moment once finished
.transition_global("idle",{
auto_advance=true,prereqs=[_cond_on_floor],priority = 1
})
.transition_global("falling",{
auto_advance=true,prereqs=[_cond_in_air],priority = 1
})
.transition_button('idle','jump',{
input='push',prereqs=[_cond_on_floor]
}) # can jump if on floor. Since it's tagged end the next state is either falling or idle, most likely falling
.transition_button("idle", "attack_1", {input="attack",min_input_delay=0.5})
.start_at("idle")
.end_at('falling')
.build()
)
var in_air :FrayRootState = (FrayRootState.builder()
.add_state('falling',MyAirMovementClassFrayState.new())
.transition('falling','to_land',{
prereqs=[_cond_on_floor]
})
.start_at("falling")
.end_at('to_land')
.build()
)
combat_state_machine.add_situation("locomotion",locomotion)
combat_state_machine.add_situation("in_air",in_air) To transition between situation I ended up connecting to the FrayRootState signals: locomotion.transitioned.connect(
func(from,to):
match [from,to]:
[_,'jump']:
pass # jump state class handle the jump and it automatically transition to falling state.
[_,'falling']:
csm.change_situation('in_air')
,CONNECT_DEFERRED)
in_air.transitioned.connect(
func(from,to):
match[from,to]:
[_,"to_land"]:
csm.change_situation('locomotion')
,CONNECT_DEFERRED) I must insist for you to connect the callable with CONNECT_DIFFERED if you are changing the situation, since this signal is emitted inside the logic when transitioning. If you don't do that it's possible to have a continuous changes of states. It's a basic example but I think it exemplify a need to facilitate changing situation when entering some specific states. The problem with CONNECT_DEFERRED was easy to encounter and was a source of frustration until I figured that changing situation while the library handled changing wasn't a good idea. I'm using situation as a parent states, and since I saw in another issues that sub states exists, it's probably better to handle it that way but I don't know how yet. Also, in my cases it's possible to have multiple End state in a situation. Falling is one that can be triggered by either quitting the floor or jumping, but every button or sequence referencing an attack would go to a AttackSituation. I'm just writing my findings of the last two days, if it can help someone. |
I saw your question on that issue and i'll address it shortly over there. But I wanted to say that while I do intend to support substates, I decided to not use substates to represent situations. I believe my reasoning at the time was that state machines should be deterministic; one input for each transition. However, I've considered changing that mainly because there's nothing else enforcing determinism plus users can already manually manage transitions if desired, and it might be more intuitive to use what's already there rather than adding this new concept that exists only on the CombatStateMachine. It really wouldn't require too much work on my part either.
I'll make a not to mention this in the documentation when I get to resolving this issue. However I wonder if there a way I could design this to avoid the need for a deferred connection, I wouldn't want anyone else to encounter the same frustrations even if the documentation mentions a solution.
Thank you for sharing! |
Closing as the state machine has changed fairly drastically since this issue was made. Also I believe the new documentation clarifies the issues discussed here. Though if you feel i'm wrong do open another issue! |
The CombatStateMachine is intended to recieve inputs or sequences and automatically resolve the transition of states, but how is that data meant to be forwarded to other nodes, such as a fighter's
AnimationPlayer
?FrayCombatStateMachine
contains no signals.FrayRootState
hastransitioned(from: StringName, to: StringName)
, but it is not referenced in any other classes and does not appear to be intended to be accessible from the state machine.What is the intended method of allowing an
AnimationPlayer
(for instance) to change its animation in coordination with a state transition within aFrayCombatStateMachine
?The text was updated successfully, but these errors were encountered: