Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
GDScript: Wprowadzenie do języków dynamicznych
O Godocie
Ten samouczek ma być szybkim punktem odniesienia dla efektywniejszego korzystania z GDScript. Koncentruje się on na specyficznych przypadkach języka, ale obejmuje również wiele informacji na temat dynamicznie pisanych języków.
Ma to być szczególnie przydatne dla programistów, którzy nie mają doświadczenia z dynamicznie typowanymi językami lub mają niewielkie doświadczenie w posługiwaniu się nimi.
Dynamiczna natura
Zalety i wady dynamicznie typowanego języka
GDScript jest dynamicznie typowanym językiem. Jego głównymi zaletami są:
The language is easy to get started with.
Większość kodu można zapisywane i zmieniane, szybko i bez kłopotów.
The code is easy to read (little clutter).
Nie potrzeba kompilacji do testowania.
Jest niewielki.
It has duck-typing and polymorphism by nature.
Podczas gdy głównymi wadami są:
Mniejsza wydajność niż statycznie typowane języki.
More difficult to refactor (symbols can't be traced).
Niektóre błędy, które zwykle są wykrywane w czasie kompilacji w statycznie wpisanych językach, pojawiają się tylko podczas uruchamiania kodu (ponieważ parsowanie wyrażeń jest bardziej rygorystyczne).
Less flexibility for code-completion (some variable types are only known at runtime).
This, translated to reality, means that Godot used with GDScript is a combination 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.
Zmienne i przypisania
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:
Statyczny:
int a; // Value uninitialized.
a = 5; // This is valid.
a = "Hi!"; // This is invalid.
Dynamiczny:
var a # 'null' by default.
a = 5 # Valid, 'a' becomes an integer.
a = "Hi!" # Valid, 'a' changed to a string.
Jako argumenty funkcji:
Funkcje mają również charakter dynamiczny, co oznacza, że można je wywoływać za pomocą różnych argumentów, przykład użycia:
Statyczny:
void print_value(int value) {
printf("value is %i\n", value);
}
[..]
print_value(55); // Valid.
print_value("Hello"); // Invalid.
Dynamiczny:
func print_value(value):
print(value)
[..]
print_value(55) # Valid.
print_value("Hello") # Valid.
Wskaźniki i odniesienia:
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++:
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.
}
Java:
@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.
}
GDScript:
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.
In GDScript, only base types (int, float, string and the vector types) are passed by value to functions (value is copied). Everything else (instances, arrays, dictionaries, etc) is passed as reference. Classes that inherit RefCounted (the default if nothing is specified) will be freed when not used, but manual memory management is allowed too if inheriting manually from Object.
Tablice
Tablice w dynamicznie typowanych językach mogą zawierać wiele różnych mieszanych typów danych i są zawsze dynamiczne (można je zmieniać w dowolnym momencie). Porównaj np. tablice w statycznie wpisanych językach:
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.
I w GDScript:
var array = [10, "hello", 40, 60] # You can mix types.
array.resize(3) # Can be resized.
use_array(array) # Passed as reference.
# Freed when no longer in use.
In dynamically typed languages, arrays can also double as other datatypes, such as lists:
var array = []
array.append(4)
array.append(5)
array.pop_front()
Lub nieuporządkowane zestawy:
var a = 20
if a in [10, 20, 30]:
print("We have a winner!")
Słowniki
Dictionaries are a powerful tool in dynamically typed languages. In GDScript, untyped dictionaries can be used for many cases where a statically typed language would tend to use another data structure.
Słowniki mogą mapować dowolne wartości na dowolne inne wartości, bez względu na to, czy są one kluczem czy wartością. Wbrew powszechnemu przekonaniu są one efektywne, ponieważ można je realizować za pomocą haszowanych tablic. W rzeczywistości są one tak wydajne, że niektóre języki implementują tablice jako słowniki.
Przykład słownika:
var d = {"name": "John", "age": 22}
print("Name: ", d["name"], " Age: ", d["age"])
Słowniki są również dynamiczne, klucze mogą być dodawane lub usuwane w dowolnym momencie niewielkim kosztem:
d["mother"] = "Rebecca" # Addition.
d["age"] = 11 # Modification.
d.erase("name") # Removal.
In most cases, two-dimensional arrays can often be implemented more easily with dictionaries. Here's a battleship game example:
# 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))
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:
# 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.
For i while
Iterating using the C-style for loop in C-derived languages can be quite complex:
const char** strings = new const char*[50];
[..]
for (int i = 0; i < 50; i++) {
printf("Value: %c Index: %d\n", strings[i], i);
}
// Even in STL:
std::list<std::string> strings;
[..]
for (std::string::const_iterator it = strings.begin(); it != strings.end(); it++) {
std::cout << *it << std::endl;
}
Because of this, GDScript makes the opinionated decision to have a for-in loop over iterables instead:
for s in strings:
print(s)
Kontenerowe typy danych (tablice i słowniki) są iteracyjne. Słowniki umożliwiają iterowanie kluczy:
for key in dict:
print(key, " -> ", dict[key])
Iterating with indices is also possible:
for i in range(strings.size()):
print(strings[i])
Funkcja range() może przyjmować 3 argumenty:
range(n) # Will count from 0 to n in steps of 1. The parameter n is exclusive.
range(b, n) # Will count from b to n in steps of 1. The parameters b is inclusive. The parameter n is exclusive.
range(b, n, s) # Will count from b to n, in steps of s. The parameters b is inclusive. The parameter n is exclusive.
Some examples involving C-style for loops:
for (int i = 0; i < 10; i++) {}
for (int i = 5; i < 10; i++) {}
for (int i = 5; i < 10; i += 2) {}
Przetłumacz na:
for i in range(10):
pass
for i in range(5, 10):
pass
for i in range(5, 10, 2):
pass
And backwards looping done through a negative counter:
for (int i = 10; i > 0; i--) {}
Staje się:
for i in range(10, 0, -1):
pass
Chociaż
Pętle while() są wszędzie takie same:
var i = 0
while i < strings.size():
print(strings[i])
i += 1
Niestandardowe iteratory
You can create custom iterators in case the default ones don't quite meet your
needs by overriding _iter_init(), _iter_next(), and _iter_get()
functions in your script. An example implementation of a forward iterator follows:
class ForwardIterator:
var _start
var _end
var _increment
func _init(start, end, increment):
_start = start
_end = end
_increment = increment
func _should_continue(current):
return current < _end
func _iter_init(iter):
# Initialize the state to store the current value.
iter[0] = _start
return _should_continue(iter[0])
func _iter_next(iter):
iter[0] += _increment
return _should_continue(iter[0])
func _iter_get(iter):
# The state is not wrapped in an array for `_iter_get()`.
# The iteration value is the same as the state.
return iter
I może być używany jak każdy inny iterator:
var itr = ForwardIterator.new(0, 6, 2)
for i in itr:
print(i) # Will print 0, 2, and 4.
It is possible but discouraged to store the state in a member variable.
Multiple states are necessary in cases such as nested loops where the same
iterator instance is used simultaneously. The iter parameter in
_iter_init() and _iter_next() is a single-element array so that updates
can persist. Whereas in _iter_get(), the state is is not wrapped because it
is supposed to be read-only.
Returning true from _iter_init() and _iter_next() indicates that the
iterator is valid. Returning false will terminate the loop.
For more details see _iter_init(), _iter_next(), and _iter_get().
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.
Jako przykład wyobraź sobie sytuację, w której duża skała opada w dół tunelu, rozbijając wszystko po drodze. Kod skały, w statycznie wpisanym języku, byłby czymś w rodzaju:
void BigRollingRock::on_object_hit(Smashable *entity) {
entity->smash();
}
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.
Yet, it would not be very elegant to add a virtual method smash() to
Entity only if a few of them can be smashed.
W przypadku języków dynamicznie typowanych nie stanowi to problemu. Duck typing upewnia się że zdefiniowałeś jedynie funkcję smash() tam, gdzie jest to wymagane i to tyle. Nie trzeba brać pod uwagę dziedziczenia, klas podstawowych itp.
func _on_object_hit(object):
object.smash()
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:
"Kiedy widzę ptaka, który spaceruje jak kaczka i pływa jak kaczka i kwacze jak kaczka, nazywam go kaczką"
W tym przypadku można przetłumaczyć to na:
"Jeśli obiekt może zostać rozbity, nie przejmuj się tym, czym jest, po prostu go rozbij."
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:
func _on_object_hit(object):
if object.has_method("smash"):
object.smash()
Then, define that method and anything the rock touches can be smashed.