Slay the Spire Clone Godot 4 Tutorial: Card Dragging & State Machines (02/08)
Vložit
- čas přidán 23. 05. 2024
- Welcome to the second installment of the "Slay the Spire Clone in Godot" series using the powerful Godot engine! 🎮
In this beginner-intermediate tutorial, we'll continue our game development journey by implementing the main card dragging mechanic for our game. Let's dive in!
🚀 Part 2 - Card Dragging Mechanic & State Machines
00:00 - Slay the Spire demo
00:40 - End result
01:29 - Creating the CardUI Scene
08:23 - Adding CardDropArea and Hand to the Battle Scene
13:06 - State Machine explanation
15:43 - Basic CardUI and CardState base class scripts
18:47 - Coding the CardStateMachine
21:06 - Adding Nodes and Scripts for the 4 States
23:00 - Coding the Base State
25:09 - Coding the Clicked State
26:21 - Coding the Dragging State
30:27 - Coding the first version of the Released State
30:55 - Hooking up all the systems, fixing bugs
42:10 - Using the CardDropArea do detect if we should reset the CardUI
46:27 - Fixing one last bug and wrapping up
👩💻 Source code for Season 1 on GitHub:
github.com/guladam/deck_build...
🎓 Learn More About Godot:
Godot Docs:
docs.godotengine.org/en/stabl...
docs.godotengine.org/en/stabl...
Heartbeast:
• Godot 4 Tutorial - Hea...
Card Fanning Tutorial by Bramwell:
• How I Fan 3D Cards in ...
☕ If you want to support me, buy me a coffee at:
ko-fi.com/godotgamelab
🔥 Connect with Me:
Instagram: / adamgulacsi
Twitter: / adam_gulacsi
Mastodon: mastodon.gamedev.place/@guladev
#godot #godotengine #2d #tutorial #godotgamelab
Hey Everyone!
Thanks for the overwhelming support you showed with this series so far! :)
I just wanted to let you know that I added timestamps and chapters for my videos so hopefully it's easier for you to navigate if you want to.
(you can also find them in the video description)
Keep on cookin' and Cheers!
I like how much in depth you are going with the architecture.
Thanks, I appreciate it! I believe breaking it down is a good way to learn these things!
So good to see a series with good code / architecture!
Glad you like it! :)
Right? I'm a little bit tired of tutorials with messy code and zero architecture thoughts
@@nandomax3 "Vomit on his IDE already, code spaghetti!"
I love how you show the final result of the episode at the beginning
Glad you like that!
I really loved the part that explained the principle and function of the object to be implemented step by step by taking the elevator as an example before working on the code, which most tutorials didn't put in even its the simplest and most central part as a "tutorial". So much appreciate for a great video and series!
Thanks, glad you like it!
I really appreciate how you run the program multiple times, and take little breaks going "this should work, right?" only to show us what we're missing, instead of just dumping all the correct code on the viewer right away. Explaining what is missing, or why it is necessary makes me understand the components a lot better. Also interesting to do input-mapping so late into the tutorial, usually CZcamsrs knock that away in the first few minutes, without ever really elaborating on it, whilst you do the code first, and the mapping second, makes me actually understand just a little bit better.
Despite having previous programming experience, GDscript is still a little tough on me, but I'm slowly learning, and hopefully after a few more videos I'll be comfortable adding my own additions! (like the 3D card fan, switching out the assets and themes, etc. and eventually adding my own non-StS gameplay mechanics) The Godot interface is still rather overwhelming, it's as if PhotoShop and Excel had a baby, with the sheer amount of menus and buttons, but your calm way of guiding the viewer through it is making it a bit more comprehensible.
I'll try to maybe get a tutorial or two done daily (though I might be lazy in the weekends) and hopefully by the time I'm done I can get a little creative with the code!
Statemachines are still a little scary, but I think I'm getting a hang of the logic, although the code formatting and syntax itself is somewhat complicated.
Ah! I've got a bug I can't seem to figure out!
Moving the cards, changing states, all works seemingly fine. The cards can enter BASE, CLICKED, DRAGGING, and return to BASE... But when I release a card it remains in DRAGGING rather than changing to RELEASED... I've looked through the code multiple times, and everything seems to be in order, I've got no idea where I went wrong...
@@marcelburdon9795If your problem is not yet solved:
I also had this problem and it was because I forgot the "not" in the first if statement in card_base_state.gd.
One comment: when pasting code (esp. for signals, e.g.: signal transition_requested(from: CardState, to: State), it would help if you explain the thought process behind each function or signal a bit slower.
EDIT: e.g.:18:10 onwards
Thank you!
Having a really hard time understanding 18:47 - Coding the CardStateMachine, even though I generally understand signals and nodes :-( what do you suggest I do?
Hey I can try to clear it up for you a bit:
The State machine does two things really:
1. Keeps track of which state we are currently in
2. Manages transitioning from one state to another.
To help code the states themselves (base state, clicked state etc.) we also delegate callbacks like input_event() and mouse_entered() so the state can handle those events individually.
The transition_requested() signal is used to provide a way for the CardStates to send a message to the State machine saying hey bro: I'm a base state but I want you to go into the clicked state when the user clicked on the card.
When transition from one state to another two things happen:
We call the exit() function of the old state,
Then we call the enter() function of the new state.
Does that help? I hope so, let me know if there's anything I can do to clear it up!
@@godotgamelab good explantion. I understand why you implemented the _on_transition_requested(from, to) signal now. At the moment only the CardStateMachine is listening to this event, but in the future we could attach other script to it to play sound, trigger other actions, and so on. I think I'm going to start a new study project here based on the space invaders theme and i'll use the knowledge you shared with us in this tutorial. I want a simpler base project to consolidate my learning, I'll create a simple FSM with three states move, fire states for the ship and a FSM for the enemy with just move and death states. I'll try to sketch it first and return to the tutorial just to clarify doubts about how you implemented yours
omg this series is exactly what I needed! Thank you so much!
Thanks for joining the Lab! 🥼🧪
You don't know how much it helps when you add chapters. The tutorial itself is great I love how you maintain to teach us with patience and good quality I'm more excited to see more of this series ❤
Thank you so much for your kind words, glad to have you with us! 🧪🥼
Very clear. Found you through your reddit post. I love your pace!
Thanks, I appreciate it 😊
Great video! Thanks for creating this content that allows beginners to see some more complex code that is explained well, as well as well-organized! Knowing how to organize code efficiently is something that I have been struggling with, but these videos are a great example!
Glad to have you with us! ☺️👌
Thanks for the tutorial keep it going. It's help me to learn about the State Machine more , still a little bit confused but i try to watch it over and over again.
Hey, happy to help!
If you're still confused about state machines I recommend checking out @TheShaggyDev 's channel, he makes some awesome content on the topic! 👌
@@godotgamelab
Thank you so much, i will check it. I try make the card game, this video really helped me with the implementation of the dragging and the others.
@@yogiwiguna9602 I also had some difficulties learning how to implement state machines, but after copying the simples exemple from the ShaggysDev, I could bootstrap my learning curve
This is a great series. Wonderful work mate.
Thanks mate! Glad you like it 😌
Im not trying to do this, but once i got watching, im pretty convinced that im gonna watch the whole series for the sheer amount of knowledge ur spilling. thank you!
thank you. keep it going!
Kúltúrált és megfelelő technológiát használ az úr!
Emellett értelmes kódot ír!!!
Hoppá hoppá! Köszönöm uram! 😍
Amazing content. I was wandering how to expand the concepts tought by the shaggy dev on the Advanced State Machine video to use signals to change states too. Your implementation looks really similar to mine, although I don't like the request_transition(from, to), it's a nice way to organize those piece of code
Thanks, I'm glad you like it.
As it's often the case in programming, there are many different ways to implement FSMs but I felt like using signals is a very "Godot way" to do it.
In the end, I believe that for your own project, you should use a method that makes the most sense to you. But it's always a good thing to keep an open eye and open mind! 👀
amazing tutorial. here's some engagement to bump it up in the algo
thanks, glad you like it! :))
Hey, first I want to say this is an absolutely amazing series!
I just have one problem I can't solve for the life of me. Everything is working with the exception of snapping the cards back to hand if you right click or play outside the play area. I've set up debug checks to see where it is going wrong. According to those everything is running and it seems that the child.reparent(self) call in hand.gd is running but simply not reseting the position of the card. I would love any direction on where to keep looking for issues!
Turns out the child.reparent(self) call was retaining the position of the child. To work around this I just replaced the reparent call with child.get_parent().remove_child(child) and self.add_child(child). It's not pretty but it works.
@@TheCamulous I had a similar issue, so tried this workaround. However it appears that should you release the first card, then exit a move with your second card, it snaps back all cards, including the previously released card. Any ideas? Did you get similar behaviour?
This is an amazing series so far and I look forward to watching the rest of it.
I think you're doing a great job of keeping concepts at the beginner / intermediate level. I am someone who knows more or less what the nodes are meant to do and has cobbled together a few small projects. Especially without a computer science background, knowing design patterns or game architecture is invaluable, and is teaching me stuff I didn't even know I needed to learn. You're doing a great job of explaining the "why" as well as the "what".
Glad you are enjoying it, nice to have you here with us! 😊
Thank you for the feedback, I'm happy to see you're learning new stuff!
Loving this, I've always wanted to create my own card game!
Quick question: What's the difference between func name(): and func name(): -> void?
Is the -> void part absolutely necessary after each func or is it redundant?
Hey, thanks for the feedback, glad to have you with us! 😌
As for the void return value I'll give you a pros and cons how about that?
Pros:
- easier to read to code at a glance (you see that it won't return a value right away)
- using static typing makes GDscript a tiny bit faster (though might be insignificant in this case)
- keeps the code consistent (I try to use static typing everywhere)
Cons:
- more verbose
- takes more to time to write for *seemingly* no reason
- you might consider it to be redundant
In this case, I think the pros outweigh the cons but it's really up to you and your personal preference. Hope I answered your question! 😌
Hi Adam, thanks for this video, I love your content. Please continue!
I would also like to leave my comment to suggest an improvement for this episode since I found it hard to follow and I perceived it as rushed.
I would have preferred to have an intro to the Input system of Godot either here or a link to another video, because it was hard to follow the part about propagation.
I Would have change the way you present the state transition by first creating Base -> Drag and then presenting the full architecture and in the end write the rest of the code. I am a programmer and the code and the state machine principle were not the problem. What I found hard was that I had to follow you switching between signals, direct reference in the code, some other mechanism of reference by group. I was also not expecting to find the state machine implemented as sub nodes instead of just class instances.
I will re-watch the video a second time.
Love your content !
Hi, thanks for the kind words and the suggestions.
I agree with most of what you've said. At the same time, this course was created for people who are already somewhat familiar with Godot. I tried to strike a balance of not over explaining because the videos are quite long already.
But you are a 100% right, this comes at a cost of sometimes having less structured explanations. Hopefully you'll find the other episodes clearer!
I love slay the spire I actually finished 1 playtrough with each character except watcher.
I didn't defeat heart but even tho the experience was fun 😊
Come on, you can beat the heart, you got this! Just need a sprinkle of luck and a great deck! 🍀💪
Really getting a lot out of this - thank you for taking the time to put this together!
Hoping you can help give some direction - the drop point detector for releasing cards seems to be triggered based on the bounds of the CardUI itself, not the mouse pointer. Is there any way to base that drop point snapping back to hand functionality on where the mouse is at when releasing, rather than where the bounds of the cards are? Because the way it's currently implemented, a card enters 'RELEASED' state on confirm even if just the very top of the card border has entered the card drop zone.
Hey, sure.
That shouldn't be too hard to change. Here's a soluition from the top of my head:
1, in the CardUI change the DropPointDetector's collision shape to a smaller rectangle, like 10x10.
2, update the CardDraggingState code, and change the card_ui.drop_point_detector.global_position to the mouse position when you start dragging the card
3, in the CardBaseState reset the card_ui.drop_point_detector.global_position to it's original position
and you should be good to go! :)
Wonderfully done, and exactly the series I need as a jumping-off point!
minor note: the naming format you're using is called snake case, not pascal case
snake = my_variable_name
pascal = MyVariableName
Yeah I keep messing that up, even in season 2 😂 I swear I know the difference but bruh. For some reason, speaking English messes up my brain sometimes I guess 🐍
@@godotgamelab The fact that you're putting out such quality content in a second language is hella impressive
@@TylerAlbers01 thanks, that means a lot me! Glad you like it 😊
First of all thank you for this series, I'm just watching it through the first time to get to grips with the concept before coding along side on my second watch to make sure I understand what's going on.
However I noticed something in this episode (sorry if you address it later): You take the CardUI out of the hand immediately on drag state, and then reparent it on release if it needs to go back - doesn't that change the order of the cards instead of restoring them to the original order?
I know in Slay and Monster train at least - cards always snap back to their original position in the hand, which will be important for mechanics like random discard and exhaust. Just wanted to check and make sure I'm getting this right!
I'm thinking when I approach this part I'll only take CardUI out of the Hand on release - but I don't know if that would cause other issues?
Glad you started doing the series! Don't worry, we'll fix this down the road. Nice catch!
@@godotgamelab Awesome thanks! I’m not surprised that you’re covering it later, considering how well you pick up the quirky bugs at the end of the episodes, and then providing great explanations and solutions to them. Really great way of teaching - since bugs are generally more memorable and it’s a good way of really getting students to remember it in the future. Thank you for doing this series and I’m looking forward to more :)
Just started with Godot and I'm loving this tutorial!
Just got a question:
When releasing the card outside of the card drop area, what input event is activating the on_input function to make it return to the base state?
Edit: I printed the event as text and it came back as a Mouse motion event. So when I drag the card by holding down the left mouse button then I release the card by releasing the left mouse button, how come a mouse motion event gets triggered after I release the card?
You have to grouw up with more subscriptors, I'm very boring to see always the same platformer series :) thnx! that's really hepful.
Thank you so much! Glad to have you with us! ☺️
Thanks for the training.
Just a small note, the part with if from!=current state, should be with an assert, not just if.
Absolutely, you can use asserts if you want to generate error messages. Thanks for the tip!
Hi, great tutorial. I have an issue, I « cannot call « non static function « is_node-ready » on the class « CardUI » directly. Make an instance instead. » does anyone know why and how to fix it? Thanks
Hi, @godotgamelab. I have a error:
Error at (18, 9): Cannot call non-static function "enter()" on the class "CardState" directly. Make an instance instead.
What do i do?
I'm around episode 5 now, but I went back to this state machine and I have a few questions:
- I can't help but think the "clicked" state is redundant. Even looking at the season 2 code, it doesn't seem to do anything other than go straight to dragging. Why not simplify so that base goes straight to dragging?
- What's the difference between on_input and on_gui_input? Around 20:00 we add both of them, and it seems like we sometimes use one and sometimes the other. From what I can tell they're not built-ins, so I don't really see why both are needed.
Thanks for making this series! I have some background in programming but game dev is new to me - it's been a great help!
i am also curious why there is a need for a "clicked" state. i was able to remove it in my game and go straight to the dragged state, but needed to increase the "DRAG_MINIMUM_THRESHOLD" variable to avoid transitioning directly to released. maybe it has something to do with that?
I'm only at episode 2, but I think I can answer your second point.
You're correct with the fact that the "on_input" and "on_gui_input" method in "card_state_machine.gd" and "card_state.gd" aren't built-in.
But they get called from "card_ui.gd" through "_input" and "_on_gui_input", and those are built-in signals.
In terms of difference, I think "_input" always accepts the input while "_on_gui_input" only accepts the input if the cursor is on the CardUI.
For example, if you change the "on_gui_input" in the "card_base_state.gd" to "on_input", you end up dragging all cards at once wherever you click.
Hey everyone,
Thanks @vexave for explaining, you summed it up nicely!
If you want to read more on Input, here's the relevant Docs page:
docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html
On the gui_input signal itself: docs.godotengine.org/en/stable/classes/class_control.html#class-control-private-method-gui-input
In terms of the ClickedState, here's my two cents:
- it's an easy concept to understand when you list all the States you can have: Base, Clicked, Dragged, Released. Pretty intuitive if you ask me.
- It can happen, that you click on the card but don't move your mouse. In my mind that is clicking and not dragging if I don't move the mouse. There's no way to handle this seperately if you instantly transition into dragging.
- UI is always the part of a game that changes the most during development. IF you want to do anything in the future ONLY when you are in a clicked state it's much easier to implement then blending those 2 states together.
I would argue that it's so simple to implement that you don't lose a lot of time, it's more flexible and it even makes more sense to me as it's easier to draw the StateMachine's possible transitions. That said, if you feel like it's superfluous you can always go directly into the dragging state instead! Do whatever makes sense to you! :)
Hope that helps!
Hey, love the videos so far, but I am having an issue with the CardStateMachine node. The class CardStateMachine doesn't appear in the Inspector window for me. I'm unable to change its default state to Base. All of the children within the CardStateMachine have the CardState class and I can select their States. Just not their parent node. Is there a way to fix that?
Have you created the script for it? Make sure you attach the CardStateMachine script to that parent node. If you have the proper code for that class, the export variable will appear in the inspector!
not sure if this is a bug or working as intended, but due to the way the "dragging" state only updates 'on_input', if you click and hold the left mouse button on a card, release it outside of the 'card_drop_detector_area' (in the hand or lower part of the screen), but do not move the mouse or click anything else, the card will float as though it is still in the "dragging" state until you do either move the mouse or click something.
i was able to fix it by changing the 'on_input' function to a '_process' function, but i'm not sure how that will effect performance or interact with other systems down the line.
oh, and thanks a ton for this series! i've been binging it over the past few days and really appreciating so many aspect of your presentation style.
Hey,
Thanks for pointing this out. Someone reported a very similar issue to this. We fix it later down the line!
func on_input(event: InputEvent):
I assume that this function will be triggered when any input is given?
since there are no process function i assume that's how these system works
But wait this is a ui node and i dont know much about them right now, so am i correct?
If i am then are they triggered on every input set up in project's input map?
If you don't know much about how Godot handles input I recommend reading the docs:
docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html
In short: _input(event: InputEvent) is called whenever an input event occurs. We can filter it out for our own desired inputevents we want to handle. In this game there are three:
- left click (defined in the InputMap project settings)
- right click (same)
- moving the mouse while in the clicked state (it is called InputEventMouseMotion)
Thank you for this tutorial, it's very interesting.
but what is the purpose of the "on_gui_input" function in the script CardState (17:57)?
Thanks, glad to have you here. Great question!
We provide 4 callback functions for the different States in our StateMachine which they can override.
gui_input() is for GUI nodes specifically. We'll override this in the BaseState latern where we prevent any kind of interaction for that card when its disabled and shouldn't be interacted with. Hope it makes sense!
@@godotgamelab Sorry, I just saw the message, thanks for the explanation.
I am really enjoying this series, I just did not really like the last part with the lambda function, your code is so well structured and the lambda parte made it look like it was rushed. I am changing that last part for a timer nested inside CardDraggingState node, when CardDraggingState enters, the timer starts and then connects to a function without a lambda function, it felt cleaner to do it this way.
But then I caught myself asking, ok so he is very organized, is there any advantage to use that lambda? Maybe something I haven't learned yet...
Hey, I'm glad you like it so far. I think it's up to personal preference.
If the timer + timeout feels cleaner to you do it that way by any means. Don't really see any advantage picking one over the other 🤔
In this example what guarantees on_input of released state to be called? In my tests it is mouse motion, but it feels a bit ugly to wait for input to return to base state. Checking the list of targets when getting out of dragging state feels so much cleaner.
I'm not sure what you mean by that. on_input() is called when whatever input event is registered for the card: this can be mouse motion, mouse clicks, actions pressed, anything really...
Input-based cancels can only happen when we right click to cancel out the dragging, I don't see the problem of using the input callback for this. But use whatever you think is clean :)
Great series! I have a bit of a question in the adding nodes and scripts for the 4 states section. I am unable to assign any states to the 4 nodes some reason. When I click assign state in the inspector tab, every node or choice is greyed out. I was only able to set a default state for the CardStateMachine node. How can I fix this? Godot 4.2.
Hey, it seems like the Editor doesn't recognize your state nodes as state type nodes. Make sure to:
- assign scripts to those nodes
- in their assigned script, don't forget the "extends CardState" line so they inherit all their properties from the base CardState class.
If both of those are are done you should be able to pick them in the inspector. If still not, try Project --> Reload Current Project from the menu after checking both of them.
Let me know how it goes!
@@godotgamelab I'm having a similar issue. Followed all your steps above but I don't even see the CardState option in the window, I can only see Node
@@EvanGSinclair If you didn't forget to attach the script to the nodes and followed all the steps, you might want to reload the editor.
Go to Project --> Reload Current Project. Sometimes it solved issues like this to me. I guess these inconveniances are still expected with Godot 4. Lemme know if that helps!
@@godotgamelabworks now, I think the issue was something to do with the "Template" selected when making the initial scripts. Yours were "Object: Empty" mine were "Node: Default" . That's the only difference I could find.
I would've liked if you went back to the state machine graphic at the end and overlayed the script names we created to relate them all back to the concept.
Also, I couldn't reproduce the click error at the very end (Godot Version 4.2.1). Followed those steps of course anyway!
Likely gonna go through again and comment my code so I get what it does. It very much feels like to me like this is real properly written code with very good practices and things. Thanks!
Good idea, thanks for the feedback! :)
Thanks for great tutorial. Thing became much cleaner for me. Especially when you point on issues and show how to address them.
One thing I didn't catch up - why do we need and exit_state function? Looks like it does nothing for a current step of tutorial. So whats the purpose of it?
Glad to hear that!
At this point, we don't really use it but sometimes we might. Later we use that logic, let me give you an example:
When we start aiming with a single targeted card (i.e. go to the aiming state) we will notify another node (the CardTargetSelector) that we started aiming so we can display an arc and select an enemy.
But when we finish aiming, we want to notify that same node that we finished the aiming process so it can hide the arc and turn off the target selector mechanic.
However, we can finish aiming for 2 reasons:
- we canceled it or selected an invalid target (we go back to the base state)
- we selected a valid single enemy (we play the card)
If you think about it, it doesn't really matter which happens because we want to turn off the target selector in both cases. For that reason, we can use the exit() function so whenever we leave aiming (for whatever reason) we can notify the CardTargetSelector that the aiming process has ended.
Hope that makes sense and helps! :)
Curious, my cards are not showing as base but I cannot find any errors that could guide me to the issue.
Can you tell me what lines or functions I need to check to find the issue.
🤩
Just started this series, thanks for putting it up!
I noticed a small bug towards the end of the video (don't know if it'll get fixed later):
If you click on a card without releasing the left mouse button, drag it over to the side (not inside the play area target), and then release WITHOUT moving the mouse, it'll go into the release state, but still stay stuck in its position. Then, only if you then provide any input, it'll snap back to the hand/base state. I think this may be a consequence of the input function being the only thing that transitions the release state back to base state?
I eventually fixed this through the dragging confirm code, checking for the target area there (if target area isn't empty, it goes to released, if not, it goes back to base). Hope this doesn't break any code down the road 😅
That's a good point, thanks for pointing it out. I'll look into it ☺️
@@godotgamelab the series is really great so far! I especially love how it’s not for beginners, since there are a lot of those kinds of tutorials. Going through and reading and thinking about the code you’re using is really helping me expand my knowledge of gdscript and coding in general! Thanks!
glad you like it and thanks for your kind words! :) @@Ichthyoid
Megacrit saw this and thought We could make this better
oh mate, I wish I had that kind of influence 😅
can't wait for the StS 2 though!
hi. what is the purpose of line "var card_ui := child as CardUI" (in hand.gd). Is it for code readability ?
Sure,
In your example we are referencing the CardUI node which is a Control Node in the SceneTree. The truth is that the code works perfectly fine without using the "as CardUI" bit.
Let's see what is does. When we type "as CardUI" we cast it from a Control Node to a CardUI type. This way, when we use the variable's name in the script, we'll get auto-complete (intellisense) based on the CardUI class. The result is that you don't need to remember property names and functions precisely and it's easier to code while also it's less error-prone because you make less typos.
Also, this is we use "class_name CardUI" at the top of our scripts! So we can use CardUI as our own custom type (class)
You can try this if you start typing card_ui. in your code and press ctrl+space. You'll see all the suggestions correctly but if you delete the "as CardUI" bit, you'll only get suggestions based on the built-in Control Node class.
I hope that clears it up a bit!
If you want to refer to the docs, you should look up the reference page for GDScript itself:
docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html#casting
@@godotgamelabthanks for the detailed answer. Your tutorial rocks.
There is an issue here. If you move the mouse and then click on the card, the card immediately transitions from the 'Clicked' state to the 'Dragging' state. This is likely because the previous mouse movement event was also captured. How can one implement the action of moving the mouse, clicking, and then staying in the 'Clicked' state, only transitioning to the 'Dragging' state upon subsequent mouse movement?
Hi i followed the guide and i dont ran into an error where the cards state dont change into BASE automatically and i cant hold them would you perhaps know the reason?
After adding CardStateMachine node and code for card states, i started getting next error: Invalid get index 'state' (on base: 'Control (CardUI)'), after tring to run the game, wich references line card_ui.state.text = "BASE" in card_base_state.gd script. Does abybodyhave some suggestions maybe?
EDIT: I had a typo as always, namely wrote label incorrectly in card_ui script.
When i enter the lines that are highlighted at 33:08, it tells me that an argument is expected for lines 24 and 28. I've triple checked my syntax and can't see any differences. Any idea what's up?
which lines you have problems with? there is no line 28 at 33:08
@godotgamelab it's the card_state_machine.on_mouse_entered and exited. They both say that they expect an argument. Sorry, I think my timestamp may have been the last frame it was on screen, but it was where I had it paused while rereading.
Edit: I finally got some time and just started over. Pretty sure I figured out where I messed up. Everything is working and I am progressing now. Thank you for your time.
Hiya!
I'm getting Parse Error: The function signature doesn't match the parent. Parent signature is "enter() -> void". This happens with the "initial_state.enter()" in the card_state_machine script. Not sure how to deal with it since i've been pretty much copying everything. Any advices or explanation would be awesome :)
Can you send me a pastebin link of your card_state_machine.gd and card_state.gd? It's hard to answer without seeing the code
@@godotgamelab I seem to have a similar problem, as there is an error stateing "Identifier "initial_state" not declared in current scope" on the "if initial_state:" line. I have been copying everything, so any help would be appreciated.
@@cobysuk-3294 sounds like you don't have the initial_state variable declared at the top.
Here's the CardStateMachine class's final version from GitHub: github.com/guladam/deck_builder_tutorial/blob/5bad93421de9c0e1978ec842de31342f7f7e4137/scenes/card_ui/card_state_machine.gd
Hopefully you can find what's wrong by comparing them.
For the CardState and derived classes you can check this folder to see what's going sideways: github.com/guladam/deck_builder_tutorial/tree/5bad93421de9c0e1978ec842de31342f7f7e4137/scenes/card_ui/card_states
Cheers,
Adam
@GodotGameLab I am getting an Error within the card_base_state script: "card_ui" not declared in the current scope.
where in the tutorial did you declare this? is it still uptodate or am I doing something wrong? hope you still read these comments!
Wow i am dumb, i typed in a previous script: vi instead of ui, that v really looked like an u :D
Happens to the best of us 😅 glad you fixed it!
Hello, on the "card_dragging_state" and "card_clicked_state" I am getting an error saying that the identifier "transition_requested" is not declared in the current scope.
From this error I have actually found a lot of stuff I missed trying to figure out what I did wrong but now everything looks like yours and it still says it is not declared in the current scope.
Hey, does your base CardState class exist? If so make sure that the CardDraggingState and CardClickedState actually extend that class where we defined that signal. Then, it should work!
I've got a Mac with a trackpad. Unfortunately, I can't test a right click action.
38:32 Could someone explain this part to me? Why does adding this variable fix anything? It doesn't seem like the variable name is being referenced anywhere so why would this prevent the crash that was just happening...?
Hey, I'll try my best:
- this is a dependency for the CardStates because they might want to do stuff like animating the CardUI, calling methods on the CardUI and so on.
- we declare this variable here to make sure that each and every CardState will have a card_ui available.
- you're right: in itself it does nothing. However, in the CardStateMachine class, we will actually assign the parent (the CardUI node itself).
- later in the video, when we code the CardStateMachine class we'll have az init() function where we set this variable.
Hope this makes sense, cheers!
@@godotgamelab oh shit. i think i missed that the individual card state scripts *are* already referencing the drop_point_detector var. so that makes sense why the crash was happening then. thanks for the explanation and the video(s)
What kind of input event we're handling in on_input function in release state 44:20? I assume that we are already handled release event in dragging state. We are just waiting for random event to happen? This code is weird for me
I think you're a bit confused about what get_viewport().set_input_as_handled() does. From the docs:
"Stops the input from propagating further down the SceneTree."
That means if we click with the LMB to confirm releasing the card, no other Node can pick up that left click. Omitting this can lead to some unexpected behaviour. For example, you start dragging a Card and you move it above another Card in your Hand. If you click with the LMB again, that SAME InputEvent (left click) can be picked up by the other Card.
That's not what you want because in general you want one click to do one thing right? So if you click to release the first card it gets released. If you decide to pick up and drag another you want to click AGAIN for that which should be a new InputEvent.
Does that make sense?
@@godotgamelab Actually not, this is not what makes me confused. My question is in what event case we are entering "on_input" function inside card release state? What event makes transition from release state to base state?
@@thetiphonOh, I see sorry. That was a misunderstanding from my part.
Here, really ANY kind of InputEvent makes the card to go back to the base state because the enter() function gets called first when we enter the state.
The on_input() callback function can only trigger AFTER the enter() function has already finished its execution.
We have a flag called played which can be either true or false depending on whether we successfully played the card or not.
In our on_input() callback we check this flag. If it's false AND we received ANY KIND OF InputEvent this card can get (left click, right click, releasing the card or just moving the mouse over the card for example) we can transition back to the base state because it means we couldn't play the card as we had no valid target.
Does this help?
If you're curious what event triggers this, put a print(_event) before the transition_requested signal and when you play the game you can see which event triggered this but it doesn't really matter.
@@godotgamelab yes, thanks. Your code architecture is great. Pleasure to see
thanks, happy to help! @@thetiphon
Hello! i'm currently experiencing issues with the snapping back to the original position on right click, i can see from the text on the card that it does go back to base state, but it doesn't get the base state position
Hey, you need to reparent it to the Hand so it goes back. When the game is running, you can use the remote SceneTree to check if the raparenting actually happens!
@@godotgamelab what should i be looking for within the remote SceneTree? also it changes the text back to "base" but the position remains where i placed it
@@godotgamelab i don't know where it goes wrong, because when i check the label it has the state"BASE" but the position is just where i left it
so at 38:50 when starting to click and drag, when clicking anywhere at all, all three cards display "CLICKED", and nothing else happens at all. No errors either. Godot 4.2, Compatibility. But before this, when testing and hooking up, I didn't get exactly the same no-error or errors at same time as in tutorial.
Hey, haven't tried it with 4.2 but everything should be working I think. Make sure to double check these:
- code for the State machine itself
- code for the individual states
- in the card UI scene, the state nodes should have their correct enum value type set as their export variable,
- double check if you set the mouse filter options correctly for the Control nodes in the card UI script.
Hopefully you can find what's causing this behaviour.
@@godotgamelab tested with 4.1, and also Mobile mode, same issue, also looked at most code and stuff (not all maybe, and probably missed something), can't find any mistakes yet, will report if I fix it.
@@dimtool4183 yeah it's probably in the code. If you can't find the mistake, you can zip the project and send it to me, so I can try to help. You can find my email in the channel page
I am also on godot 4.2 and this works like a charm
in the card_ui script make sure that the input function is "_input" and not "on_input" had the same issue and this fixed my problem also make sure to check the scripts that @godotgamelab mentioned
My cards will transition to the base state, but no matter what I do, I cannot get them to transition to “clicked”. I’ve double and triple checked and everything should be working. It seems to either be something in my settings or something where the base state does not emit its signal correctly I can’t seem to fix it.
Hmm, there are a lot of ways this can go wrong... Did you connect the signals in the editor too? You need to connect the CardUI nodes signals to their corresponding functions.
@@godotgamelab I got it all the way to the Dragging State. It will return to the base state on a right click. It will set to input_as_handled on a right click. However it will not register a mouse release nor will it transition to the Released state. The only difference in code is that I used on_gui_input instead of on_input.
@@godotgamelabOkay, I went back and redid the video. There was a tiny error in my card_state_machine
@@nicholasmoreno6358 Hi! I've got the same problem where the card will enter CLICKED and DRAGGED, and return to BASE, but won't enter RELEASED, just staying in DRAGGED.
I've looked through the code many times and can't seem to see any issue, what was your problem, maybe I'm experiencing the same?
I should have missed something, but let me leave here my experience: moving position of cards in _gui_input might not work when your card is too small. _gui_input only sees what happens on the control, so it may lose the cursor if it moves quickly.
I don't really get it. I use pretty small cards too (20*30 pixels).
Can you move them faster than the input event registers? That seems a bit weird but I need to test this.
@@godotgamelab Thanks, you indeed got a point, this may not be a general bug but system-specific. I noticed my get_global_mouse_position() returns different coordinates every call.
I hope to find an answer by myself in a few hours, but I have this problem, where only thing that's being processed by the game is first click. All the cards together go orange and @clicked", but no dragging or anything. Any ideas?
I do have the same problem. Have you found a solution already by any chance or does anyone else have an idea?
no matter where you click, it will permanently change the state of all cards to "CLICKED"
@@XxKagarwaxX I believe I did. Will Get to the PC and check for you
@@tars9063 you are my hero
@@XxKagarwaxX didn't find it then, found now. Check card_state_machine script, func on_input, if you have "current_state.on_input(event)" or on __gui__input
@@tars9063 thank you for checking back!
Sadly I already have the current_state.on_input(event), so there is likely another mistake I have made.
from the card_state_machine-script
func on_input(event: InputEvent) -> void:
if current_state:
current_state.on_input(event)
func on_gui_input(event: InputEvent) -> void:
if current_state:
current_state.on_gui_input(event)
Does this part of the tutorial work in godot 4.2.1 to?
Yes, it should!
Thanks!
@@godotgamelab
Idk what I did wrong but I get "Cannot call method 'is_node_ready' on a null value. Im not able to fix it :c
Fixed it
Hi, how did you fix it?
The hierachy was wrong in the CardStates, basically I make the Drag, Click and so on inherit from the Battle node instead of the CardStateMachine or however you called it like
@@itadaimasuIf I have not explained myself good enough lemme know
Invalid call. Nonexistent function 'on_input' in base 'Nil'.。。。。。thats why?
If I press CTRL+INIT() and so on, I can jump to the function I wrote before in card_state_machine.gd, but I still get an error
I tried to follow this tutorial with C#, but the reparenting and signals part is getting out of hand. I think I will stick to gdscript even if i dont like it much...
There is someone in the comments who did the whole thing in C# under one of the videos...
Maybe you can try to contact them?
hi, I'm new to this series; great content so far! i'm making a deckbuilder myself and I approached the drag and drop using the built-in functions instead: _get_drag_data, _can_drop_data, _drop_data. do you have any thoughts/opinions on using them instead of your apprach? i feel like it can avoid the need for state machines
here's what I followed: czcams.com/video/IaAqhIC5DaI/video.html
my cards call the _get_drag_data function
my card drop areas call the _can_drop_data and _drop_data functions
Hey!
That works fine too, but I find it a bit too limiting for this game so I opted for a different solution. The Godot built-in version only works on Control nodes, and you have to set a preview which shows below the cursor. I feel like having more control over the dragging for a Card game is a worth trade-off if you have to / want to change things up later.
If the built-in solution works for you however, I recommend sticking to it because it's actually much much easier then implementing it on your own like we do :)
Cheers!
@@godotgamelab well said! i'm going to try the built-in route as i follow along your course. props to your solution though, it really gives much more freedom to do what's needed. thanks for your perspective
Your channel icon kinda looks like Godot givin me the middle finger..
It's supposed to be a lab bottle. Nothing personal, promise 😅
Great series so far! I'm a bit stuck unfortunately, I'm getting type errors in _on_transition_requested when I click my cards.
E 0:00:01:0988 card_base_state.gd:14 @ on_gui_input(): Error calling from signal 'transition_requested' to callable: 'Node(Card_State_Machine.gd)::_on_transition_requested': Cannot convert argument 1 from Object to int.
core/object/object.cpp:1140 @ emit_signalp()
card_base_state.gd:14 @ on_gui_input()
Card_State_Machine.gd:26 @ on_gui_input()
card_ui.gd:18 @ _on_gui_input()
However my card_state.gd seems fine:
What could cause my transition requested to consider my from variable as an int?
------------------------------------
class_name CardState
extends Node
enum State {BASE, CLICKED, DRAGGING, AIMING, RELEASED}
signal transition_requested (from: CardState, to: State)
@export var state: State
var card_ui: CardUI
func enter() -> void:
pass
func exit() -> void:
pass
func on_input(_event: InputEvent) -> void:
pass
func on_mouse_entered() -> void:
pass
func on_mouse_exited() -> void:
pass
------------------
class_name CardStateMachine
extends Node
@export var initial_state: CardState
var current_state: CardState
var states := {}
func init(card:CardUI) -> void:
for child in get_children():
if child is CardState:
states[child.state] = child
child.transition_requested.connect(_on_transition_requested)
child.card_ui = card
if initial_state:
initial_state.enter()
current_state = initial_state
func on_input(event: InputEvent) -> void:
if current_state:
current_state.on_input(event)
func on_gui_input(event: InputEvent) -> void:
if current_state:
current_state.on_gui_input(event)
func on_mouse_entered() -> void:
if current_state:
current_state.on_mouse_entered()
func on_mouse_exited() -> void:
if current_state:
current_state.on_mouse_exited()
func _on_transition_requested(from: CardState.State , to: CardState.State) -> void:
if from != current_state.State:
return
var new_state: CardState = states[to]
if not new_state:
return
if current_state:
current_state.exit()
new_state.enter()
current_state = new_state
Hey,
Check for the signature of your _on_transition_requested function:
The first parameter should be CardState (you have CardState.State instead), and the second one is CardState.State (the enum)! That should do it
@@godotgamelab Thank you so much!!
at 32:52, I've typed:
------------------------------------------------------
func _ready() -> void:
card_state_machine.init(self)
-------------------------------------------------------
I get an error.......Invalid Call. Nonexistent function 'init' in base "Nil'
The init function seems to be set up in Card Machine script correctly.
----------------------------------------------------
func _init(card: CardUI) -> void:
for child in get_children():
if child is CardState:
states[child.state] = child
child.transition_requested.connect(_on_transition_requested)
child.card_ui = card
if initial_state:
initial_state.enter()
current_state = initial_state
------------------------------------------------
I have very little experience with coding. so issues are harder to logically figure out in many cases.
this means that the card_state_machine node is not found.
You need to make sure ifthe CardStateMachine node with the script actually exists in the CardUI scene AND in the cody your @onready var reference to the CardStateMachine node is correct.
Thank you for you reply....again, a little lost!. The CardStateMachine node, with script, is in the CardUI scene....not sure what you meant by "AND in the cody". I assume cody is code, but if so not sure what that means.
@@718Outdoors I meant that you need to reference the state machine as an onready variable like so:
@onready var card_state_machine: CardStateMachine = $CardStateMachine
Please note that this course isn't really meant for beginners. What I'm saying is you can expect a lot of struggle and bumps because this is aimed at intermediate users.
Hope that helps nevertheless!
@onready var card_state_machine: CardStateMachine = $CardStateMachine as CardStateMachine
func _ready() ->void:
card_state_machine.init(self)
,,,,,,
but still had Invalid call. Nonexistent function 'on_input' in base 'Nil'.
@@godotgamelab
That's what I wrote, but it's still the same mistake as him.@@godotgamelab