more collisions and player pickable items, part #5 of godot-roguelike series
Wed 26 October 2016 2D Godot roguelike Unity3D , 0 comments
Wed 26 October 2016

disclaimer

Although I have quite a bit of programming experience in high level languages, I may not be qualified to teach you anything about game development as I am learning myself and I probably do not have a sense of how code should be written just yet. So please keep in mind that I am very new at this as well and don’t take it as the definite way to do it. It is only my way

updates

2016-10-26 14:26: forgot that setters & getters are both optional, I corrected this and added some more details about in the setters&getters section. Also expanded on another gotcha in the AnimationPlayer details section

overview

with Godot v2.1.stable.officiali-godot
project files

Before you continue make sure you have the project files from the last part as you will need them here if you want to follow along!

overview Fig. 01. Pickable Food and Soda items that replenish Player energy. Keep on reading to find out how :)

We are going to do some exciting stuff this time! We’re going to make the items (Food & Soda) pickable with a bit of animation for some nice feedback and add a food variable to our Player object that gets replenished every time the player picks up an item and that gets diminished when the player moves a tile. It doesn’t sound like much but here’s what we’ll be covering to make all of this work:

  • making unique resources
  • a simple way of passing values when loading/reloading scenes
  • the AnimationPlayer node and how powerful it can be!
  • efficiently using scripts to manage game (yet again :D) objects
  • built-in signals
  • proper set-up for collision detection
  • setters & getters and why they are useful
  • a few gotchas that can ruin your day if you don’t know about them

Very exciting stuff indeed so let’s get started!

keeping the Player energized

So the idea here is that the Player starts with some energy value at the beginning of the game and consumes some each time he makes a move. He’ll also loose some when he’ll be attacked by the enemy but we’ll get to that later down the road. The Player will also be able to pick up items which will replenish some of this energy.

setters & getters

Godot has this pretty cool feature (which is most likely borrowed from Python) for defining setters & getters that work directly on the variables. What I mean by that is this: let’s say we have a variable called self.x and we want some code to get triggered when we say self.x = 5. Yes, we could write a function, but we can actually tell Godot to use a function we define whenever self.x = is used, which is pretty cool. It’s really only syntactic sugar, but it’s nice to have! Getters work the same, they get triggered whenever we access self.x from somewhere. Getters are required to return a value. So how do we implement this you say? The syntax is like so:

[export] var x [= 10] setget [set_x][, get_x]

Then we’ll need to define the functions:

func set_x(val):
    # do something with `val` and perhaps some other code
    x = val

func get_x():
    # clean up code perhaps
    return x

The values within the [] brackets are optional. So it isn’t mandatory to set the variable to some default value. Also the setter/getter are optional so they can be omitted, but one of them has to be defined at all times… otherwise we wouldn’t use the setget keyword in the first place of course. Let’s go over some examples to drive the point:

Note that if you want to use export without specifying a default value (like = 10 in the example) then you have to specify the type of the variable (like so: export(int)) so that Godot knows how to show it in the Inspector panel. Grab more info in the docs.

# set x to 5 then define only the setter, the getter is omitted and the default
# operation happens when retrieving the value
var x = 5 setget set_x

# define the variable `x` and set only the getter. When setting some value to
# the variable the usual default behavior takes place
# NOTE the comma which is mandatory - that's how Godot knows it's a getter and not a setter
var x setget ,get_x
using setget with export

This isn’t really a bug, but when playing with this I discovered that when using export, together with setget, to be able to modify variables in the Inspector panel, the setter gets triggered when the object enters the scene tree. This is something that I found unintuitive and that it can cause problems with some implementations, like I wanted to do in this tutorial. So for the Player object I’m unable to use the export for what I intended to do so I opened up a discussion on the github tracker. Again, I encourage everyone to do so when they find bugs or have suggestions!

One last thing. The setter function can be anything really, but if we want to assign a value to self.x inside of it, we’re not allowed to use self (so only x = 5 for example is allowed), that’s just how Godot implements this function, otherwise it will trigger an infinite loop and we don’t want that! So be careful with it.

We’ll have to modify a bit the Player script for this. Add a var energy = 100 setget __set_energy to start off with, at the beginning where all of the other variables are defined. I expect you to know where to place this line of code already! At the end of the script add this function:

1
2
3
4
func __set_energy(val):
    print(val)  # just for testing the energy when running the game
    Globals.set('player_energy', val)
    energy = val

I’m sure you can pretty much guess what the above function does. We’ll go over it all, but first let’s finish the modifications to the script. We need to change the _ready() function like so:

1
2
3
4
func _ready():
    if Globals.has('player_energy'):
        self.energy = Globals.get('player_energy')
    self.__state.enter(self)

Let’s go over it as it introduces some new concepts. Now, anyone who’s familiar with setters and getters I’m sure already gets the idea. The idea is that whenever the energy value gets assigned some value, the __set_energy() function gets triggered. Yes, but why use it? Well, the Player needs to keep track of the energy value over scene changes when we’ll implement level changing, but whenever we transition to a new level this value will reset (to the default 100 in this case)… So how can we then keep track of it? That’s where Globals comes in. This is an object which is made available to us by Godot, is accessible from anywhere and it doesn’t get reset on loading/reloading scenes, which is exactly what we need in this case. As you can see from the above implementation it has some basic methods for getting, setting and checking of custom properties.

the Globals object

As you saw the Globals object is very simple, it has a get(), set() and has() functions to help us propagate and check for values even if we load/reload scenes. There’s one more interesting thing about it though. Godot automatically loads the values from the engine.cfg file inside this Globals object. And engine.cfg can store user created variables as well, it’s just a text file which can be opened in any text editor and values can be assigned to keys which end up in the Globals object. I’ll leave it to the documentation: “Contains global variables accessible from everywhere. Use the normal Object API, such as Globals.get(variable), Globals.set(variable,value) or Globals.has(variable) to access them. Variables stored in engine.cfg are also loaded into globals, making this object very useful for reading custom game configuration options”. Nice isn’t it?

fixing collisions and using dynamic dimensions

Next we need to take care of the TileSet scene. At this moment, the items which are supposed to be pickable behave like obstacles, not even letting the Player pass over them. We need to fix that. We also need to take care of the dimensions and positions we set-up in the previous tutorial because ideally we want these to be calculated based on the dimensions of the tile set we use.

generic Area2D tile set based dimensions

As you know, we’re not actually using the TileMap node in this tutorial and I said at the very beginning that this will work even for different sized tiles, not only 32x32 px. To make this happen we’ll have to modify the Shape under the Area2D nodes dynamically, based on the tile size stored in the Board node.

Let’s get to work. Create a new script in res://Scripts/Area2D.gd (a pretty generic name as it will be used by all Area2D out there) by right clicking on the TileSet/Walls/1/Area2D node and selecting Add Script. This is what we’ll want in it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
extends Area2D

export var scale = 1.0
onready var __board = self.get_node('/root/Game/Board')

func _ready():
    self.__set_shape()

func __set_shape():
    var dimension = self.__board.tile_collection.tile_size * 0.5
    self.set_pos(dimension)
    self.get_node('CollisionShape2D') \
        .get_shape() \
        .set_extents(dimension * scale)

It’s pretty self explanatory, the only mystery really is the last part: self.get_node('CollisionShape2D').get_shape().set_extents(dimension * scale). As you remember, the CollisionShape2D node uses a Shape2D resource object which we created and set-up in the previous tutorial. That’s what the get_shape() part does, it accesses this Shape2D so we can specify its extents. Remember that the extents need to be half of the size we want for the rectangular shape. The scale parameter will be used for the Food and Soda nodes to scale the size of the shape even further as you shall see in a bit. This prevents unwanted collision detections due to rounding errors.

Now that we have constructed this generic script, it’s time to put it to work. Select all of the Area2D nodes from the TileSet scene, except for the ones under Food and Soda, then in the Inspector panel go down at the bottom and load the res://Scripts/Area2D.gd in the Script property (that’s basically what happens when you right click on a node and select Add Script: this property gets set). There’s one more Area2D where we need to set the Script on: Players Area2D. So open up the Player scene, select the Area2D and do the same thing, set the Script property in the Inspector so that it points to res://Scripts/Area2D.gd.

Now going back to the TileSet scene, before moving on, make sure that all of these Area2D nodes are on physics layer 2 (under Collision in the Inspector). It’s important for the collision detection. As you remember, in the last tutorial we set the RayCast2D nodes on the Player object to scan for physics layers 2 and 3.

Food, Soda and Exit tiles feedback animation and set up

This part is going to be interesting because we’ll get to use the AnimationPlayer for sending some feedback to the user when he picks up the items! Exciting and you’ll get a taste of how powerful the AnimationPlayer can be!

Select the Food node and insert a new AnimationPlayer node (CTRL + A). You’ll see a panel pop up at the bottom of the screen, that’s the Animation panel and it can be closed and opened by clicking on the Animation button near the Debugger button at the very bottom of these panels. Let’s create an animation for the Food item now. Press the Create new animation in player button and name it pick-feedback (the name isn’t that important because we’re going to have only one animation, but it’s still a good idea to name them properly).

To actually see what we’re animating, select the Food node and in the Inspector look for the Z property and set it to 1. At this point you should also see a key icon that appeared in the Inspector panel near each property. This is due to the AnimationPlayer node and this is a button for introducing key frames in the AnimationPlayer for those properties. This Z property is the order in which Godot draws the nodes (Sprites in this case) on the screen. Higher values get drawn first.

drawing order

As mentioned in the previous paragraph nodes have these Z property for specifying the draw order. To start off with, all nodes have a Z value of 0. This value can be both negative and positive, it’s just an integer that orders the nodes for drawing, with higher values being drawn on top. If this is set to the same value for all nodes, Godot will draw in the given order from the scene tree, meaning that nodes towards the bottom of the scene tree are drawn last, and on top of the others. Just something to keep in mind.

Let’s do a simple scale-fade animation. Make sure the Food node is selected in the Scene panel and press the key icon near the Scale property from the Inspector and accept the creation of a new track. Do the same for the Opacity property. Next, in the Animation panel move the current frame to time 0.5 by clicking and dragging with the mouse button on the timeline. You’ll see the red line follow along. Think of this as the cursor in a text editor. This is where key frames will be inserted when pressing the key buttons from the Inspector panel. Now set the Scale property to (2, 2) in the Inspector and press the key button near it. You’ll see a new key frame appeared in the timeline in the Animation panel, on the first track. Now set Opacity to 0 and insert a frame for it, like you did for Scale.

Before moving forward to the most awesome part, make sure that Length (at the bottom of the Animation panel) is set to 0.5. Each animation has a specific length defined through this property. We’ll just do a 0.5 second animation.

Now comes the good part. The AnimationPlayer can be used to call functions on specific nodes at specific times. You just need to make a new track for that and insert key frames when you want to call these functions. This is a very powerful features. When the items get picked up by the Player we want them to be removed from the scene tree as well. We’ll do that by calling on the last frame of the animation a function that removes the node. Press the + button in the bottom right side of the Animation panel, Add new tracks and select Add Call Func Track from the menu. A panel with the scene tree will pop up. Sere we select the node on which we want to call the method. In our case we want to call the queue_free() method on the Food node, so double-click on Food in this panel. You’ll see a new track appear in the Animation panel, with a green disk icon this time, instead of the blue one. At the very right of this track, press the + green button. This will insert a key frame at the position of the cursor (red vertical line). Make sure that this cursor is placed on the 0.5 second marker, the last frame of the animation. Now, select the new green key frame by clicking on it and turn on the editing panel by clicking the right bottom most button in this Animation panel (looks like a pencil). A new panel will appear in the right side with two tabs, Key and Transition, we’re just interested in this Key tab. Under Name write queue_free (without parentheses). This is the name of the method we want to call on the Food node when this key frame is reached. It’s a built-in function which instructs Godot to dispose of this node when it gets the chance.

There were a lot of word about the AnimationPlayer and panel, and because these things are better explained by showing, I’ve included a gif with all of these steps in Fig. 02:

Animating the Food object Fig. 02. Process for animating the Food item. It takes a lot to describe it in workds, but as you can see in this gif, things are pretty simple really. You’ll get used to it in no time

some details about the AnimationPlayer node

The animation player node works by creating tracks for each property. Tracks can be either created automatically (if you accept) when keying the first time a property from the Inspector panel using the key icon near these properties. Or they can be created manually, like we did for the “call function” track above. In Godot, properties are reached in code (in specific functions) through the same principle as the node path.

Let’s go over an example. Select the Food node and look at the Scale parameter in the Inspector panel. You’ll notice this parameter is under a Transform header under Node2D (it means that the Sprite node inherits from Node2D). If you hover the mouse over this Scale property for a while, a pop-up will appear and the first thing it will say is Property: transform/scale. This transform/scale is the path to this property in Godot terminology and this is a way to find this path, by hovering the mouse over it and waiting for the tool tip. I don’t think there’s any other way for now.

In the Animation panel, you’ll notice that the first track is called .:transform/scale. This isn’t just some arbitrary name. In English this means: this track is for setting the transform/scale property of the node from path .. If you’re used to moving around in Linux in a terminal this will look very familiar. This is a relative path meaning the current node, but… relative to what? Select the AnimationPlayer node in the Scene panel and you’ll notice that it has a property called Root which is set to .. by default. This .. means up one level in the scene tree hierarchy, relative to the current node (AnimationPlayer in this case), just like in Linux when moving around in the folder tree structure. So basically this .. means the parent node, which is Food in this case, since AnimationPlayer is a child of Food. I hope it isn’t that complicated, I guarantee that you’ll get used to this in no time!

Going back to the Animation panel, the track “names” that appear in the left side (refer to Fig. 02) are not really names, but relative paths to nodes and node properties. Better keep this in mind if you want to create tracks manually as you’ll have to introduce these paths yourself!

gotcha

But wait, there’s more! If you’ll run the game with the timeline cursor (the red vertical line) at a location other than the first frame, the properties affected by the animation will be set to the values at that specific frame where the timeline cursor is placed! 99% of the time this isn’t the desired behavior so be careful about it, always place the timeline cursor at the 0 marker after finishing an animation.

But wait, there’s even more! The above only works if we define a key frame at position 0 in the timeline! That is, if we store the default value of our properties at this key frame. If for some reason you have an animation that has some tracks with key frames that don’t start off with default values there’s no way to go back to these default value! A workaround to this is to specifically create a default animation with only one key frame at time 0 and record all default values for the properties you modify in other animations. Then whenever you need to start the game you can go back to this default animation and reset the values! So be mindful of these limitations.

Now that we finished the animation for Food I know what you’re thinking: “ah man… this took so much time and I have to do it all over again for the Soda!”. No! Remember those tracks that refer to properties in nodes at relative paths (relative to the Root property from the Inspector of the AnimationPlayer)? Yeah! It’s perfect, we just need to duplicate the AnimationPlayer from the Food node and place it under the Soda node and voila (make sure to rename it to AnimationPlayer)! That’s all there is to it! Perfect, no more work! Now before finishing this there are just a few more things to do:

  1. set the Z property of Soda to 1 so it gets drawn on top of the rest of the tiles
  2. select the Area2D nodes under Food, Soda and Exit and set the Layers property to 4th layer only. We’ll revisit the collision detections and layers in greater detail later when we’re going to work on the enemy AI, but the main idea is that we don’t want the AI to interact with the Items or the Exit tiles and we don’t want the Player RayCast2D nodes to interact with them either, because those are just for detecting obstacles and these objects shouldn’t be treated as obstacles. Makes sense, right? But we will want the Player Area2D node to interact with them. So to recap, RayCast2D on Player won’t interact with Food, Soda or Exit, but Area2D on Player will because we want to detect the Items and also the Exit tile that triggers the next level
  3. since we just set the Food, Soda and Exit to be on physics layer 4 earlier, in the Player scene, select the Area2D node and make sure that in the Mask property only the 4th layer is selected. This will basically enable collision detections between the Player and the other tree tiles

Food, Soda and Exit tiles collision shape dimensions

We next want a separate script just for the Food and Soda Area2D nodes. We can’t use res://Scripts/Area2D.gd here directly because we need a bit more functionality, remember that Food and Soda are supposed to interact with the Player object. Add a script to TileSet/Items/Food/Area2D node and save it as res://Scripts/Item.gd. Here’s what it will contain:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
extends 'Area2D.gd'


export var energy_restored = 20
onready var animation_player = self.get_node('../AnimationPlayer')


func _ready():
    self.connect('area_enter', self, '__on_area_enter')


func __on_area_enter(player_area):
    self.animation_player.play('pick-feedback')
    player_area.get_node('..').energy += self.energy_restored

So, first thing to note is that we’re extending Area2D.gd not Area2D! In other words, we’re reusing the generic script by extending it a bit to accommodate the items’ special needs. First, the items will have the power to replenish the Players energy value (that’s what energy_restored is for). Next, we get a reference for the AnimationPlayer as we’ll need it to start the feedback animation.

In the _ready() function we are connecting the built-in area_enter signal to the __on_area_enter() function. This will get triggered whenever the Player moves onto a tile that has items in it as it is called every time a collision is detected between two Area2D objects.

signals from UI

I’m not going to go into details about it, but the way signals work is: they are defined per node. And there are two types, built-in and custom made that we need to emit manually, but I’ll leave it to the docs to explain this part.

Then these signals can be connected to methods so when they’re emitted the specific method gets called. But these methods are part of objects, so connecting them goes like this: self.connect('signal_that_is_part_of_this_object', node_where_method_we_want_to_call_resides, 'method_name'). I hope this is clear enough. Go and compare this with the connection from the Item.gd script in the _ready() function and try to figure out the parts. And yes, if you don’t understand something, consult the documents, as a matter of fact… always consult the documents!

But signals can also be connected directly through the interface. I personally prefer to connect them through code in 90% of the cases at least. It’s more powerful from code since you can access nodes that are loaded dynamically through code, something which you can’t do from the UI. Refer to Fig. 03:

signals UI Fig. 03. Connecting signals through the interface isn’t that difficult, the highlighter parts are the things you’ll use 90% of the time. Something to note is that unfortunate red color on the current node was chosen because it feels like it’s something forbidden. It isn’t! So don’t be fooled, you can select the red node just as easily. That’s just self really

The __on_area_enter() function is pretty self explanatory, its job is simple: just start the pick-feedback animation and restore some of the energy on the Player object. The function receives a parameter from Godot (in this case named player_area because that’s the only possibility) which is the other Area2D object that collided with this one. In our case it is the Player/Area2D node. We then use this Area2D to get the Player node (that’s what get_node('..') does, it gets the parent of Area2D which in this case is the Player node) and add self.energy_restored to Player.energy. Remember that this will trigger the __set_energy() function from the Player node!

Now that we have it working for the Food node, select the Area2D under Soda and set the Script property in the Insector to point to the same res://Scripts/Item.gd script as we’ll reuse it. Finally, select the Area2D nodes under Food, Soda and Exit in the Scene panel and set our custom Scale parameter in the Inspector to 0.5. We’re doing this because if the Player collision shape has the size of (32, 32) and the tiles that it interacts with also have Area2D nodes with collision shapes of (32, 32), Godot will detect collisions on adjacent tiles due to rounding errors, and we definitely don’t want that! So we’re shrinking down the collision shape on the tiles that will interact with the Area2D from the Player object to fix this.

gotcha

This is important so pay attention! If you turn on the Visible Collision Shapes from the debug options up top and run the game, you’ll notice that the blue squares (representing the collision shapes) from the Food, Soda and Exit tiles still have the same dimensions as the other tiles although we changed the Scale parameter for them. And we didn’t have any problem with the script so it must be working. So what gives?! Why aren’t the collision shapes shrunken? Well, remember that we duplicated the Area2D in the previous part to set up collisions on tiles. And these Area2D have a child node, CollisionShape2D… and this CollisionShape2D has a property called Shape and we created a RectangleShape2D in there! Thing is, this Shape we created is being reused by all of these CollisionShape2D nodes! So whenever we modify the dimensions of this Shape we are affecting all of the nodes! This is obviously something that we don’t want for the Food, Soda and Exit tiles!

The fix is really simple though, but it has to be done in sequence. Select TileSet/Food/Area2D/CollisionShape2D and from the drop down menu for the Shape property in the Inspector choose Make Unique. Do this for the other two under Soda and Exit.

Now if you run the game with Visible Collision Shapes turned on you should see the shrunken collision shapes for the Items and Exit tiles.

This only applies if you duplicated the Area2D node when setting up the TileSet in the previous tutorial which you most likely did!

Moving cost

One tiny thing we need to change in Moving.gd. Every time the Player makes a move to a new tile he looses some energy. After all, it’s a costly action requiring lots of energy! We need to make a very very simple modification in the Moving.gd script. Open it up and add

export var energy_cost = 5

at the right place in the script. You should already know by now where this goes so I’m not even going to bother telling you! And the other place we need to add a line of code is just after state_name = 'IdlePlayer' from the update() method. Find this place and add:

entity.energy -= self.energy_cost

That’s all, it’s very simple. Now every time the Player makes a move, it will cost him some energy. And because we used export in the variable declaration we can now play witht his value from the Inspector when selecting the Moving node in the Scene panel in order to tweak it later for balancing out the game.

Note that there is nothing preventing the energy to go below 0 and continue doing so without any consequence. We’ll have to deal with this later.

Player code design

I have a confession to make… now that we used these cool setters to update the Glogals variable to be sent to the other levels I have to say… this is completely useless. Because all we need is really to set the value only on level completion, not every time the Player moves. Then why did I put you through this hell?!? Well, because I wanted to introduce you to setters & getters and I thought this is a good opportunity for it. Nothing more really. And this also gave me the perfect opportunity to give you a nice little assignment:

assignment 02 - saving Player.energy without the setter

This is going to be a very simple assignment or so I hope because by this stage you should have been exposed to a lot of Godot elements and should be comfortable with scripts, certain decision making steps.

Given what I said above, in this section, the task this time is to remove the setter function __set_energy() and instead set the player_energy key on Globals only when the Player reaches the Exit tile. Furthermore, when the Player collides with the Exit tile, the scene should be reloaded.

Hints:

  1. the Exit Area2D node will need a new script, instead of the generic Area2D.gd. You’ll need to pretty much do the same as we did for the Food, create a new script and extend Area2D.gd, then check collision with Players Area2D. The only difference is that the Exit tile won’t replenish any energy and you’ll have to reload the scene. This can be done by calling self.get_tree().reload_current_scene()
  2. you should verify that the energy value is indeed set correctly when the scene gets reloded. That is, if the Player reaches the Exit tile with an energy value of 15 let’s say, then, after the reload of the scene is triggered, the Player should start with an energy of15 instead of the default 100. You should check this with a print statement inside of the _ready() method from Player

closing remarks

This part was surprisingly a lot longer than I expected. Well, I suppose this is true about the other parts as well :). But I hope you got something out of it, I tried to go over everything I could think of which is important to know about and some of the problems that you might encounter, those type of problems that would leave you wondering what’s wrong because they don’t give any hints. The best thing you could do if you find something like this is of course to go to the github issues tracker and notify the developers about it, maybe propose a solution.

As usual, let me know if you find any typos or inconsistencies or grave mistakes so I can correct them. Thanks!

project files

Get the zipped finished project for this part with annotated scripts and see if you could follow along! Or if just want to consult them.