Finite State Machines - Godot 4 Tutorial (Intermediate)
HTML-код
- Опубликовано: 19 сен 2024
- Thanks for watching this tutorial on finite state machines!
If you are interested in taking a deeper dive into the Godot game engine you can buy my courses at this link: www.heartgamed...
Here is the starting point for this tutorial (MIT): github.com/uhe...
Here is the completed code for this tutorial (MIT): github.com/uhe...
This video shows how I currently set up more complex state machines in my own projects. I hope you find it useful, even if you end up making some changes or improving it for your own projects.
Check out the video description for a link to the GitHub and the setup files for this tutorial.
I guess RUclips doesn't like links in pinned comments now. They deleted every pinned comment with a link across my entire channel.
Enjoy your weekend!
- Ben
Thank you, Ben! ❤
RUclips has been dumb these last years
Always used to watch your gamemaker tutorials back in the day
Are you still messing around in gamemaker? I haven't tried it for a bit. I miss it sometimes, tho.
@@uheartbeast yeah I’ve actually been showing my little brother the ropes(he’s 10). I thought gamemnaker would be a good place for him to start and it seems like he’s loving it.
@@baconwarriorgames756you're a great brother 👾
A small piece of code about halfway through gave me a more elegant solution for rotating something randomly in a game I'm working on for Godot Wild Jam right now. Thanks for all the excellent tutorials!
Babe, wake up, new HeartBeast just dropped 🎉
Lol
Learned FSM's and composition 2 days ago. Very nice video! will help a lot of people to get into game making in a more organized way LOL
I was literally going to look back at some of your old tutorials for FSM as I'm iterating on my own projects now from your different tutorials/videos and this literally drops today, thank you!
Just found your channel and subscribed!
Lots of great content, I am seriously considering migrating my pixel based action RPG to GODOT.
One suggestion - It would be great if you can start your video with a quick demo of what you will achieve at the end of the tutorial.
You are by far my favorite GODOT tutorial creator. I really appreciate the effort and work you put in... Thanks!!!
I'm excited to see you going into more advanced topics.
Few more notes:
- you could extend fsm node's script to manage state changes, this would unclutter goblin's script
- usually _process func of the current state is called by the fsm node, this way you don't have to turn processing on/off inside the states themselves
Hey! Thanks for the tips.
I wanted the transitions between states to be linked to the enemy itself because when those transitions take place and to which states may be different depending on the enemy type. As an example, I may want an enemy that transitions from a wander state to a shooting state instead of an attack state. I'd personally rather have that logic in the enemy itself rather than the state machine.
The second point is a bit of personal preference for me. I'd rather have the states explicitly enable and disable the functions they need. It is a more work to set up states, but I like that the states feel more godoty.
@@uheartbeast sure, I see your point. What I meant is you could put all the boilerplat-y connection code in the fsm node script instead of root node script. This way It will still be enemy-specific but won't be cluttering the main script.
That sounds like a good approach. I'm still having a hard time visualizing in my mind what you mean, though. When you say boilerplat-y connection code do you mean the part where I connect the signals to the change state function or maybe you mean the bit about enabling the physics_process in each state?
@@uheartbeast I think they mean that you should put the state connecting part inside an extended script from the FSM instead of inside the goblin script. I think both can be valid, but I'd prefer putting it inside the goblin script, to not have to generate another file just for the FSM
I finally understand FSM after wtching multiple video. Hearbest is the best teacher ever.
This was great! I'm immediately diving into trying out new and custom states. (And making things that are really buggy and don't work right but that's how you learn right?!) I've been going through your Heart Platformer tutorial recently, and I was thinking of adding a state machine for the player controls. I've implemented a FSM in other engines, but this was a great overview of how to do it in Godot. I love the approach you used here. The use of signals connected to state changes is really elegant, and makes it really intuitive to put the pieces together. Thanks for the awesome content!
Just got your book in the mail today! real excited to dive into it soon!
Next up: Simple Turing Machine in Godot
Jk on a serious note, nice to see you back so soon!
Lol! Thanks. It's good to be back.
Thanks so much for this tutorial! I was struggling to wrap my head around how to implement states and how a FSM actually works. The other tutorials I found didn't cover node layouts and dependancies but I finally am at a point I can set up my own FSM thanks to this tutorial
Finaly some good FSM tutorial. I was trying to understand this topic for a looooong time, because everybody makes video on 10 minutes and you have to watch them on 0.5x speed just to understand their actions. But this is finaly good old slow tutorial that explains a lot.
Wow thanks so much for this, I’m not far in yet but it’s the most approachable look at an FSM that I’ve seen yet
Hi Ben, thanks for this! I was already doing state machines in my own way, but it involved enums and tonnes of if statements. Just made my scripts such bloated, unwieldy messes with tonnes of places where things could go wrong. This has none of that. It also helps for my current project, which requires implementing new character behaviours for different items. Thanks again!
the tutorial was so verbose that I finally understood FSM syntax/logic, thanks m8!
Great vid! even being new to Godot, I was able to understand your process.
Hi! Nice video :) Have you considered just wiring the signals directly between states though? What I've done is: (1) write a change_state func on the fsm class, (2) write a generic enter_state func in a state base class that calls up to that, passing self as the argument, (3) make state classes that implement overridden on_enter_state and on_exit_state funcs, (4) make the fsm change_state call on_enter_state and on_exit_state funcs as appropriate. With that setup, you emit a new signal from any state, click on it in the signal panel, then click on any other state and pick the enter_state func to wire them together, without bouncing through another script. The change_state func also becomes a good place to do any routine stuff, like the set_physics_process calls in your example, without having to specify it in every state file.
But what if you don't necessarily always want one state to wire to that exact other state? What if, for some entities, you want that state to wire to a third completely different state instead?
@MoogieSRO That's the whole point of signals, you can unplug and plug them in the editor, and the sender and receiver don't need to know anything about each other.
If you have a scene per entity type, then each type will have a unique tree of states, with a unique statemechine instance at their root, right? Those are the nodes you'd wire together, and that configuration would be unique to that entity, even though their share some or all of the same state node types.
awesome timing I just learned about dfsa’s and ndfsa’s in uni
This video is fantastic and i finally understand finite state machines! thank you so much
Great tutorial, so easy to follow along with, thanks
really helpful for me to understand FSM,thank you!!
Awesome tutorial! Do you have a whole Godot Course by any chance? Your teaching style is so easy to follow and simple that I'd buy it in a heartbeat
Yes I do! It's on sale right now: courses.heartgamedev.com
That was a great video! This approach to implementing a state machine is my favorite so far. I do have a question though - is there a way to pass arguments between states?
Haven't tried it yet, but from what I get there are two main ways I would go about it.
First, all the states seem to need to share the actor the StateMachine is linked to, and thus have access to its properties so you could pull the data from here.
Second, in the StateMachine's change_state() method, you call both the old state's exit_state() and new state's enter_state() methods. What I would do is have exit_state() methods return a Dict of data you would like to sent to the next state, and have enter_state(dict) take that Dict as an argument to look into it and pull the data.
I don't know what you need this for, but as is, I would say the first option looks easier to deal with :)
Are all the "-> void" just a habit from coding in another language? I didn't think GDscript required a return type.
This forces the type on the return, which helps with debugging.
9:10 that's where I would create a parent `Actor` class and inherit an enemy from it
wohoo! welcome back heartbeast :)
this is so well time, tysm
Pretty good implementation! Subscribed!
I think it has been well past 7 years since I bought your original HeartBeast PDF for GameMaker 1. RUclips recommended this video to me, as I've recently been toying around with Godot (out from Unity).
It is such a welcome nostalgic feeling hearing that the opening has still not changed after all these years,
"Good morning afternoon or evening wherever and whenever you are"
Are you planning on transitioning into doing Godot for your future projects/lessons? Or is this just while Godot has picked up some steam from the Unity fallout?
Hey! Welcome to Godot and welcome back to the channel! I switched over to Godot quite a while back (when it was Godot 2) so I'm planning to stay with it :)
@@uheartbeast Oh wow, you're right! I just looked through your channel, you've been busy on there!
I think I always just imagined you went from GM1 to GM2 to GM:S and have been plugging away making nice lessons there.
I'm looking forward to going through your lessons and feeling the nostalgia all over again :)
Have you made any Godot Udemy lessons as well? I think I just have all of your GM ones.
Unfortunately, I don't use Udemy anymore because of how often they changed their pricing policy and their coupon policies. I do have some courses on my own website (courses.heartgamedev.com).
Thanks a lot for the tutorial! Really well explained 😊
very cool tutorial! thank you very much!
A lot packed in to one video!
Great tutorial as always! Thank you.
Thank you so much, this helped me a lot
Great tutorial ❤
Thank you! Cheers!
"saw_player() sounds like a horror movie". lmao
hahaha
Thanks for the help
Hello Heartbeast, thank you for these videos sharing your expertise.
The goblin is in the chase state then goes to the wander state, it would be cool for the goblin once out of the chase state to have 10 seconds in a "Seek" state. The "seek" state im thinking would have the goblin run faster seaching for the cursor. In that 10 seconds is it possible to have the goblin follow a pathfinding state till it sees the cursor or the time runs out. If it doesnt find the cursor it goes back to wander but rather than wondering anywhere, if i wanted the goblin to remain roaming around the area where the goblin saw the cursor last. Would that be a different state from the starting wander state? isn't the pathfinding used for AI to avoid objects and move around the map more effectively? I think it is, so i wondering rathet than using a pathfinding state and a follow cursor state separately can both pathfinding and follow cursor be used at the same time always, not just one or the other? Or is this redundant becuase both are more alike then i realize?
I absolutely adore your content! but you need to do a better job of adding tag words for stuff like this like "enemy chase" or something more common to draw in more people to this amazing tutorial
Hey heartbeast thanks for the great tutorial. I have a question, when connecting signals to the finite state machine why cant we call the change_state function with the enemy_attack_state as a parameter? Whats the reason we need to use the .bind()?
I hit a snag while trying to use this. I didn't understand that enabling the physics process is what causes a State to be "active" or not. If a State using this design has a regular process() function, it will run process() every frame whether it's the active State or not. So even if your enemy isn't a RigidBody and doesn't use the physics engine in any way, you must enable physics_process() and use it. For me this design is a very surprising way to accomplish this, and I thought it was using quirks of plain nodes and gdscript that I wasn't aware of yet. It's not actually like that.
I'm a little confused why you made the BuisnessGoblin connect to the Enemy script, which I believe you said would be the base class? Seems like there should be a BuisnessGoblin script as well, which then inherits from the base Enemy script?
Also, thanks for the lovely videos
11:16 For some reason Godot does not recognise get_local_mouse_position()
@@TheWizardsRoom uuuhhhm I didn't continue that tutorial, it was too advanced for me...
Hello, HeartBeast
First, thank you for your tutorials, I've done some of them and they're brilliant and helped me a lot!
Second, I have one question about the @export. For example, in the EnemyWandererState you export the player, animator and raycast, but why do you need to do that? Being the node's father and siblings, can't it just access the data doing an @onready thing-y?
If you move nodes around, export will adapt to its current position in the scene tree. A lot more dynamic than giving it the exact route. That is one reason, but it also makes the code a little bit more reusable I guess.
Brilliant!
Fantastic tutorial ! how would you implement parallel states though? for example you have an enemy that can be idle or tracking the player, but you have a "boosted" state, and the enemy can be idle/boosted and tracking/boosted ?
Did you ever find out why the autocompletion for the _exit_state didn't work? I am having the same problem. Also thanks for making a video on State Machines. Still trying to wrap my mind around various design patterns
I have one question though: normally, you'd chase something else, not a cursor. Therefore, your wandering state would need to pass what it saw to your chase state. Where is the "canonical" place to do so?
I usually have a way to access the player through a MainInstances singleton. The singleton has a player variable that stores the Player node and the enemy can find the player through that reference. There are a lot of ways to do this, though.
@@uheartbeast cursor or player are still singleton-ish. Imagine a bunch of RTS soldiers... all are wondering, one sees a particular enemy soldier and switches to combat mode... you need to communicate this particular enemy from one state to another (as in combat state has TARGET, but wandering state is responsible for creating said TARGET).
I'm asking since I've recently had an issue with this pattern (written by someone else), where I needed to communicate one state to another and by doing so, I've kinda broken the "state holds state relevant data" idea and I didn't see a solution that I'd consider good yet.
I'd give the wandering state access to an enemy detector area2d and then signal out when it finds a target.
Can anyone explain why I can’t access velocity? Or move and slide? I understand that some notes have access to things that other nodes don’t, why can’t I just give the nodes that I’m working on the functions or variables that I want to use? If anyone can shed some light on this, I would really appreciate it.
because your enemy isnt a character 2d
Can't you just export the state that you want to transition to from the source state?
E.g.
class_name ChaseState
extends State
export on_lost_target : State
... fsm.change_state(on_lost_target)
So that you can just select the transitions you want via exports instead of writing transition code in the root node each time
thank you
I see one problem with this example, because basically in all games there is more than one enemy. So if you add more goblins to the scene then they all will run to the player if one of them will see player, due to how signals work, because all of them will be activated. Or those signals are limited to the scope of one enemy? And if so, then why not just call function instead of signals.
Hey! I'm using this in my game currently and it works just fine. This signal is only being connected to the single instance of the enemy and not to every other enemy in the scene. I like your thinking, though. It shows you are trying to anticipate the potential consequences of the code structure.
Hey HeartBeast, i dived into stateMachines and you are great help, but, i don't think you are using this one right? To my knowledge disabling/enabling physics process inside state scripts does nothing, and the physics process function still run even though a different state is currently active. I don't believe it was supposed to be like that? I've seen a different approach that uses the physics process and process functions in the state machine code, and calls a custom function inside states so this can be avoided (Sorry if my english is bad)
I'm using this state machine in my game, and it works well. Disabling physics process does work. You could use custom functions and that is a great approach. It comes down to personal preference some. I just wanted to use Godot's built in functions and that's why I went with this route.
@@uheartbeast Thanks for clarification :P
Why are you sometimes specifying a void return type and other times leaving it blank.
Anyone know how to use this state machine for player like get input and change state any other method to change state of player movement to idle or attack
Is there a reason you use the Actor property instead of just using the Owner property?
Also, curious about your naming convention decision. I'm currently naming all of my "Files" with PascalCase where as you said you "now prefer" snake case. Any particular reason?
Godot uses snake case for files now by default (Godot 4) so I'm just switching to the new standard there. Personally, I don't mind either one.
Hey! Good to see you here :)
Good point. Using owner would work really well. It certainly has the benefit of removing some extra setup with each state.
I guess the only benefit of explicitly exporting the actor is if you wanted a state that performed operations on some other node in the scene. Not sure why anyone would ever want that tbh.
I was leaning toward the side of slightly more work but being more explicit. For example, I could have exported actor in the state parent class, but I wanted to be able to explicitly type the "actor" node to get code completion when performing actions with it.
15:50 yup editor does not autocomplete user classes.
can this also be applied for platformer games?
Hi HeartBeast, thanks for yout video.
A small question: how do you make the enemy target a player instead of the mouse? the target is in another scene, so I can't select it as the exported variable.
thanks for your content
You every find an answer for this?
Make a singleton node (let's call it WorldObjects ) with a currentPlayer property. When the player is created, it sends a signal to the WorldObjects node, and updates the currentPlayer. Then you just need to ask the WorldObjects who the current player is and you'll get it.
How can you make it so then you can drag the player(gd) to the enemy and then the enemy will follow the player
For this I usually have a singleton like MainInstances and then I have the player store a reference to itself in that singleton. Once the player is stored in something like MainInstances.player I can access the player in enemy states through that reference. There may be better ways of doing it, but this works pretty well for me.
Is that John Goblikon?
thanks for sharing :) any reason for the state_finished signal?
Ah great question! That is just a generic signal for states that don't need special case signals. As an example, maybe I have a resting state for an enemy where it stops and rests for 2 seconds. I'd just emit the state_finished signal after 2 seconds for that state instead of creating a new signal.
@@uheartbeast cool :)
nice video :)
Thanks!
1.25x speed everyone
Thanks!!!!