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: Uma introdução às linguagens dinâmicas
Sobre
Este tutorial pretende ser uma referência rápida de como usar GDScript de forma mais eficiente. Focando, assim, em casos mais comuns específicos da linguagem, mas também contém informações sobre linguagens dinâmicamente tipadas.
É destinado a ser especialmente útil para programadores com pouca ou nenhuma experiência com linguagens dinâmicamente tipadas.
Natureza dinâmica
Prós e contras da tipagem dinâmica
GDScript é uma linguagem Dinamicamente Tipada. Como tal, suas maiores vantagens são:
The language is easy to get started with.
A maioria do código pode ser escrito e modificado rapidamente sem complicações.
The code is easy to read (little clutter).
Nenhuma compilação é necessária para testar.
Tempo de execução é bem pequeno.
It has duck-typing and polymorphism by nature.
Enquanto as maiores desvantagens são:
Menor performance que linguagens com tipagem estática.
More difficult to refactor (symbols can't be traced).
Alguns erros que, tipicamente, seriam detectados em tempo de compilação em linguagens tipadas estaticamente aparecem apenas rodando o código (porque a análise de expressões é mais estrita).
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.
Variáveis & atribuição
Todas as variáveis em uma linguagem dinamicamente tipada são tipo-"variante". Isto significa que seus tipos não são fixos, e é modificado apenas na atribuição. Exemplos:
Estático:
int a; // Value uninitialized.
a = 5; // This is valid.
a = "Hi!"; // This is invalid.
Dinâmico:
var a # 'null' by default.
a = 5 # Valid, 'a' becomes an integer.
a = "Hi!" # Valid, 'a' changed to a string.
Como argumentos da função:
Funções também têm natureza dinâmica, o que significa que elas podem ser invocadas com argumentos diferentes, por exemplo:
Estático:
void print_value(int value) {
printf("value is %i\n", value);
}
[..]
print_value(55); // Valid.
print_value("Hello"); // Invalid.
Dinâmico:
func print_value(value):
print(value)
[..]
print_value(55) # Valid.
print_value("Hello") # Valid.
Ponteiros & referência:
Em linguagens estáticas como C ou C++ (e de certa medida Java e C#), existe uma distinção entre uma variável e um ponteiro/referência a uma variável. Esse último deixa o objeto ser modificado por outras funções através da passagem de uma referência ao original.
Em C# ou Java, tudo que não é um tipo embutido (int, float ou, às vezes string) é sempre um ponteiro ou uma referência. Referências são coletadas pelo coletor de lixo automaticamente, o que significa que são apagadas quando não mais usadas. Linguagens tipadas dinamicamente tendem a usar esse modelo de memória também. Alguns exemplos:
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.
Arrays
Arrays em linguagens dinamicamente tipadas podem conter muitos tipos de dados mistos diferentes e são sempre dinâmicos (podem ser redimensionados a qualquer momento). Compare, por exemplo, arrays em linguagens estaticamente tipadas:
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.
E no 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.
Em linguagens dinamicamente tipadas, os arrays também podem ser usados como outros tipos de dados, como listas:
var array = []
array.append(4)
array.append(5)
array.pop_front()
Ou conjuntos não ordenados:
var a = 20
if a in [10, 20, 30]:
print("We have a winner!")
Dictionaries
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.
Os dicionários podem mapear qualquer valor para qualquer outro valor com total desconsideração do tipo de dados usado como chave ou valor. Ao contrário da crença popular, eles são eficientes porque podem ser implementados com tabelas de hash. Eles são, de fato, tão eficientes que algumas linguagens vão tão longe quanto implementar matrizes como dicionários.
Exemplo de Dictionary:
var d = {"name": "John", "age": 22}
print("Name: ", d["name"], " Age: ", d["age"])
Dictionaries também são dinâmicos, as chaves podem ser adicionadas ou removidas a qualquer momento a baixo custo:
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))
Dicionários também podem ser usados como marcação de dados ou estruturas rápidas. Enquanto dicionários em GDScript lembram dicionários do Python, ele também suporta sintaxe e indexação no estilo Lua, o que o torna útil para escrever estados iniciais e estruturas rápidas:
# 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 e 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)
Tipos de dados container (arrays e dictionaries) são iteráveis. Dictionaries permitem a iteração das chaves:
for key in dict:
print(key, " -> ", dict[key])
Também é possível iterar com índices:
for i in range(strings.size()):
print(strings[i])
A função range() pode ter 3 argumentos:
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) {}
Traduzir para:
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--) {}
Torna-se:
for i in range(10, 0, -1):
pass
Enquanto
Loops while() são os mesmos em todos os lugares:
var i = 0
while i < strings.size():
print(strings[i])
i += 1
Iteradores personalizados
Você pode criar iteradores personalizados caso os existentes não satisfaçam suas necessidades, sobrescrevendo as funções _iter_init
, _iter_next
e _iter_get
da classe Variant em seu script. Aqui está um exemplo de implementação de um iterador avançado:
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
E pode ser usado como qualquer outro iterador:
var itr = ForwardIterator.new(0, 6, 2)
for i in itr:
print(i) # Will print 0, 2, and 4.
Certifique-se de redefinir o estado do iterador em _iter_init
, caso contrário, loops for aninhados que usam iteradores personalizados não funcionarão como esperado.
Duck typing
Um dos conceitos mais difíceis de entender quando passa de uma linguagem estaticamente tipada para uma dinâmica é duck typing. Duck typing faz com que o design geral do código seja mais simples e direto de escrever, mas não é óbvio como ele funciona.
Como exemplo, imagine uma situação onde uma grande rocha está caindo de um túnel, esmagando tudo em seu caminho. O código para a rocha, em uma linguagem estaticamente tipada seria algo como:
void BigRollingRock::on_object_hit(Smashable *entity) {
entity->smash();
}
Desta forma, tudo o que pode ser esmagado por uma rocha teria que herdar Smashable. Se um personagem, um inimigo, uma peça de mobília, uma pequena pedra fossem todos quebráveis, eles precisariam herdar da classe Smashable, possivelmente exigindo herança múltipla. Se herança múltipla fosse indesejada, eles teriam que herdar uma classe comum como Entity. No entanto, não seria muito elegante adicionar um método virtual smash()
à Entity apenas se alguns deles puderem ser destruídos.
Com linguagens dinamicamente tipadas, isso não é um problema. Duck typing garante que você só precisa definir uma função smash()
onde for necessário e pronto. Não há necessidade de considerar herança, classes base, etc.
func _on_object_hit(object):
object.smash()
E é isso. Se o objeto que atingiu a pedra grande tiver um método smash (), ele será chamado. Não há necessidade de herança ou polimorfismo. Linguagens dinamicamente tipadas só se preocupam com a instância que possui o método ou membro desejado, não o que ele herda ou o tipo de classe. A definição de Duck Typing deve tornar isso mais claro:
"Quando eu vejo um pássaro que anda como um pato e nada como um pato e grasna como um pato, eu chamo esse pássaro de pato"
Neste caso, traduz para:
"Se o objeto pode ser esmagado, não importa o que seja, apenas esmague-o."
Sim, nós deveríamos chamar isso de tipo Hulk ao invés disso.
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.