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...

Комментарии • 108

  • @uheartbeast
    @uheartbeast  Год назад +51

    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

  • @baconwarriorgames756
    @baconwarriorgames756 Год назад +15

    Always used to watch your gamemaker tutorials back in the day

    • @uheartbeast
      @uheartbeast  Год назад +6

      Are you still messing around in gamemaker? I haven't tried it for a bit. I miss it sometimes, tho.

    • @baconwarriorgames756
      @baconwarriorgames756 Год назад +12

      @@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.

    • @theseangle
      @theseangle 3 месяца назад +2

      ​@@baconwarriorgames756you're a great brother 👾

  • @wreckingballgames
    @wreckingballgames Год назад +9

    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!

  • @BigBossRazz
    @BigBossRazz Год назад +30

    Babe, wake up, new HeartBeast just dropped 🎉

  • @ShinsukeMoriya
    @ShinsukeMoriya Год назад +9

    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

  • @RandomNewb
    @RandomNewb Год назад +7

    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!

  • @IndicIndieGameDev
    @IndicIndieGameDev Год назад +16

    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.

  • @randomjimbitz512
    @randomjimbitz512 11 месяцев назад +7

    You are by far my favorite GODOT tutorial creator. I really appreciate the effort and work you put in... Thanks!!!

  • @badunius_code
    @badunius_code Год назад +7

    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

    • @uheartbeast
      @uheartbeast  Год назад +5

      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.

    • @badunius_code
      @badunius_code Год назад

      @@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.

    • @uheartbeast
      @uheartbeast  Год назад +1

      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?

    • @LosfrogerX
      @LosfrogerX Год назад +1

      @@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

  • @craigcashman2275
    @craigcashman2275 Год назад +2

    I finally understand FSM after wtching multiple video. Hearbest is the best teacher ever.

  • @ModernArtery
    @ModernArtery Год назад +6

    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!

  • @HeroG9000
    @HeroG9000 Год назад +3

    Just got your book in the mail today! real excited to dive into it soon!

  • @nevdevyt4015
    @nevdevyt4015 Год назад +4

    Next up: Simple Turing Machine in Godot
    Jk on a serious note, nice to see you back so soon!

    • @uheartbeast
      @uheartbeast  Год назад +2

      Lol! Thanks. It's good to be back.

  • @codumus
    @codumus Месяц назад

    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

  • @littledev556
    @littledev556 3 месяца назад

    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.

  • @scronk3627
    @scronk3627 7 месяцев назад

    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

  • @liefhebber6277
    @liefhebber6277 8 месяцев назад

    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!

  • @viniciusantonio2253
    @viniciusantonio2253 11 месяцев назад

    the tutorial was so verbose that I finally understood FSM syntax/logic, thanks m8!

  • @GiantCloudGames
    @GiantCloudGames 11 месяцев назад +1

    Great vid! even being new to Godot, I was able to understand your process.

  • @Draidzeven
    @Draidzeven 11 месяцев назад +1

    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.

    • @MoogieSRO
      @MoogieSRO 11 месяцев назад

      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?

    • @Draidzeven
      @Draidzeven 11 месяцев назад

      @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.

  • @quantumrelativity3849
    @quantumrelativity3849 Год назад

    awesome timing I just learned about dfsa’s and ndfsa’s in uni

  • @mooplym1691
    @mooplym1691 3 месяца назад

    This video is fantastic and i finally understand finite state machines! thank you so much

  • @BillyWestbury
    @BillyWestbury Год назад +1

    Great tutorial, so easy to follow along with, thanks

  • @愚安-v7k
    @愚安-v7k 4 месяца назад

    really helpful for me to understand FSM,thank you!!

  • @Biowulf21
    @Biowulf21 9 месяцев назад +2

    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

    • @uheartbeast
      @uheartbeast  9 месяцев назад

      Yes I do! It's on sale right now: courses.heartgamedev.com

  • @goonba23344
    @goonba23344 8 месяцев назад +1

    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?

    • @montecrysto33
      @montecrysto33 8 месяцев назад

      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 :)

  • @brothaman4578
    @brothaman4578 10 месяцев назад +3

    Are all the "-> void" just a habit from coding in another language? I didn't think GDscript required a return type.

    • @TampopoInteractive
      @TampopoInteractive 3 дня назад +1

      This forces the type on the return, which helps with debugging.

  • @badunius_code
    @badunius_code Год назад +2

    9:10 that's where I would create a parent `Actor` class and inherit an enemy from it

  • @YOSFP
    @YOSFP Год назад

    wohoo! welcome back heartbeast :)

  • @itsViirtueYEAH
    @itsViirtueYEAH Год назад +1

    this is so well time, tysm

  • @thepolyglotprogrammer
    @thepolyglotprogrammer 11 месяцев назад

    Pretty good implementation! Subscribed!

  • @superdookiebros3788
    @superdookiebros3788 11 месяцев назад +3

    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?

    • @uheartbeast
      @uheartbeast  11 месяцев назад +1

      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 :)

    • @superdookiebros3788
      @superdookiebros3788 11 месяцев назад +1

      ​@@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.

    • @uheartbeast
      @uheartbeast  9 месяцев назад

      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).

  • @lovfall642
    @lovfall642 9 месяцев назад

    Thanks a lot for the tutorial! Really well explained 😊

  • @Mazzolli
    @Mazzolli Месяц назад

    very cool tutorial! thank you very much!

  • @slot9
    @slot9 Год назад

    A lot packed in to one video!

  • @ChristoffHavenga
    @ChristoffHavenga Год назад

    Great tutorial as always! Thank you.

  • @nikhilkadiyan4847
    @nikhilkadiyan4847 3 месяца назад

    Thank you so much, this helped me a lot

  • @wulfrickwille3871
    @wulfrickwille3871 Год назад +1

    Great tutorial ❤

  • @tryptamigo
    @tryptamigo 9 месяцев назад +1

    "saw_player() sounds like a horror movie". lmao

  • @Hanoolka
    @Hanoolka Год назад +1

    Thanks for the help

  • @monkeypanda-ib5cz
    @monkeypanda-ib5cz Год назад +1

    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?

  • @outofthenorv
    @outofthenorv 8 месяцев назад

    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

  • @yueteeee577
    @yueteeee577 Год назад +1

    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()?

  • @JrIcify
    @JrIcify 9 месяцев назад

    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.

  • @colecofer8474
    @colecofer8474 7 месяцев назад

    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

  • @khaledmoussa2668
    @khaledmoussa2668 28 дней назад +1

    11:16 For some reason Godot does not recognise get_local_mouse_position()

    • @khaledmoussa2668
      @khaledmoussa2668 20 дней назад

      @@TheWizardsRoom uuuhhhm I didn't continue that tutorial, it was too advanced for me...

  • @Lolmaniaco_
    @Lolmaniaco_ 9 месяцев назад

    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?

    • @porkman1838
      @porkman1838 8 месяцев назад

      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.

  • @kenknudsen7478
    @kenknudsen7478 11 месяцев назад

    Brilliant!

  • @psychobarge5279
    @psychobarge5279 10 месяцев назад

    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 ?

  • @krysidian
    @krysidian Год назад

    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

  • @GonziHere
    @GonziHere Год назад +1

    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?

    • @uheartbeast
      @uheartbeast  Год назад +1

      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.

    • @GonziHere
      @GonziHere Год назад

      ​@@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.

    • @uheartbeast
      @uheartbeast  Год назад +1

      I'd give the wandering state access to an enemy detector area2d and then signal out when it finds a target.

  • @jontay4199
    @jontay4199 8 месяцев назад

    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.

  • @theseangle
    @theseangle 3 месяца назад

    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

  • @moonnight9474
    @moonnight9474 Год назад

    thank you

  • @tutkarz
    @tutkarz Год назад +2

    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.

    • @uheartbeast
      @uheartbeast  Год назад +6

      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.

  • @hnydev
    @hnydev Год назад

    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)

    • @uheartbeast
      @uheartbeast  Год назад

      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.

    • @hnydev
      @hnydev Год назад

      @@uheartbeast Thanks for clarification :P

  • @sm5574
    @sm5574 8 месяцев назад

    Why are you sometimes specifying a void return type and other times leaving it blank.

  • @RandomGuy-kd3xs
    @RandomGuy-kd3xs Год назад

    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

  • @PixelatedPope
    @PixelatedPope Год назад +1

    Is there a reason you use the Actor property instead of just using the Owner property?

    • @PixelatedPope
      @PixelatedPope Год назад

      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?

    • @uheartbeast
      @uheartbeast  Год назад +1

      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.

    • @uheartbeast
      @uheartbeast  Год назад +2

      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.

  • @badunius_code
    @badunius_code Год назад

    15:50 yup editor does not autocomplete user classes.

  • @Flamebamboo
    @Flamebamboo 4 месяца назад

    can this also be applied for platformer games?

  • @Loethor
    @Loethor 10 месяцев назад

    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

    • @Jprojects1
      @Jprojects1 9 месяцев назад

      You every find an answer for this?

    • @TheMadLiteralist
      @TheMadLiteralist 6 месяцев назад

      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.

  • @LoIandfunny
    @LoIandfunny Год назад

    How can you make it so then you can drag the player(gd) to the enemy and then the enemy will follow the player

    • @uheartbeast
      @uheartbeast  Год назад +2

      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.

  • @harmoen
    @harmoen 9 месяцев назад

    Is that John Goblikon?

  • @grahamgordon9434
    @grahamgordon9434 Год назад

    thanks for sharing :) any reason for the state_finished signal?

    • @uheartbeast
      @uheartbeast  Год назад

      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.

    • @grahamgordon9434
      @grahamgordon9434 Год назад

      @@uheartbeast cool :)

  • @korbsoncoding
    @korbsoncoding Год назад

    nice video :)

  • @twelvethirtyfour
    @twelvethirtyfour Год назад +2

    1.25x speed everyone

  • @kimbaptempura4073
    @kimbaptempura4073 8 месяцев назад

    Thanks!!!!