2023-01-12 20:49:14 +01:00
|
|
|
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
GDScript: An introduction to dynamic languages
|
|
|
|
==============================================
|
|
|
|
|
|
|
|
About
|
|
|
|
-----
|
|
|
|
|
|
|
|
This tutorial aims to be a quick reference for how to use GDScript more
|
|
|
|
efficiently. It focuses on common cases specific to the language, but
|
|
|
|
also covers a lot of information on dynamically typed languages.
|
|
|
|
|
|
|
|
It's meant to be especially useful for programmers with little or no previous
|
|
|
|
experience with dynamically typed languages.
|
|
|
|
|
|
|
|
Dynamic nature
|
|
|
|
--------------
|
|
|
|
|
|
|
|
Pros & cons of dynamic typing
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
GDScript is a Dynamically Typed language. As such, its main advantages
|
|
|
|
are that:
|
|
|
|
|
|
|
|
- The language is simple and easy to learn.
|
|
|
|
- Most code can be written and changed quickly and without hassle.
|
|
|
|
- Less code written means less errors & mistakes to fix.
|
|
|
|
- Easier to read the code (less clutter).
|
|
|
|
- No compilation is required to test.
|
|
|
|
- Runtime is tiny.
|
|
|
|
- Duck-typing and polymorphism by nature.
|
|
|
|
|
|
|
|
While the main disadvantages are:
|
|
|
|
|
|
|
|
- Less performance than statically typed languages.
|
|
|
|
- More difficult to refactor (symbols can't be traced)
|
|
|
|
- Some errors that would typically be detected at compile time in
|
|
|
|
statically typed languages only appear while running the code
|
|
|
|
(because expression parsing is more strict).
|
|
|
|
- Less flexibility for code-completion (some variable types are only
|
|
|
|
known at run-time).
|
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
This, translated to reality, means that Pandemonium+GDScript are a combination
|
2022-03-18 17:46:08 +01:00
|
|
|
designed to create games quickly and efficiently. For games that are very
|
|
|
|
computationally intensive and can't benefit from the engine built-in
|
|
|
|
tools (such as the Vector types, Physics Engine, Math library, etc), the
|
|
|
|
possibility of using C++ is present too. This allows you to still create most of the
|
|
|
|
game in GDScript and add small bits of C++ in the areas that need
|
|
|
|
a performance boost.
|
|
|
|
|
|
|
|
Variables & assignment
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
All variables in a dynamically typed language are "variant"-like. This
|
|
|
|
means that their type is not fixed, and is only modified through
|
|
|
|
assignment. Example:
|
|
|
|
|
|
|
|
Static:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
int a; // Value uninitialized.
|
|
|
|
a = 5; // This is valid.
|
|
|
|
a = "Hi!"; // This is invalid.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Dynamic:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var a # 'null' by default.
|
|
|
|
a = 5 # Valid, 'a' becomes an integer.
|
|
|
|
a = "Hi!" # Valid, 'a' changed to a string.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
As function arguments:
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Functions are of dynamic nature too, which means they can be called with
|
|
|
|
different arguments, for example:
|
|
|
|
|
|
|
|
Static:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
void print_value(int value) {
|
|
|
|
|
|
|
|
printf("value is %i\n", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
[..]
|
|
|
|
|
|
|
|
print_value(55); // Valid.
|
|
|
|
print_value("Hello"); // Invalid.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Dynamic:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
func print_value(value):
|
|
|
|
print(value)
|
|
|
|
|
|
|
|
[..]
|
|
|
|
|
|
|
|
print_value(55) # Valid.
|
|
|
|
print_value("Hello") # Valid.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Pointers & referencing:
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
In static languages, such as C or C++ (and to some extent Java and C#),
|
|
|
|
there is a distinction between a variable and a pointer/reference to a
|
|
|
|
variable. The latter allows the object to be modified by other functions
|
|
|
|
by passing a reference to the original one.
|
|
|
|
|
|
|
|
In C# or Java, everything not a built-in type (int, float, sometimes
|
|
|
|
String) is always a pointer or a reference. References are also
|
|
|
|
garbage-collected automatically, which means they are erased when no
|
|
|
|
longer used. Dynamically typed languages tend to use this memory model,
|
|
|
|
too. Some Examples:
|
|
|
|
|
|
|
|
- C++:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
void use_class(SomeClass *instance) {
|
|
|
|
|
|
|
|
instance->use();
|
|
|
|
}
|
|
|
|
|
|
|
|
void do_something() {
|
|
|
|
|
|
|
|
SomeClass *instance = new SomeClass; // Created as pointer.
|
|
|
|
use_class(instance); // Passed as pointer.
|
|
|
|
delete instance; // Otherwise it will leak memory.
|
|
|
|
}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
- Java:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
@Override
|
|
|
|
public final void use_class(SomeClass instance) {
|
|
|
|
|
|
|
|
instance.use();
|
|
|
|
}
|
|
|
|
|
|
|
|
public final void do_something() {
|
|
|
|
|
|
|
|
SomeClass instance = new SomeClass(); // Created as reference.
|
|
|
|
use_class(instance); // Passed as reference.
|
|
|
|
// Garbage collector will get rid of it when not in
|
|
|
|
// use and freeze your game randomly for a second.
|
|
|
|
}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
- GDScript:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
func use_class(instance): # Does not care about class type
|
|
|
|
instance.use() # Will work with any class that has a ".use()" method.
|
|
|
|
|
|
|
|
func do_something():
|
|
|
|
var instance = SomeClass.new() # Created as reference.
|
|
|
|
use_class(instance) # Passed as reference.
|
|
|
|
# Will be unreferenced and deleted.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2022-09-10 12:15:58 +02:00
|
|
|
In GDScript, only base types (int, float, String and PoolArray types)
|
2022-03-18 17:46:08 +01:00
|
|
|
are passed by value to functions (value is copied). Everything else
|
2022-09-10 12:15:58 +02:00
|
|
|
(instances, Arrays, Dictionaries, etc) is passed as reference. Classes
|
2023-01-12 19:32:38 +01:00
|
|
|
that inherit `Reference` (the default if nothing is specified)
|
2022-03-18 17:46:08 +01:00
|
|
|
will be freed when not used, but manual memory management is allowed too
|
2023-01-12 19:32:38 +01:00
|
|
|
if inheriting manually from `Object`.
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:55:57 +01:00
|
|
|
Note:
|
|
|
|
|
2022-09-10 12:15:58 +02:00
|
|
|
|
|
|
|
A value is **passed by value** when it is copied every time it's specified
|
|
|
|
as a function parameter. One consequence of this is that the function cannot
|
2023-01-12 22:32:46 +01:00
|
|
|
modify the parameter in a way that is visible from outside the function:
|
|
|
|
|
|
|
|
```
|
2022-09-10 12:15:58 +02:00
|
|
|
func greet(text):
|
|
|
|
text = "Hello " + text
|
|
|
|
|
|
|
|
func _ready():
|
|
|
|
# Create a String (passed by value and immutable).
|
2024-03-16 20:56:52 +01:00
|
|
|
var example = "Pandemonium"
|
2022-09-10 12:15:58 +02:00
|
|
|
|
|
|
|
# Pass example as a parameter to `greet()`,
|
|
|
|
# which modifies the parameter and does not return any value.
|
|
|
|
greet(example)
|
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
print(example) # Pandemonium
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-09-10 12:15:58 +02:00
|
|
|
|
|
|
|
A value is **passed by reference** when it is *not* copied every time it's
|
|
|
|
specified as a function parameter. This allows modifying a function
|
|
|
|
parameter within a function body (and having the modified value accessible
|
|
|
|
outside the function). The downside is that the data passed as a function
|
|
|
|
parameter is no longer guaranteed to be immutable, which can cause
|
2023-01-12 22:32:46 +01:00
|
|
|
difficult-to-track bugs if not done carefully:
|
2022-09-10 12:15:58 +02:00
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-09-10 12:15:58 +02:00
|
|
|
func greet(text):
|
|
|
|
text.push_front("Hello")
|
|
|
|
|
|
|
|
func _ready():
|
|
|
|
# Create an Array (passed by reference and mutable) containing a String,
|
|
|
|
# instead of a String (passed by value and immutable).
|
2024-03-16 20:56:52 +01:00
|
|
|
var example = ["Pandemonium"]
|
2022-09-10 12:15:58 +02:00
|
|
|
|
|
|
|
# Pass example as a parameter to `greet()`,
|
|
|
|
# which modifies the parameter and does not return any value.
|
|
|
|
greet(example)
|
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
print(example) # [Hello, Pandemonium] (Array with 2 String elements)
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-09-10 12:15:58 +02:00
|
|
|
|
|
|
|
Compared to passing by value, passing by reference can perform better when
|
|
|
|
using large objects since copying large objects in memory can be slow.
|
|
|
|
|
2024-03-16 20:56:52 +01:00
|
|
|
Additionally, in Pandemonium, base types such as String are **immutable**. This
|
2022-09-10 12:15:58 +02:00
|
|
|
means that modifying them will *always* return a copy of the original value,
|
|
|
|
rather than modifying the value in-place.
|
|
|
|
|
2022-03-18 17:46:08 +01:00
|
|
|
Arrays
|
|
|
|
------
|
|
|
|
|
|
|
|
Arrays in dynamically typed languages can contain many different mixed
|
|
|
|
datatypes inside and are always dynamic (can be resized at any time).
|
|
|
|
Compare for example arrays in statically typed languages:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
int *array = new int[4]; // Create array.
|
|
|
|
array[0] = 10; // Initialize manually.
|
|
|
|
array[1] = 20; // Can't mix types.
|
|
|
|
array[2] = 40;
|
|
|
|
array[3] = 60;
|
|
|
|
// Can't resize.
|
|
|
|
use_array(array); // Passed as pointer.
|
|
|
|
delete[] array; // Must be freed.
|
|
|
|
|
|
|
|
// or
|
|
|
|
|
|
|
|
std::vector<int> array;
|
|
|
|
array.resize(4);
|
|
|
|
array[0] = 10; // Initialize manually.
|
|
|
|
array[1] = 20; // Can't mix types.
|
|
|
|
array[2] = 40;
|
|
|
|
array[3] = 60;
|
|
|
|
array.resize(3); // Can be resized.
|
|
|
|
use_array(array); // Passed reference or value.
|
|
|
|
// Freed when stack ends.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
And in GDScript:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var array = [10, "hello", 40, 60] # Simple, and can mix types.
|
|
|
|
array.resize(3) # Can be resized.
|
|
|
|
use_array(array) # Passed as reference.
|
|
|
|
# Freed when no longer in use.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
In dynamically typed languages, arrays can also double as other
|
|
|
|
datatypes, such as lists:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var array = []
|
|
|
|
array.append(4)
|
|
|
|
array.append(5)
|
|
|
|
array.pop_front()
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Or unordered sets:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var a = 20
|
|
|
|
if a in [10, 20, 30]:
|
|
|
|
print("We have a winner!")
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Dictionaries
|
|
|
|
------------
|
|
|
|
|
|
|
|
Dictionaries are a powerful tool in dynamically typed languages.
|
|
|
|
Most programmers that come from statically typed languages (such as C++
|
|
|
|
or C#) ignore their existence and make their life unnecessarily more
|
|
|
|
difficult. This datatype is generally not present in such languages (or
|
|
|
|
only in limited form).
|
|
|
|
|
|
|
|
Dictionaries can map any value to any other value with complete
|
|
|
|
disregard for the datatype used as either key or value. Contrary to
|
|
|
|
popular belief, they are efficient because they can be implemented
|
|
|
|
with hash tables. They are, in fact, so efficient that some languages
|
|
|
|
will go as far as implementing arrays as dictionaries.
|
|
|
|
|
|
|
|
Example of Dictionary:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var d = {"name": "John", "age": 22} # Simple syntax.
|
|
|
|
print("Name: ", d["name"], " Age: ", d["age"])
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Dictionaries are also dynamic, keys can be added or removed at any point
|
|
|
|
at little cost:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
d["mother"] = "Rebecca" # Addition.
|
|
|
|
d["age"] = 11 # Modification.
|
|
|
|
d.erase("name") # Removal.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
In most cases, two-dimensional arrays can often be implemented more
|
|
|
|
easily with dictionaries. Here's a simple battleship game example:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
# Battleship Game
|
|
|
|
|
|
|
|
const SHIP = 0
|
|
|
|
const SHIP_HIT = 1
|
|
|
|
const WATER_HIT = 2
|
|
|
|
|
|
|
|
var board = {}
|
|
|
|
|
|
|
|
func initialize():
|
|
|
|
board[Vector2(1, 1)] = SHIP
|
|
|
|
board[Vector2(1, 2)] = SHIP
|
|
|
|
board[Vector2(1, 3)] = SHIP
|
|
|
|
|
|
|
|
func missile(pos):
|
|
|
|
if pos in board: # Something at that position.
|
|
|
|
if board[pos] == SHIP: # There was a ship! hit it.
|
|
|
|
board[pos] = SHIP_HIT
|
|
|
|
else:
|
|
|
|
print("Already hit here!") # Hey dude you already hit here.
|
|
|
|
else: # Nothing, mark as water.
|
|
|
|
board[pos] = WATER_HIT
|
|
|
|
|
|
|
|
func game():
|
|
|
|
initialize()
|
|
|
|
missile(Vector2(1, 1))
|
|
|
|
missile(Vector2(5, 8))
|
|
|
|
missile(Vector2(2, 3))
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Dictionaries can also be used as data markup or quick structures. While
|
|
|
|
GDScript's dictionaries resemble python dictionaries, it also supports Lua
|
|
|
|
style syntax and indexing, which makes it useful for writing initial
|
|
|
|
states and quick structs:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
# Same example, lua-style support.
|
|
|
|
# This syntax is a lot more readable and usable.
|
|
|
|
# Like any GDScript identifier, keys written in this form cannot start
|
|
|
|
# with a digit.
|
|
|
|
|
|
|
|
var d = {
|
|
|
|
name = "John",
|
|
|
|
age = 22
|
|
|
|
}
|
|
|
|
|
|
|
|
print("Name: ", d.name, " Age: ", d.age) # Used "." based indexing.
|
|
|
|
|
|
|
|
# Indexing
|
|
|
|
|
|
|
|
d["mother"] = "Rebecca"
|
|
|
|
d.mother = "Caroline" # This would work too to create a new key.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
For & while
|
|
|
|
-----------
|
|
|
|
|
|
|
|
Iterating in some statically typed languages can be quite complex:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
const char* strings = new const char*[50];
|
|
|
|
|
|
|
|
[..]
|
|
|
|
|
|
|
|
for (int i = 0; i < 50; i++) {
|
|
|
|
|
|
|
|
printf("Value: %s\n", i, strings[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Even in STL:
|
|
|
|
|
|
|
|
for (std::list<std::string>::const_iterator it = strings.begin(); it != strings.end(); it++) {
|
|
|
|
|
|
|
|
std::cout << *it << std::endl;
|
|
|
|
}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
This is usually greatly simplified in dynamically typed languages:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
for s in strings:
|
|
|
|
print(s)
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Container datatypes (arrays and dictionaries) are iterable. Dictionaries
|
|
|
|
allow iterating the keys:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
for key in dict:
|
|
|
|
print(key, " -> ", dict[key])
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Iterating with indices is also possible:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
for i in range(strings.size()):
|
|
|
|
print(strings[i])
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
The range() function can take 3 arguments:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
range(n) # Will go from 0 to n-1.
|
|
|
|
range(b, n) # Will go from b to n-1.
|
|
|
|
range(b, n, s) # Will go from b to n-1, in steps of s.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Some statically typed programming language examples:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
for (int i = 0; i < 10; i++) {}
|
|
|
|
|
|
|
|
for (int i = 5; i < 10; i++) {}
|
|
|
|
|
|
|
|
for (int i = 5; i < 10; i += 2) {}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Translate to:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
for i in range(10):
|
|
|
|
pass
|
|
|
|
|
|
|
|
for i in range(5, 10):
|
|
|
|
pass
|
|
|
|
|
|
|
|
for i in range(5, 10, 2):
|
|
|
|
pass
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
And backwards looping is done through a negative counter:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
for (int i = 10; i > 0; i--) {}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Becomes:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
for i in range(10, 0, -1):
|
|
|
|
pass
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
While
|
|
|
|
-----
|
|
|
|
|
|
|
|
while() loops are the same everywhere:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var i = 0
|
|
|
|
|
|
|
|
while i < strings.size():
|
|
|
|
print(strings[i])
|
|
|
|
i += 1
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Custom iterators
|
|
|
|
----------------
|
|
|
|
You can create custom iterators in case the default ones don't quite meet your
|
2023-01-12 20:57:31 +01:00
|
|
|
needs by overriding the Variant class's `iter_init`, `iter_next`, and `iter_get`
|
2022-03-18 17:46:08 +01:00
|
|
|
functions in your script. An example implementation of a forward iterator follows:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
class ForwardIterator:
|
|
|
|
var start
|
|
|
|
var current
|
|
|
|
var end
|
|
|
|
var increment
|
|
|
|
|
|
|
|
func _init(start, stop, increment):
|
|
|
|
self.start = start
|
|
|
|
self.current = start
|
|
|
|
self.end = stop
|
|
|
|
self.increment = increment
|
|
|
|
|
|
|
|
func should_continue():
|
|
|
|
return (current < end)
|
|
|
|
|
|
|
|
func _iter_init(arg):
|
|
|
|
current = start
|
|
|
|
return should_continue()
|
|
|
|
|
|
|
|
func _iter_next(arg):
|
|
|
|
current += increment
|
|
|
|
return should_continue()
|
|
|
|
|
|
|
|
func _iter_get(arg):
|
|
|
|
return current
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
And it can be used like any other iterator:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
var itr = ForwardIterator.new(0, 6, 2)
|
|
|
|
for i in itr:
|
|
|
|
print(i) # Will print 0, 2, and 4.
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
2023-01-12 20:57:31 +01:00
|
|
|
Make sure to reset the state of the iterator in `iter_init`, otherwise nested
|
2022-03-18 17:46:08 +01:00
|
|
|
for-loops that use custom iterators will not work as expected.
|
|
|
|
|
|
|
|
Duck typing
|
|
|
|
-----------
|
|
|
|
|
|
|
|
One of the most difficult concepts to grasp when moving from a
|
|
|
|
statically typed language to a dynamic one is duck typing. Duck typing
|
|
|
|
makes overall code design much simpler and straightforward to write, but
|
|
|
|
it's not obvious how it works.
|
|
|
|
|
|
|
|
As an example, imagine a situation where a big rock is falling down a
|
|
|
|
tunnel, smashing everything on its way. The code for the rock, in a
|
|
|
|
statically typed language would be something like:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
void BigRollingRock::on_object_hit(Smashable *entity) {
|
|
|
|
|
|
|
|
entity->smash();
|
|
|
|
}
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
This way, everything that can be smashed by a rock would have to
|
|
|
|
inherit Smashable. If a character, enemy, piece of furniture, small rock
|
|
|
|
were all smashable, they would need to inherit from the class Smashable,
|
|
|
|
possibly requiring multiple inheritance. If multiple inheritance was
|
|
|
|
undesired, then they would have to inherit a common class like Entity.
|
2023-01-12 19:43:03 +01:00
|
|
|
Yet, it would not be very elegant to add a virtual method `smash()` to
|
2022-03-18 17:46:08 +01:00
|
|
|
Entity only if a few of them can be smashed.
|
|
|
|
|
|
|
|
With dynamically typed languages, this is not a problem. Duck typing
|
2023-01-12 19:43:03 +01:00
|
|
|
makes sure you only have to define a `smash()` function where required
|
2022-03-18 17:46:08 +01:00
|
|
|
and that's it. No need to consider inheritance, base classes, etc.
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
func _on_object_hit(object):
|
|
|
|
object.smash()
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
And that's it. If the object that hit the big rock has a smash() method,
|
|
|
|
it will be called. No need for inheritance or polymorphism. Dynamically
|
|
|
|
typed languages only care about the instance having the desired method
|
|
|
|
or member, not what it inherits or the class type. The definition of
|
|
|
|
Duck Typing should make this clearer:
|
|
|
|
|
|
|
|
*"When I see a bird that walks like a duck and swims like a duck and
|
|
|
|
quacks like a duck, I call that bird a duck"*
|
|
|
|
|
|
|
|
In this case, it translates to:
|
|
|
|
|
|
|
|
*"If the object can be smashed, don't care what it is, just smash it."*
|
|
|
|
|
|
|
|
Yes, we should call it Hulk typing instead.
|
|
|
|
|
|
|
|
It's possible that the object being hit doesn't have a smash() function.
|
|
|
|
Some dynamically typed languages simply ignore a method call when it
|
|
|
|
doesn't exist, but GDScript is stricter, so checking if the function
|
|
|
|
exists is desirable:
|
|
|
|
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
func _on_object_hit(object):
|
|
|
|
if object.has_method("smash"):
|
|
|
|
object.smash()
|
2023-01-12 22:32:46 +01:00
|
|
|
```
|
2022-03-18 17:46:08 +01:00
|
|
|
|
|
|
|
Then, simply define that method and anything the rock touches can be
|
|
|
|
smashed.
|