hi my name is Adam welcome back to gdo game lab in this video we're taking a little bit of a breather because we did some heavy lifting in the last two videos so in this one we'll explore godo's resource system in a lot more detail you can see that we already have a player character and this crab enemy has a health component attached to it so we'll see how we can use our Resources with the nodes we have and if I left click with the mouse button you can see that the crab enemy has the portion of its health and gain some block after that and if I right click the same happens to the player character loses some health gains some block and the stats UI gets updated accordingly by relying on the resource for the data itself so without further Ado let's get started first of all I promised the more in-depth look of resources in gdo so why don't we start with that we already created a theme for the project right that's a resource so what's the advantage of using resource for this while the biggest one is that we only need to define the design specifics once and share that data between different nodes and scenes so if we design how our buttons look like what font fuse for the project and so on we can share these details between all the different screens and menus we have in the game so for example the main menu the level up screen the save load window the options menu the credits the game over screen you get the idea and then if the designer comes in and says okay this button should be changed we only need to change it in the theme itself and all the screens will update accordingly resources are basically data containers meaning their primary goal is to store and contain some sort of data in the case of the themes if you think about it it's all the design specifics the font size the font type how buttons look like what kind of style boxes they're use and so on we usually don't use resources by themselves we want the nodes to use the data which is contained in the resource files right and another big Advantage is resources are only loaded once in gdo if you try to load the resource again when it was already loaded then you'll just get the reference to that already loaded resource preventing you from loading it over and over again creating a performance bottleneck and the last one which is a big one is that they can be nested so a resource can contain another resource in its own data container I took all these from the goo documentation so if you want to read more about the resources you can check the link just to show you another example in our game we'll Define the character stats as a resource and we'll reuse it in a bunch of different places like the battle node in the battle you I knowde to display the current Mana for example or in the player node to display the stats the health and the block and so on so with this solution these nodes don't really need to communicate with each other to get these data because they can just access it from directly the resource itself cool right so open up the project and we can start creating a bunch of new custom resources in the file system doc let's select the custom resources folder right click create new script let's start with the card pile and make sure that it inh it's not from the node class but from the resource class press create double click and open this up okay so the first thing we do is we define a class name for the card pile so we can use it as a static type throughout the project then we'll Define a signal when the card pile size has changed and we'll pass in a parameter for the amount of cards we'll have after the change and then for the data structure it's pretty simple actually the only thing we need to have is an array of cards but we'll Define a bunch bunch of utility functions because if you remember we can also create functions inside of resources so we'll Define a function to check if the card pile is empty or not which is just returning whether the array is empty or not then we'll have a function for drawing a single card which will pop the front value from the array then emit the card pile size changed signal and return the card we popped from the array here you could argue that popping the front value from an array is much more resource heavy or performance heavy than popping the back value but I think for this project worrying about something like this would be premature optimization right because when you think about slay the Spire how big your deck can get when you end your run it's like 30 to 40 cards maximum I think and with an array of 30 or 40 that's that's not a problem that's not a performance bottleneck so you don't need to worry about this if the game gets a per performance hit because of this function because we use the pop front instead of the pop back we can always refactor right so we don't need to prematurely optimize for this I think okay next up we'll have a function to add a new card which means appending the card to our array and omitting the card pile size changed signal we'll have a method to shuffle our deck which is really just calling the shuffle function for the array we also have a function to clear the card pile which means clearing the array and again emitting the signal that the card pile size has been changed and finally I override the two string function so if we ever want to print out a card pile for debugging purposes we'll just use a p string array for the strings and iterate through all the cards and append the string to the array with the position of the card and the card's ID and join those strings or array of strings together with a new line character and return that string from the function go ahead and save the script and that's all we really need for the card pile resource but now if we go back to the file system we can create a car pile namely the warrior characters starting deck so let's expand the characters folder we can collapse the cards under the warrior select the Warriors folder right click create new resource and search for card pile and again because we Define the custom class name we should see see this resource available from this list press create and let's call this Warrior underscore starting deck save this double click to edit it in the inspector and you can see that we only have one property for the cards which is fine because we only defined one export variable so if we click on this array we can say that let's say we have 10 cards in our starting deck and this will all be empty and if we expand the cards F again we can drag the warrior X attack and the warrior Block cards and to and to fill up the empty empty spaces we could do something like dragging it again but the way I usually do this is just to drag it from here so we have five attacks and five lock cards as the starting deck so that's all for the card pios for now and let's move on to the next one the next one we'll create is the stats so again let's select the custom resources folder right click create new script call this stats. GD and again inherit not from the note class but rather the resource class press create and double click this stats. GD to open it up so the stats script is a really important one because we'll use this resource to keep track of the health and the block for both the enemies and the player character too so let's start by defining a custom class name stats so we can use this as a static type throughout the whole project then we'll have a signal when the stats changed we'll use this signal to notify for example the UI to change how much health or block an enemy or the player currently has then we'll have two export variables one for the maximum health and one for the image the texture of this Anem player character and we'll have two member variables health and block and they use their own custom Setter functions so we can emit the stats change signal so for the health when we set it to a new value we do two things first of all we clamp the value the new value between zero and maximum health why because we don't want the health to go over the maximum health we can have and we don't want it to go under zero either because we don't don't want to have negative Health right and then after changing into this clamped value we emit the stats change signal and for the block we'll do something real similar the only difference is that we clamp the value between 0 and 999 because that's the maximum block you can have in state Spire so I figured i' go with this too then we'll have a function to take damage which is a bit more of a complicated one so first of all if the damage is less than or equal to zero we can return from the function because we don't need to do anything otherwise we store the initial damage value in a separate variable and then we calculate the real damage we take and to do this we'll subtract the block from the damage number and clam that value between zero and damage because if we have more block than the damage we would take that would mean taking negative damage which doesn't really make sense we need to clamp this value between zero and damage and then we update both our health and the current block we have we use self.
block and self. health here to make sure that the setter function gets called so for the block to calculate how much block we'll have remaining we need to subtract the initial damage from our current block and again we can clamp this value between Zer and block to make sure that we don't go under zero into the negatives and for the health we just subtract the real damage we calculated from our current health I hope that makes sense we'll have a function for healing which is pretty simple we'll just update the health with the amount we want to heal and we'll also have a create instance function which returns a new stats resource so what we do is we duplicate this resource we have and initialize it by setting the health to our Max health and setting the block to zero and return this new resource instance so why do we need this function at all well the reason behind this if you think about it you can have multiple enemies from the same kind so let's say we did this C crab enemy in the scene so let's say you have to fight three crab enemies right and if you remember I said that resources are loaded once so all three crab enemies would share the same resource we defined in the enemies folder for the crab enemy stats and if they have 10 health for example and they share the same one resource we Define in the project folder then that would mean if one of the crabs takes three damage all of the crabs Health would be set to seven from 10 for example because they share the same resource and it is only loaded once we want to avoid that right because we want to track the stats individually for all the crab enemies but to do that we need to duplicate the resource and we do this from code so you wouldn't have to do it in the file system because if you remember we create resources in the file system right and okay if I want to have 10 Gremlin enemies I don't want to create 10 identical resources just so we can can track their health individually it's much easier to do this from code because we'll just provide a function like this and we'll call this function in the enemy's script to create a duplicate of the source source data so to speak I hope this makes sense let me know in the comments if I need to explain this in more detail okay but for now we're done with the stest script so save this with contrl s and so to test out the stats and create a new resource let's go back to the file system Doc and for now we can collapse the folders so we can see our structure better and create a new folder under the root folder so let's select the root folder create new folder and we'll call this enemies under the enemies create again a new folder let's call this the crab enemy and if we expand this we'll have this crab and here we can create a new resource search for stats. GD create a new one and for now we can call this test enemy save this and double click to open it up in the inspector and we can you can see that we have the two values we we wanted we have the max Health let's set it to something like 10 and the art which is a a texture so we can go to load art and use this crab enemy Sprite so we'll have an example stats resource we can use to test out the UI later that's it for the stats and the last resource we'll do in this video is the character stats which extends this basic stats functionality we have here so again go to the custom resources folder right click create new script call this character stats.
GD and this one will inherit neither from the node and neither from the resource class but rather the stats class we already have and double click the script to open it up so what do we need to do in the character stats resource well first of all let's not forget that we have access to all the stuff we provided in the stats resource so the health the block the taking damage function all of that good stuff we we already have access to and we'll build on top of that so first of all let's define a class name and let's call this character stats then we'll have three export variables one for the starting deck which is a card pile if you remember we already created that resource by the way and we'll have a number to determine how many cards we can draw in a turn and we'll also have a variable for the maximum Mana this character has and then we'll have member variables one for the current Mana we have we'll also Define a Setter function for this just like we did for the block and the health and we have three more card piles actually one for the current deck the player has one for the discard pile and one for the draw pile too so the setter function for the Mana is pretty simple we set the value to the new value we passed on and we'll emit the stats change signal so the UI can update accordingly and one thing you could ask hey Adam we use this clamp function extensively to set the block and the Hand why don't we use it here well because if you think about State as P for example The Watcher character has a card called miracle and if you use that card it cost zero and provides you for that turn an extra Mana this means that in the case of the Mana we can actually go above the maximum limit we have in our deck temporarily but we can so in this case it doesn't really make sense to clim the Mana between the maximum man let's say zero and you could argue that okay we can clamp it between 0 and 999 so we can't go into the negatives and while yeah you can do that I don't think it's necessary because in theory you can't really go into the negatives because if you don't have enough Mana to play the card then you can't play it so you can't subtract from your Mana that much but again if we find a bug for this later on in the project we can always refactor it right it's just one line of code and we can clamp between 0 and 99 or 999 for now I just leave it as it is so the next up is the reset Mana function which is just calling s. Mana so the server gets called and resetting it to the max Mana this will be called in the start of every turn then we'll have a simple Boolean function to check if we can play a card or not we'll pass that card as a parameter and return whether the Mana is greater than or equal to the card's cost then we'll have a create instance function which serves a similar purpose to the one we've seen with the base stats resource but here we need to do a bunch of other stuff so first of all we duplicate the character stats instance then we set the health to Max Health the block to zero but then we also reset the Mana we set the current deck to a duplicate of the starting deck why do we need this well because if you think about it the starting deck we would use at the beginning of every run but then you start drafting new cards and new cards added to the deck some cards getting removed from the deck right so we need to separate between those two because if you were to modify the original deck then you would be in trouble because it saved on the disc and when you start the next run you wouldn't start with the 10 cards we have the five Attack cards and the five defense cards but you would end up with starting with your deck from the previous run so we need to have a current deck and we need to have the starting deck as a separate card pile and then for the draw pile and the discard pile we start them both as empty card piles we create a new card pile with this card pile. new and with everything set up we return this instance from the function that's all the code we need for the character STS resource now let's go ahead and create it so go back to the file system Dock and expand characters and under the warrior if we collapse the cards we can put the character right next to the starting deck I think so right click on the warrior folder create new resource and search for character stats but we have it here and you can see it's under the stats so you can see that it's extending that functionality press create call this Warrior double click so we can edit it and you can see that we have the properties from the character stats script but also from the stats script so the starting deck will be the warrior starting deck so we can drag and drop about this cards per turn will be something like four Max Mana will be three the max Health we can say something like 35 and for the art we can load the Warriors Sprite so let's use which one do we use let's use this one now save everything and for this video that's all the setup we need for the stats and the characters we can create the UI to display those health and block numbers to see the health and block in game Let's create the stats UI next so click on the plus sign to create a new scene let's go to 2D View and this time we'll start with an hbox container Noe zoom in a bit double click and rename these two stats UI so so under layout and transform we can set the size to something like 90 by 16 and we can anchor it in the center whichever works fine here I'll use Center top and under the stats UI we'll create two new nodes and these will be hbox containers again so the first one will be for the block so let's rename this to block I can press contrl D to duplicate it and rename this to health and under both of these we'll need again two nodes uh so let's click the plus sign again we use first of all we use a texture rect for the icon and if we select block again and hit plus we use a label as a second one let's rename this to block image and this one to block label we can select both of these you know what let's let's um set them up first so when we copy them over it will be less work for us to do so for the block image first of all let's load the texture and we'll use use this Shield icon and you can see that it looks a bit weird it's because stretch mode is set to scale so let's see let's use keep aspect centered so you can see that it keeps the aspect ratio so it looks a lot better and for the block label we can use a test number something like um six and oh okay what's going on with the font it looks much bigger don't forget that if you select the stats UI root node we need to apply the theme we have so under theme we have this empty go to quick load and load the theme we have and you can see already the font gets updated so if we go back to the block label U we can align this to the center on both the horizontal and vertical axis it looks like it's not centered properly because vertically it it's not the same height as the as the icon or the container itself so let's change that and go to layout container sizing and instead of shrink Center we'll use fill so you can see it fills the whole container vertically and now it looks a lot better but still the the image and the label are a bit far away from each other so if we select the block hbox container and go to theme overrides and constants we can check this separation checkbox to make sure that they have zero separation between each other and I think this looks fine and actually it's easier to just delete this health hbox container for now and select the block one and duplicate this right it's much faster can rename this to hell and we can rename both of these two Health image and the other one as health label and change this image the texture should be whichever heart I use heart.
png okay that looks fine but you can see that here zero separation may not be the best because the aspect ratio is different so they are much closer to each other so here we can go to theme overrides under the health hbox container and increase the separation I think two pixels looks perfect and maybe here we can use a different number so it doesn't look the same cool yeah what else do we need if we go back to the stats UI you can see that it's aligned to the left which I think looks a bit weird so maybe if we go back to the top the inspector panel we can set the alignment to Center and one thing we also need is we need access to both of these labels right because we want to set them from the code based on the stats we have so to do this we could just reference the onready variable like we usually do but with UI you can always change your mind about that later is this hbox container l layout the best what if you want to display the block and the health under each other and change it to a vbox then when you change the node structure for the UI you have to update all the on ready variables but with good 4 uh we have something called seen unique names so if you right click this node and see this percent sign accesses unique name if you turn this on for the labels then um you will have this percent sign here and you can access these nodes if they have unique names for that scene you can access them from the script no matter where they are in the scene three basically and I think that's all the setup we need so let's save this scene contrl s go to go back to the root folder go to scenes UI and stats ui. dscn is fine and let's see how we can hook up this scen script for it to work so first of all let's select the stats UI root node and attach a new script to this stats UI that GD is fine press create and delete the autogenerated code so what do we need in this script well first of all let's start by providing a class name for the stats UI and then we'll have four already variables references to the block and the health containers and to the labels to both labels the block label and the health label and you can see that they have this per sign before their names instead of the usual dollar sign meaning that they are using this scen unique names feature then we only need one function really the update stats function which receives the new stats as a parameter and updates the block labels text and the health labels text according to the block and health we have in the stats and we also update the visibility of the block container and the Heth container based on the amount of block and health we have so for example if we have zero Block in this turn then we don't need to display the shield and put a zero next to it we can just hide it right and that's probably the easiest way to do it and that's all the rest to it to the stats UI script it doesn't need to know about anything else is just a self-contained UI script so let's go ahead and save this so in the next step we'll hook this up with the mock enemy and we can also create a scene for the player we can continue by creating the player scene so click on the plus sign to create a new scene go back to 2D View and for the root node we'll use a note 2D we can rename this two player and originally in my reference project I've used an area 2D for this but looking at it now I couldn't really justify using one because we never want to really check if we Collide or Target the player at all and why don't we go with the easier solution right and later on if we do need to somehow check if the mouse is over the player or not for whatever reason we can always go back and refactor this right so that's not a big deal so for now I just stick to using a basic node 2D and as a child We'll add the spry 2D node and if we select the player the root node again we'll instantiate the stats UI we created so click on instantiate child scene and search for stats UI it's right here so open this and we can zoom in to see what's going on and for the Sprite 2D let's use a placeholder image so we can see how this will look like so select the Sprite 2D go to texture load and we can use the same Warrior image for example and you can see that the stats UI is not really at the right place because they overlap so if we select the statsu go to move mode by pressing W and if I start to drag I hold down shift so I'll only move uh vertically and I think some like this looks fine so that's all right what else do we need for the player node the root node we will also go ahead and add it to a to its own group so go to the Note Tab groups and in this text box type player for example and add it to this group you can see that again this icon appears no is in this group Player so why do we need this at all when we'll Implement card playing and card logic in the game we need to make sure that the cards can access their targets so we'll have one group for the player character that's this group the player group and we'll have another group for all the enemies which we call the enemies group so we'll use get three. get noes in group to access the valid targets when we play cards and with that finished we can save this scene h in the SS folder create a new folder let's call this player and player.
tscn is fine and we need to hook up our character stats resource with the stats UI and we'll do this from the root node the player node so let's attach a new script call this player and don't forget to delete the autogenerated code so what do we need to do here well first of all we'll Define a new class name for the player then we'll have an export variable for the stats which will be of type character stats and we'll also Define a Setter function for this we also have two on ready variables one for the Sprite 2D and one for the stats UI and here to get intense we use as stats UI after the node path and the setter function first of all creates a new instance of the character stats resource and assigns it to the stats export variable and then we want to connect the stats changed signal to our update stats function but here I used an if if check to see whether we already connected the signal or not and only connected if it's not connected yet why do we need this Val in G do the setter function for an exported variable gets called even when you run the game so by assigning a value in the editor calls the Ser function when you run that scene and what that means is we can accidentally connect it twice because later on when we expand the project we'll only assign the character stats to the battle node which is at the very top and we will set all the other dependencies from there which means that in theory we could connect the signal twice if we already have an export variable set up for the player scene and that can lead to unexpected Behavior but you could ask Adam okay why do we bother with this why don't we just make this a member variable and set it from the battle node where we wanted to well we could surely do that and uh many people I think do this or do it this way I think an upside side for this solution even if it looks or sounds a bit more complicated is that we can test the scene in isolation right because if we had a setup function which we Ed to inject the dependency then we need someone to call that setup function in order to test the scene independently and really with gdo if you want to debug a project or you want to find an error or you just want to see how the player scene itself works you want to be able to run it on its own right not just in context so this solution with a bit more overhead I agree that it does produce a bit more overhead we get the best of both words right because we can still test the scene in isolation but we can also do the stuff we wanted to do originally to get the dependency from somewhere else and after connecting the signal we call the update player function and the update player function will first of all check if our stats is an instance of a character stats resource because if it's not then something really messed up happen then we can return from the function immediately but if it is a character stats resource we need to check if we are already inside the tree or not and if we're not we need to avade the ready signal why is that well remember I told you that if we set the export variable in the editor then the Ser function will be called when we run the game and from the Ser function we call this update player function too right and in theory we could call this function sooner than the node is ready and that would lead to an error because after this we want to set the Sprite to this texture to the one defined in the stats resource so we need to make sure that the node is already inside the inry and then we can assign the texture and we'll also call the update stats function the same one which we connected to the signal and this update stats function is pretty simple we'll just delegate this call to the stats UI and make sure it handles updating the health and the block and last but not least we'll also have a take damage function where we'll first of all check if we we are already dead or not because if we are already dead we don't need to take damage but if we're not we'll use the stats take damage function which we coted earlier and also after taking the damage we'll check if our new health is less than or equal to zero because if so we can Q free the player we can delete it from the scene and that's all for the player code go ahead and save this and we can instantiate it in the battle scene to see how it looks like so go back to the battle scene go back to 2D View select the root node the battle node click on the instantiate chart scene and search for player okay we added our new player to keep things organized I'll drag this let's say under or below the enemy and we can move this to someplace else press W to enter move mode and we can put the player I don't know somewhere around here looks fine fine and we can also go back to the inspector Tab and you can see that the stats are empty so we can assign our stats from the characters Warrior folder right and if we run the scene should be working and it is so you can see that we have 35 Health by default which is perfect that's what we have in the stats resource instead of the six block and four Health we have as a place holder value in the statu I see okay so we're done with the player let's move on to the enemy next so fortunately for us for the enemies we already have a bunch of stuff going on right because we did some mock enemy first earlier in the project so we have the area 2D which is set up correctly we have the Collision shape and the Sprite so we can reuse those just go ahead and right click this enemy and click save Branch as scene in the scenes folder we can create a new one and call this enemy and save it as enemy. TSN and you can see that it collapses and we no longer have access to those children to the child nodes so we can go ahead and pick open an Editor to open it in a new tab and we can edit this so first of all let's select the enemy the root node and reset its transform because we don't want every enemy to start from that position so under transform we can click on this reset icon cool we still have the Collision layers and masks set up and all that good stuff we'll go ahead and add a new Sprite 2D to this because we want to create an arrow to indicate that this is a selected Target so we can rename this to Arrow and for the texture we can load the arrow. png from the art folder and we can position this better so under transform let's rotate this by 90° and press the W to enter move mode I'm holding down shift so I only move in one axis and yeah something like this looks better and we can also select the enemy the root node again and instantiate a stats UI to this scene as well and we can again move this down so it's not that close to the Sprite okay that looks fine go back to select mode with q and don't forget that we need to add the enemies to their own group as well so select the enemy rot node again go to node groups and and let's call these groups enemies press enter to add them you can see that we successfully added it to the enemies group save the scene and that's all the setup we need from the scene 3 also we can hide the Arrow by default if it looks fine we'll only show it when we need to and attach a new script to the enemy enemy.
GD is perfect delete the autogenerated code so what do we need to do with the Enemy first of all we Define a custom class name and then we'll have a constant for offsetting the arrow why do this we already position the arrow in the editor right well yes but we can assume that not all enemies will be the same with right so for example if you imagine we'll have a big boss later on or something we want the arrow to be five pixels to the right from the right edge of the of the image or the Sprite of the enemy so to do this we need to position the arrow from code dynamically and to do this we can define an offset which is how many pixels away will be the arrow from the right edge of the enemy then we have an export variable for the stats similarly to the player script we'll use a Setter function to set this and we have three on ready variables one for the Sprite one for the arrow and one for the stats UI in the setter function we do virtually the same thing we did with the with the player character we create a new instance from the stats resource assign it then if it's not connected to our update stats function we connect the stats change signal to it then update the enemy the update stats function is just a delegate to the stats youi to do this updating for us and then in the update enemy function really similar to The Players script we check if the stats is a valid resource we can handle or not if it's not then we return then we need to wait for the enemy node to be inside and ready in the scene 3 and if it is we we do three things first of all we set the texture to the one which is defined in the resource then we calculate the error position which is the right Vector multiplied by half of the size of the Sprite and we offset it with the arrow offset uh constant and then last but not least we update the stats and we also have a take damage function which is identical to the players if the enemy is already dead we return if it's not then we take the damage by definition from the stats resource and if after taking damage we died then we can Q free or delete this enemy from the scene and that's all there is to it to the enemy script let's save this and if we go back to the battle scene go back to 2D view you can see that first of all the enemy got repositioned to the top left corner because we reset its position but that's not a problem if we select the enemy node go back to the inspector and press W to enter move mode we can move move it back to any position we like I just drop him here and we also need to assign stats to this enemy now so if you remember we created a fake a temporary enemy so let me collapse some of these folders so if we're in the route we go to enemies and we'll have this test enemy resource so if I drag and drop this we can test how the battle scene looks like let's play we should see yeah so we see the crab enemy has 10 health and we have 35 cool so now how do we test if the stats UI gets updated or not a pretty easy way to test this is to go to the enemies and the player script let's start with the Enemy first and just do something like this in the ready callback we create a timer for 2 seconds and after the timer expires let's say we take six damage and add eight block to this enemy and save this and we can do something similar in the player script so if we press control. o search for player.