Reification of the Coin
"Reification" is the process of turning something abstract into something concrete and material. In short, we're turning abstract ideas into objects.
This also happens to be the most natural and clean way of implementing your game objects.
First let's see at the "naive" pseud-code implementation where we add code to the player himself. Our player will wait for a coin to enter his hitbox and some code will run:
#Player.gd
func _on_collectible_area_entered(area):
if area is Coin:
coins +1
play ding_sound
remove coin
While this is servicable code that would work fine for a quick prototype, it has some problems:
- The player script now contains things specific to the coin. So if you want to change something about the coin behavior, you have to do to Player.gd!
- If we want to add more pickable objects we need to add additional `if` statements to the player code.
This way, we didn't really turn the coin idea into a game object. Instead of just introduced a "ghost" of a coin that the player reacts to. A much better way would be to store this logic inside the coin and have the player interact with it.
With any Object Oriented Language you'll have the ability to create classes and interfaces. These can be a very powerful way of creating nicely organized and reusable code. With GDScript we have one more option that results in a bit less code, the .has_method(name) call. This allow us to take a look at any node and check if it implements a method by name! Let's see how the code above could be organized inside the Coin node.
#Player.gd
func _on_collectible_area_entered(area):
if area.get_parent().has_method("pickup"):
area.get_parent().pickup()
and for the coin script:
#Coin.gd
func pickup():
Globals.coins += 1
$SE_Pickup.play()
await($SE_Pickup.finished)
queue_free()
Let's break down the code a little bit:
Inside the player code, we still hook up an area signal to a function. But instead of checking if the area we collided with is specifically a coin, we instead check if whatever it is, it implements a method named "pickup". And if it does, we call it
Inside the coin code, we create the "pickup" method and put all our logic inside of it as we did before. We add a coin to the counter, we play a sound, we wait for the sound to finish playing and then we remove the coin itself.
This elegantly moves all the code and nodes (like the Sound effect player node) to the coin itself, so the player doesn't need to be concerned with any of the coin specific stuff. Only the coin itself knows how it will behave when it is interacted with.
This approach neatly solves the problems with the naive approach. The coin code is only contain inside our coin scene and the coin.gd script. So when we want to fix or change something about the coin, we're only interested in the coin scene. Secondly, if we want to add more pickable objects now, we can do it without changing anything in the player script. We can just create a new scene with a new object, create a new script for it and include the pickup method in the new object and it will *just work*. This is what we call "decoupling" in software engineering, since the Player and the Coin are no longer tighly connected (coupled) together. They exist completely independently of each other, yet they can interact.
In the next post we'll look at how we can improve the usage of our coin for our future level designers (possibly ourselves!) and we'll add a new "signpost" game object that we'll use to present tutorials to the player!