6.5 KiB
This week, we will be diving into code for the purpose of crafting unique game mechanics.
Often people are scared about learning to code because it can all seem very complex when you first get started. A lot of that complexity comes from developer jargon like “state machines” “data persistence”, “dynamic type-checking” and so forth. These concepts can seem insurmountable to begin with.
Today there is also a general perception that coding will all be accomplished by AI, and is barely worth learning.
The truth is that coding is neither as hard or as easy as either of these two extremes would suggest.
If you can read and follow a recipe, you can learn to code at a proficient level. And many examples and resources exist to help you along the way.
In this lesson we will be learning by doing - creating fun game mechanics through the use of code. To be able to do this, we need to learn a few fundamentals.
Variables, Data Types, and Collections
The most basic unit in code is the variable - a little bit of memory assigned to recall some kind of value. Think of how many things get stored in memory in a game:
- Player position (a transform)
- System volume
- Health points
I’m sure you can think of many more and they all have something in common - they will very likely be updated repeatedly during gameplay.
Behind the scenes your operating system is constantly working to optimize performance, including picking up things stored in memory and moving them around to new locations.
A “variable” is like an address that always points to the current location of that value, wherever it may be moved in memory.
To be even more efficient, the operating system will try to determine the best place to store various types of values, called “data types”. Godot respects a few data types needed for game development, including:
- Integers - whole numbers with no fractions
- Floats - numbers with decimals (i.e. fractions)
- Strings - alpha-numeric characters joined together
- Booleans - true or false values (binary)
- Vector2 - two-dimensional numbers, good for positions and velocity
- Vector3 - three-dimensional version of positions and velocity
Why do the data types matter? Two reasons - performance and consistency. On the performance side applications generally run faster and more efficiently if they can anticipate what type of data they are dealing with.
And as developers, we are less likely to create problems (and more likely to catch them) if we also can consistently anticipate the type of data we are working with.
While it is occasionally convenient, it is usually bad practice to change the type of a data by reassigning it a value with another data type. For example, an Integer 10 can also be expressed as a string with quotes “10”, but changing the data type to a string can ruin the ability to do math operations on it.
I.e.
10 + 1 = 11
“10” + “1” = “101”
See how very different results are produced by the same operation, just based on the data type?
Variable assignment and type
In the GDScript language variables are optionally dynamically typed. This means that we can assign data of any type to a variable, and that variable will imply the type from it. Give a variable a string, and it will be typed to a string. Give it a number, and it will get assigned an integer or a float type.
This is very convenient and works well quite often - but not always! Sometimes the type will be assigned incorrectly.
And even when the type is assigned correctly, we still lose the efficiency bonus of assigning the type ourselves because the software anticipates that the type could be reassigned at any time, just by applying a value with a different data type (and that is true).
Therefore a good practice is to assign the type along with the value, like so:
Var myBonnie: String = “Over the ocean”
Implied Type
Godot has an interesting compromise available between loose and strict typing, which again is a kind of implied type. The difference this time is that the type can not be reassigned. So in this scenario we get some of the ease of dynamic types, but we also get the performance bonus of strict typing. It looks like this:
Var myBonnie := “over the ocean”
In this case the variable will be cast to a string and given the string value. That variable can be assigned a new value, but that value must forever-more be a string.
Access Modifiers
For variables, Godot supports a couple of access modifiers. These are rules that state what should happen when any code tries to change the value of the variable.
The “const” keyword prevents a value from being changed. We see a couple of examples of this in the default character movement template:
const SPEED = 200.0
const JUMP_VELOCITY = -300.0
These values are not allowed to change, and an error will be thrown if anything attempts to do so.
The “var” keyword does allow a variable’s value to be changed.
var faceLeft = false
And Godot also supports “getters” and “setters”. These are simple functions that get called implicitly whenever a variable is accessed to either retrieve or assign their value. These can be used to run additional code that formats the value (or any other purpose).
var cents : int = 0
var dollars : float :
get:
return cents / 100.0
set(value):
cents = int(value * 100)
This simple set of formatters convert the “dollars” value to store them in memory as cents.
Decorators
In Godot decorators can be used to control when a variable will be assigned. This is a convenience outside of placing the variable assignment in a function.
A very typical example is a variable that should be assigned during the “ready” state of the game object, which happens when the object and its children have fully loaded. Trying to assign a variable to one of those children before that point will result in an error.
The “onready” decorator takes care of this by making sure the variable is assigned during the ready state.
@onready var animated_sprite = $AnimatedSprite2D
Note that the $ in front of the value assigned is a shortcut designation for the current scope. It is called an operator, and is used to retrieve children of the parent node.
Functions and Returns
In code, lines predictably execute one after the other as instructions. Those instructions are usually grouped together into discrete jobs. Those jobs are called “functions”.
You can think of functions like little factories that can receive some kind of input, and produce some kind of output.