528
tutorials/scripting/gdscript/gdscript_advanced.rst
Normal file
@@ -0,0 +1,528 @@
|
||||
.. _doc_gdscript_more_efficiently:
|
||||
|
||||
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).
|
||||
|
||||
This, translated to reality, means that Godot+GDScript are 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.
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
int a; // Value uninitialized.
|
||||
a = 5; // This is valid.
|
||||
a = "Hi!"; // This is invalid.
|
||||
|
||||
Dynamic:
|
||||
|
||||
::
|
||||
|
||||
var a # 'null' by default.
|
||||
a = 5 # Valid, 'a' becomes an integer.
|
||||
a = "Hi!" # Valid, 'a' changed to a string.
|
||||
|
||||
As function arguments:
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Functions are of dynamic nature too, which means they can be called with
|
||||
different arguments, for example:
|
||||
|
||||
Static:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
void print_value(int value) {
|
||||
|
||||
printf("value is %i\n", value);
|
||||
}
|
||||
|
||||
[..]
|
||||
|
||||
print_value(55); // Valid.
|
||||
print_value("Hello"); // Invalid.
|
||||
|
||||
Dynamic:
|
||||
|
||||
::
|
||||
|
||||
func print_value(value):
|
||||
print(value)
|
||||
|
||||
[..]
|
||||
|
||||
print_value(55) # Valid.
|
||||
print_value("Hello") # Valid.
|
||||
|
||||
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++:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: 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 :ref:`class_Reference` (the default if nothing is specified)
|
||||
will be freed when not used, but manual memory management is allowed too
|
||||
if inheriting manually from :ref:`class_Object`.
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
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.
|
||||
|
||||
And in GDScript:
|
||||
|
||||
::
|
||||
|
||||
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.
|
||||
|
||||
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()
|
||||
|
||||
Or unordered sets:
|
||||
|
||||
::
|
||||
|
||||
var a = 20
|
||||
if a in [10, 20, 30]:
|
||||
print("We have a winner!")
|
||||
|
||||
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:
|
||||
|
||||
::
|
||||
|
||||
var d = {"name": "John", "age": 22} # Simple syntax.
|
||||
print("Name: ", d["name"], " Age: ", d["age"])
|
||||
|
||||
Dictionaries are also dynamic, keys can be added or removed at any point
|
||||
at little cost:
|
||||
|
||||
::
|
||||
|
||||
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 simple 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 & while
|
||||
-----------
|
||||
|
||||
Iterating in some statically typed languages can be quite complex:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
This is usually greatly simplified in dynamically typed languages:
|
||||
|
||||
::
|
||||
|
||||
for s in strings:
|
||||
print(s)
|
||||
|
||||
Container datatypes (arrays and dictionaries) are iterable. Dictionaries
|
||||
allow iterating the keys:
|
||||
|
||||
::
|
||||
|
||||
for key in dict:
|
||||
print(key, " -> ", dict[key])
|
||||
|
||||
Iterating with indices is also possible:
|
||||
|
||||
::
|
||||
|
||||
for i in range(strings.size()):
|
||||
print(strings[i])
|
||||
|
||||
The range() function can take 3 arguments:
|
||||
|
||||
::
|
||||
|
||||
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.
|
||||
|
||||
Some statically typed programming language examples:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
for (int i = 0; i < 10; i++) {}
|
||||
|
||||
for (int i = 5; i < 10; i++) {}
|
||||
|
||||
for (int i = 5; i < 10; i += 2) {}
|
||||
|
||||
Translate to:
|
||||
|
||||
::
|
||||
|
||||
for i in range(10):
|
||||
pass
|
||||
|
||||
for i in range(5, 10):
|
||||
pass
|
||||
|
||||
for i in range(5, 10, 2):
|
||||
pass
|
||||
|
||||
And backwards looping is done through a negative counter:
|
||||
|
||||
::
|
||||
|
||||
for (int i = 10; i > 0; i--) {}
|
||||
|
||||
Becomes:
|
||||
|
||||
::
|
||||
|
||||
for i in range(10, 0, -1):
|
||||
pass
|
||||
|
||||
While
|
||||
-----
|
||||
|
||||
while() loops are the same everywhere:
|
||||
|
||||
::
|
||||
|
||||
var i = 0
|
||||
|
||||
while i < strings.size():
|
||||
print(strings[i])
|
||||
i += 1
|
||||
|
||||
Custom iterators
|
||||
----------------
|
||||
You can create custom iterators in case the default ones don't quite meet your
|
||||
needs by overriding the Variant class's ``_iter_init``, ``_iter_next``, and ``_iter_get``
|
||||
functions in your script. An example implementation of a forward iterator follows:
|
||||
|
||||
::
|
||||
|
||||
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
|
||||
|
||||
And it can be used like any other iterator:
|
||||
|
||||
::
|
||||
|
||||
var itr = ForwardIterator.new(0, 6, 2)
|
||||
for i in itr:
|
||||
print(i) # Will print 0, 2, and 4.
|
||||
|
||||
Make sure to reset the state of the iterator in ``_iter_init``, otherwise nested
|
||||
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:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
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.
|
||||
|
||||
With dynamically typed languages, this is not a problem. Duck typing
|
||||
makes sure you only have to define a ``smash()`` function where required
|
||||
and that's it. No need to consider inheritance, base classes, etc.
|
||||
|
||||
::
|
||||
|
||||
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:
|
||||
|
||||
*"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:
|
||||
|
||||
::
|
||||
|
||||
func _on_object_hit(object):
|
||||
if object.has_method("smash"):
|
||||
object.smash()
|
||||
|
||||
Then, simply define that method and anything the rock touches can be
|
||||
smashed.
|
||||
1730
tutorials/scripting/gdscript/gdscript_basics.rst
Normal file
289
tutorials/scripting/gdscript/gdscript_exports.rst
Normal file
@@ -0,0 +1,289 @@
|
||||
.. _doc_gdscript_exports:
|
||||
|
||||
GDScript exports
|
||||
================
|
||||
|
||||
Introduction to exports
|
||||
-----------------------
|
||||
|
||||
In Godot, class members can be exported. This means their value gets saved along
|
||||
with the resource (such as the :ref:`scene <class_PackedScene>`) they're
|
||||
attached to. They will also be available for editing in the property editor.
|
||||
Exporting is done by using the ``export`` keyword::
|
||||
|
||||
extends Button
|
||||
|
||||
export var number = 5 # Value will be saved and visible in the property editor.
|
||||
|
||||
An exported variable must be initialized to a constant expression or have an
|
||||
export hint in the form of an argument to the ``export`` keyword (see the
|
||||
*Examples* section below).
|
||||
|
||||
One of the fundamental benefits of exporting member variables is to have
|
||||
them visible and editable in the editor. This way, artists and game designers
|
||||
can modify values that later influence how the program runs. For this, a
|
||||
special export syntax is provided.
|
||||
|
||||
.. note::
|
||||
|
||||
Exporting properties can also be done in other languages such as C#.
|
||||
The syntax varies depending on the language. See :ref:`doc_c_sharp_exports`
|
||||
for information on C# exports.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
::
|
||||
|
||||
# If the exported value assigns a constant or constant expression,
|
||||
# the type will be inferred and used in the editor.
|
||||
|
||||
export var number = 5
|
||||
|
||||
# Export can take a basic data type as an argument, which will be
|
||||
# used in the editor.
|
||||
|
||||
export(int) var number
|
||||
|
||||
# Export can also take a resource type to use as a hint.
|
||||
|
||||
export(Texture) var character_face
|
||||
export(PackedScene) var scene_file
|
||||
# There are many resource types that can be used this way, try e.g.
|
||||
# the following to list them:
|
||||
export(Resource) var resource
|
||||
|
||||
# Integers and strings hint enumerated values.
|
||||
|
||||
# Editor will enumerate as 0, 1 and 2.
|
||||
export(int, "Warrior", "Magician", "Thief") var character_class
|
||||
# Editor will enumerate with string names.
|
||||
export(String, "Rebecca", "Mary", "Leah") var character_name
|
||||
|
||||
# Named enum values
|
||||
|
||||
# Editor will enumerate as THING_1, THING_2, ANOTHER_THING.
|
||||
enum NamedEnum {THING_1, THING_2, ANOTHER_THING = -1}
|
||||
export(NamedEnum) var x
|
||||
|
||||
# Strings as paths
|
||||
|
||||
# String is a path to a file.
|
||||
export(String, FILE) var f
|
||||
# String is a path to a directory.
|
||||
export(String, DIR) var f
|
||||
# String is a path to a file, custom filter provided as hint.
|
||||
export(String, FILE, "*.txt") var f
|
||||
|
||||
# Using paths in the global filesystem is also possible,
|
||||
# but only in scripts in "tool" mode.
|
||||
|
||||
# String is a path to a PNG file in the global filesystem.
|
||||
export(String, FILE, GLOBAL, "*.png") var tool_image
|
||||
# String is a path to a directory in the global filesystem.
|
||||
export(String, DIR, GLOBAL) var tool_dir
|
||||
|
||||
# The MULTILINE setting tells the editor to show a large input
|
||||
# field for editing over multiple lines.
|
||||
export(String, MULTILINE) var text
|
||||
|
||||
# Limiting editor input ranges
|
||||
|
||||
# Allow integer values from 0 to 20.
|
||||
export(int, 20) var i
|
||||
# Allow integer values from -10 to 20.
|
||||
export(int, -10, 20) var j
|
||||
# Allow floats from -10 to 20 and snap the value to multiples of 0.2.
|
||||
export(float, -10, 20, 0.2) var k
|
||||
# Allow values 'y = exp(x)' where 'y' varies between 100 and 1000
|
||||
# while snapping to steps of 20. The editor will present a
|
||||
# slider for easily editing the value.
|
||||
export(float, EXP, 100, 1000, 20) var l
|
||||
|
||||
# Floats with easing hint
|
||||
|
||||
# Display a visual representation of the 'ease()' function
|
||||
# when editing.
|
||||
export(float, EASE) var transition_speed
|
||||
|
||||
# Colors
|
||||
|
||||
# Color given as red-green-blue value (alpha will always be 1).
|
||||
export(Color, RGB) var col
|
||||
# Color given as red-green-blue-alpha value.
|
||||
export(Color, RGBA) var col
|
||||
|
||||
# Nodes
|
||||
|
||||
# Another node in the scene can be exported as a NodePath.
|
||||
export(NodePath) var node_path
|
||||
# Do take note that the node itself isn't being exported -
|
||||
# there is one more step to call the true node:
|
||||
var node = get_node(node_path)
|
||||
|
||||
# Resources
|
||||
|
||||
export(Resource) var resource
|
||||
# In the Inspector, you can then drag and drop a resource file
|
||||
# from the FileSystem dock into the variable slot.
|
||||
|
||||
# Opening the inspector dropdown may result in an
|
||||
# extremely long list of possible classes to create, however.
|
||||
# Therefore, if you specify an extension of Resource such as:
|
||||
export(AnimationNode) var resource
|
||||
# The drop-down menu will be limited to AnimationNode and all
|
||||
# its inherited classes.
|
||||
|
||||
It must be noted that even if the script is not being run while in the
|
||||
editor, the exported properties are still editable. This can be used
|
||||
in conjunction with a :ref:`script in "tool" mode <doc_gdscript_tool_mode>`.
|
||||
|
||||
Exporting bit flags
|
||||
-------------------
|
||||
|
||||
Integers used as bit flags can store multiple ``true``/``false`` (boolean)
|
||||
values in one property. By using the export hint ``int, FLAGS, ...``, they
|
||||
can be set from the editor::
|
||||
|
||||
# Set any of the given flags from the editor.
|
||||
export(int, FLAGS, "Fire", "Water", "Earth", "Wind") var spell_elements = 0
|
||||
|
||||
You must provide a string description for each flag. In this example, ``Fire``
|
||||
has value 1, ``Water`` has value 2, ``Earth`` has value 4 and ``Wind``
|
||||
corresponds to value 8. Usually, constants should be defined accordingly (e.g.
|
||||
``const ELEMENT_WIND = 8`` and so on).
|
||||
|
||||
Export hints are also provided for the physics and render layers defined in the project settings::
|
||||
|
||||
export(int, LAYERS_2D_PHYSICS) var layers_2d_physics
|
||||
export(int, LAYERS_2D_RENDER) var layers_2d_render
|
||||
export(int, LAYERS_3D_PHYSICS) var layers_3d_physics
|
||||
export(int, LAYERS_3D_RENDER) var layers_3d_render
|
||||
|
||||
Using bit flags requires some understanding of bitwise operations.
|
||||
If in doubt, use boolean variables instead.
|
||||
|
||||
Exporting arrays
|
||||
----------------
|
||||
|
||||
Exported arrays can have initializers, but they must be constant expressions.
|
||||
|
||||
If the exported array specifies a type which inherits from Resource, the array
|
||||
values can be set in the inspector by dragging and dropping multiple files
|
||||
from the FileSystem dock at once.
|
||||
|
||||
::
|
||||
|
||||
# Default value must be a constant expression.
|
||||
|
||||
export var a = [1, 2, 3]
|
||||
|
||||
# Exported arrays can specify type (using the same hints as before).
|
||||
|
||||
export(Array, int) var ints = [1, 2, 3]
|
||||
export(Array, int, "Red", "Green", "Blue") var enums = [2, 1, 0]
|
||||
export(Array, Array, float) var two_dimensional = [[1.0, 2.0], [3.0, 4.0]]
|
||||
|
||||
# You can omit the default value, but then it would be null if not assigned.
|
||||
|
||||
export(Array) var b
|
||||
export(Array, PackedScene) var scenes
|
||||
|
||||
# Arrays with specified types which inherit from resource can be set by
|
||||
# drag-and-dropping multiple files from the FileSystem dock.
|
||||
|
||||
export(Array, Texture) var textures
|
||||
export(Array, PackedScene) var scenes
|
||||
|
||||
# Typed arrays also work, only initialized empty:
|
||||
|
||||
export var vector3s = PoolVector3Array()
|
||||
export var strings = PoolStringArray()
|
||||
|
||||
# Default value can include run-time values, but can't
|
||||
# be exported.
|
||||
|
||||
var c = [a, 2, 3]
|
||||
|
||||
Setting exported variables from a tool script
|
||||
---------------------------------------------
|
||||
|
||||
When changing an exported variable's value from a script in
|
||||
:ref:`doc_gdscript_tool_mode`, the value in the inspector won't be updated
|
||||
automatically. To update it, call
|
||||
:ref:`property_list_changed_notify() <class_Object_method_property_list_changed_notify>`
|
||||
after setting the exported variable's value.
|
||||
|
||||
Advanced exports
|
||||
----------------
|
||||
|
||||
Not every type of export can be provided on the level of the language itself to
|
||||
avoid unnecessary design complexity. The following describes some more or less
|
||||
common exporting features which can be implemented with a low-level API.
|
||||
|
||||
Before reading further, you should get familiar with the way properties are
|
||||
handled and how they can be customized with
|
||||
:ref:`_set() <class_Object_method__get_property_list>`,
|
||||
:ref:`_get() <class_Object_method__get_property_list>`, and
|
||||
:ref:`_get_property_list() <class_Object_method__get_property_list>` methods as
|
||||
described in :ref:`doc_accessing_data_or_logic_from_object`.
|
||||
|
||||
.. seealso:: For binding properties using the above methods in C++, see
|
||||
:ref:`doc_binding_properties_using_set_get_property_list`.
|
||||
|
||||
.. warning:: The script must operate in the ``tool`` mode so the above methods
|
||||
can work from within the editor.
|
||||
|
||||
Adding script categories
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For better visual distinguishing of properties, a special script category can be
|
||||
embedded into the inspector to act as a separator. ``Script Variables`` is one
|
||||
example of a built-in category.
|
||||
|
||||
::
|
||||
|
||||
func _get_property_list():
|
||||
var properties = []
|
||||
properties.append(
|
||||
{
|
||||
name = "Debug",
|
||||
type = TYPE_NIL,
|
||||
usage = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_SCRIPT_VARIABLE
|
||||
}
|
||||
)
|
||||
return properties
|
||||
|
||||
* ``name`` is the name of a category to be added to the inspector;
|
||||
|
||||
* ``PROPERTY_USAGE_CATEGORY`` indicates that the property should be treated as a
|
||||
script category specifically, so the type ``TYPE_NIL`` can be ignored as it
|
||||
won't be actually used for the scripting logic, yet it must be defined anyway.
|
||||
|
||||
Grouping properties
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A list of properties with similar names can be grouped.
|
||||
|
||||
::
|
||||
|
||||
func _get_property_list():
|
||||
var properties = []
|
||||
properties.append({
|
||||
name = "Rotate",
|
||||
type = TYPE_NIL,
|
||||
hint_string = "rotate_",
|
||||
usage = PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SCRIPT_VARIABLE
|
||||
})
|
||||
return properties
|
||||
|
||||
* ``name`` is the name of a group which is going to be displayed as collapsible
|
||||
list of properties;
|
||||
|
||||
* every successive property added after the group property will be collapsed and
|
||||
shortened as determined by the prefix defined via the ``hint_string`` key. For
|
||||
instance, ``rotate_speed`` is going to be shortened to ``speed`` in this case.
|
||||
|
||||
* ``PROPERTY_USAGE_GROUP`` indicates that the property should be treated as a
|
||||
script group specifically, so the type ``TYPE_NIL`` can be ignored as it
|
||||
won't be actually used for the scripting logic, yet it must be defined anyway.
|
||||
280
tutorials/scripting/gdscript/gdscript_format_string.rst
Normal file
@@ -0,0 +1,280 @@
|
||||
.. _doc_gdscript_printf:
|
||||
|
||||
GDScript format strings
|
||||
=======================
|
||||
|
||||
GDScript offers a feature called *format strings*, which allows reusing text
|
||||
templates to succinctly create different but similar strings.
|
||||
|
||||
Format strings are just like normal strings, except they contain certain
|
||||
placeholder character-sequences. These placeholders can then easily be replaced
|
||||
by parameters handed to the format string.
|
||||
|
||||
As an example, with ``%s`` as a placeholder, the format string ``"Hello %s, how
|
||||
are you?`` can easily be changed to ``"Hello World, how are you?"``. Notice
|
||||
the placeholder is in the middle of the string; modifying it without format
|
||||
strings could be cumbersome.
|
||||
|
||||
|
||||
Usage in GDScript
|
||||
-----------------
|
||||
|
||||
Examine this concrete GDScript example:
|
||||
|
||||
::
|
||||
|
||||
# Define a format string with placeholder '%s'
|
||||
var format_string = "We're waiting for %s."
|
||||
|
||||
# Using the '%' operator, the placeholder is replaced with the desired value
|
||||
var actual_string = format_string % "Godot"
|
||||
|
||||
print(actual_string)
|
||||
# Output: "We're waiting for Godot."
|
||||
|
||||
Placeholders always start with a ``%``, but the next character or characters,
|
||||
the *format specifier*, determines how the given value is converted to a
|
||||
string.
|
||||
|
||||
The ``%s`` seen in the example above is the simplest placeholder and works for
|
||||
most use cases: it converts the value by the same method by which an implicit
|
||||
String conversion or ``str()`` would convert it. Strings remain unchanged,
|
||||
Booleans turn into either ``"True"`` or ``"False"``, an integral or real number
|
||||
becomes a decimal, other types usually return their data in a human-readable
|
||||
string.
|
||||
|
||||
There is also another way to format text in GDScript, namely the ``String.format()``
|
||||
method. It replaces all occurrences of a key in the string with the corresponding
|
||||
value. The method can handle arrays or dictionaries for the key/value pairs.
|
||||
|
||||
Arrays can be used as key, index, or mixed style (see below examples). Order only
|
||||
matters when the index or mixed style of Array is used.
|
||||
|
||||
A quick example in GDScript:
|
||||
|
||||
::
|
||||
|
||||
# Define a format string
|
||||
var format_string = "We're waiting for {str}"
|
||||
|
||||
# Using the 'format' method, replace the 'str' placeholder
|
||||
var actual_string = format_string.format({"str": "Godot"})
|
||||
|
||||
print(actual_string)
|
||||
# Output: "We're waiting for Godot"
|
||||
|
||||
There are other `format specifiers`_, but they are only applicable when using
|
||||
the ``%`` operator.
|
||||
|
||||
|
||||
Multiple placeholders
|
||||
---------------------
|
||||
|
||||
Format strings may contain multiple placeholders. In such a case, the values
|
||||
are handed in the form of an array, one value per placeholder (unless using a
|
||||
format specifier with ``*``, see `dynamic padding`_):
|
||||
|
||||
::
|
||||
|
||||
var format_string = "%s was reluctant to learn %s, but now he enjoys it."
|
||||
var actual_string = format_string % ["Estragon", "GDScript"]
|
||||
|
||||
print(actual_string)
|
||||
# Output: "Estragon was reluctant to learn GDScript, but now he enjoys it."
|
||||
|
||||
Note the values are inserted in order. Remember all placeholders must be
|
||||
replaced at once, so there must be an appropriate number of values.
|
||||
|
||||
|
||||
Format specifiers
|
||||
-----------------
|
||||
|
||||
There are format specifiers other than ``s`` that can be used in placeholders.
|
||||
They consist of one or more characters. Some of them work by themselves like
|
||||
``s``, some appear before other characters, some only work with certain
|
||||
values or characters.
|
||||
|
||||
|
||||
Placeholder types
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
One and only one of these must always appear as the last character in a format
|
||||
specifier. Apart from ``s``, these require certain types of parameters.
|
||||
|
||||
+-------+---------------------------------------------------------------------+
|
||||
| ``s`` | **Simple** conversion to String by the same method as implicit |
|
||||
| | String conversion. |
|
||||
+-------+---------------------------------------------------------------------+
|
||||
| ``c`` | A single **Unicode character**. Expects an unsigned 8-bit integer |
|
||||
| | (0-255) for a code point or a single-character string. |
|
||||
+-------+---------------------------------------------------------------------+
|
||||
| ``d`` | A **decimal integral** number. Expects an integral or real number |
|
||||
| | (will be floored). |
|
||||
+-------+---------------------------------------------------------------------+
|
||||
| ``o`` | An **octal integral** number. Expects an integral or real number |
|
||||
| | (will be floored). |
|
||||
+-------+---------------------------------------------------------------------+
|
||||
| ``x`` | A **hexadecimal integral** number with **lower-case** letters. |
|
||||
| | Expects an integral or real number (will be floored). |
|
||||
+-------+---------------------------------------------------------------------+
|
||||
| ``X`` | A **hexadecimal integral** number with **upper-case** letters. |
|
||||
| | Expects an integral or real number (will be floored). |
|
||||
+-------+---------------------------------------------------------------------+
|
||||
| ``f`` | A **decimal real** number. Expects an integral or real number. |
|
||||
+-------+---------------------------------------------------------------------+
|
||||
|
||||
|
||||
Placeholder modifiers
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These characters appear before the above. Some of them work only under certain
|
||||
conditions.
|
||||
|
||||
+---------+-------------------------------------------------------------------+
|
||||
| ``+`` | In number specifiers, **show + sign** if positive. |
|
||||
+---------+-------------------------------------------------------------------+
|
||||
| Integer | Set **padding**. Padded with spaces or with zeroes if integer |
|
||||
| | starts with ``0`` in an integer placeholder. When used after |
|
||||
| | ``.``, see ``.``. |
|
||||
+---------+-------------------------------------------------------------------+
|
||||
| ``.`` | Before ``f``, set **precision** to 0 decimal places. Can be |
|
||||
| | followed up with numbers to change. Padded with zeroes. |
|
||||
+---------+-------------------------------------------------------------------+
|
||||
| ``-`` | **Pad to the right** rather than the left. |
|
||||
+---------+-------------------------------------------------------------------+
|
||||
| ``*`` | **Dynamic padding**, expect additional integral parameter to set |
|
||||
| | padding or precision after ``.``, see `dynamic padding`_. |
|
||||
+---------+-------------------------------------------------------------------+
|
||||
|
||||
|
||||
Padding
|
||||
-------
|
||||
|
||||
The ``.`` (*dot*), ``*`` (*asterisk*), ``-`` (*minus sign*) and digit
|
||||
(``0``-``9``) characters are used for padding. This allows printing several
|
||||
values aligned vertically as if in a column, provided a fixed-width font is
|
||||
used.
|
||||
|
||||
To pad a string to a minimum length, add an integer to the specifier:
|
||||
|
||||
::
|
||||
|
||||
print("%10d" % 12345)
|
||||
# output: " 12345"
|
||||
# 5 leading spaces for a total length of 10
|
||||
|
||||
If the integer starts with ``0``, integral values are padded with zeroes
|
||||
instead of white space:
|
||||
|
||||
::
|
||||
|
||||
print("%010d" % 12345)
|
||||
# output: "0000012345"
|
||||
|
||||
Precision can be specified for real numbers by adding a ``.`` (*dot*) with an
|
||||
integer following it. With no integer after ``.``, a precision of 0 is used,
|
||||
rounding to integral value. The integer to use for padding must appear before
|
||||
the dot.
|
||||
|
||||
::
|
||||
|
||||
# Pad to minimum length of 10, round to 3 decimal places
|
||||
print("%10.3f" % 10000.5555)
|
||||
# Output: " 10000.556"
|
||||
# 1 leading space
|
||||
|
||||
The ``-`` character will cause padding to the right rather than the left,
|
||||
useful for right text alignment:
|
||||
|
||||
::
|
||||
|
||||
print("%-10d" % 12345678)
|
||||
# Output: "12345678 "
|
||||
# 2 trailing spaces
|
||||
|
||||
|
||||
Dynamic padding
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
By using the ``*`` (*asterisk*) character, the padding or precision can be set
|
||||
without modifying the format string. It is used in place of an integer in the
|
||||
format specifier. The values for padding and precision are then passed when
|
||||
formatting:
|
||||
|
||||
::
|
||||
|
||||
var format_string = "%*.*f"
|
||||
# Pad to length of 7, round to 3 decimal places:
|
||||
print(format_string % [7, 3, 8.8888])
|
||||
# Output: " 8.889"
|
||||
# 2 leading spaces
|
||||
|
||||
It is still possible to pad with zeroes in integer placeholders by adding ``0``
|
||||
before ``*``:
|
||||
|
||||
::
|
||||
|
||||
print("%0*d" % [2, 3])
|
||||
# Output: "03"
|
||||
|
||||
|
||||
Escape sequence
|
||||
---------------
|
||||
|
||||
To insert a literal ``%`` character into a format string, it must be escaped to
|
||||
avoid reading it as a placeholder. This is done by doubling the character:
|
||||
|
||||
::
|
||||
|
||||
var health = 56
|
||||
print("Remaining health: %d%%" % health)
|
||||
# Output: "Remaining health: 56%"
|
||||
|
||||
|
||||
Format method examples
|
||||
----------------------
|
||||
|
||||
The following are some examples of how to use the various invocations of the
|
||||
``String.format`` method.
|
||||
|
||||
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| **Type** | **Style** | **Example** | **Result** |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| Dictionary | key | ``"Hi, {name} v{version}!".format({"name":"Godette", "version":"3.0"})`` | Hi, Godette v3.0! |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| Dictionary | index | ``"Hi, {0} v{1}!".format({"0":"Godette", "1":"3.0"})`` | Hi, Godette v3.0! |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| Dictionary | mix | ``"Hi, {0} v{version}!".format({"0":"Godette", "version":"3.0"})`` | Hi, Godette v3.0! |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| Array | key | ``"Hi, {name} v{version}!".format([["version","3.0"], ["name","Godette"]])`` | Hi, Godette v3.0! |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| Array | index | ``"Hi, {0} v{1}!".format(["Godette","3.0"])`` | Hi, Godette v3.0! |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| Array | mix | ``"Hi, {name} v{0}!".format([3.0, ["name","Godette"]])`` | Hi, Godette v3.0! |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
| Array | no index | ``"Hi, {} v{}!".format(["Godette", 3.0], "{}")`` | Hi, Godette v3.0! |
|
||||
+------------+-----------+------------------------------------------------------------------------------+-------------------+
|
||||
|
||||
Placeholders can also be customized when using ``String.format``, here's some
|
||||
examples of that functionality.
|
||||
|
||||
|
||||
+-----------------+------------------------------------------------------+------------------+
|
||||
| **Type** | **Example** | **Result** |
|
||||
+-----------------+------------------------------------------------------+------------------+
|
||||
| Infix (default) | ``"Hi, {0} v{1}".format(["Godette", "3.0"], "{_}")`` | Hi, Godette v3.0 |
|
||||
+-----------------+------------------------------------------------------+------------------+
|
||||
| Postfix | ``"Hi, 0% v1%".format(["Godette", "3.0"], "_%")`` | Hi, Godette v3.0 |
|
||||
+-----------------+------------------------------------------------------+------------------+
|
||||
| Prefix | ``"Hi, %0 v%1".format(["Godette", "3.0"], "%_")`` | Hi, Godette v3.0 |
|
||||
+-----------------+------------------------------------------------------+------------------+
|
||||
|
||||
Combining both the ``String.format`` method and the ``%`` operator could be useful, as
|
||||
``String.format`` does not have a way to manipulate the representation of numbers.
|
||||
|
||||
+---------------------------------------------------------------------------+-------------------+
|
||||
| **Example** | **Result** |
|
||||
+---------------------------------------------------------------------------+-------------------+
|
||||
| ``"Hi, {0} v{version}".format({0:"Godette", "version":"%0.2f" % 3.114})`` | Hi, Godette v3.11 |
|
||||
+---------------------------------------------------------------------------+-------------------+
|
||||
839
tutorials/scripting/gdscript/gdscript_styleguide.rst
Normal file
@@ -0,0 +1,839 @@
|
||||
.. _doc_gdscript_styleguide:
|
||||
|
||||
GDScript style guide
|
||||
====================
|
||||
|
||||
This style guide lists conventions to write elegant GDScript. The goal is to
|
||||
encourage writing clean, readable code and promote consistency across projects,
|
||||
discussions, and tutorials. Hopefully, this will also support the development of
|
||||
auto-formatting tools.
|
||||
|
||||
Since GDScript is close to Python, this guide is inspired by Python's
|
||||
`PEP 8 <https://www.python.org/dev/peps/pep-0008/>`__ programming
|
||||
style guide.
|
||||
|
||||
Style guides aren't meant as hard rulebooks. At times, you may not be able to
|
||||
apply some of the guidelines below. When that happens, use your best judgment,
|
||||
and ask fellow developers for insights.
|
||||
|
||||
In general, keeping your code consistent in your projects and within your team is
|
||||
more important than following this guide to a tee.
|
||||
|
||||
.. note:: Godot's built-in script editor uses a lot of these conventions
|
||||
by default. Let it help you.
|
||||
|
||||
Here is a complete class example based on these guidelines:
|
||||
|
||||
::
|
||||
|
||||
class_name StateMachine
|
||||
extends Node
|
||||
# Hierarchical State machine for the player.
|
||||
# Initializes states and delegates engine callbacks
|
||||
# (_physics_process, _unhandled_input) to the state.
|
||||
|
||||
|
||||
signal state_changed(previous, new)
|
||||
|
||||
export var initial_state = NodePath()
|
||||
var is_active = true setget set_is_active
|
||||
|
||||
onready var _state = get_node(initial_state) setget set_state
|
||||
onready var _state_name = _state.name
|
||||
|
||||
|
||||
func _init():
|
||||
add_to_group("state_machine")
|
||||
|
||||
|
||||
func _ready():
|
||||
connect("state_changed", self, "_on_state_changed")
|
||||
_state.enter()
|
||||
|
||||
|
||||
func _unhandled_input(event):
|
||||
_state.unhandled_input(event)
|
||||
|
||||
|
||||
func _physics_process(delta):
|
||||
_state.physics_process(delta)
|
||||
|
||||
|
||||
func transition_to(target_state_path, msg={}):
|
||||
if not has_node(target_state_path):
|
||||
return
|
||||
|
||||
var target_state = get_node(target_state_path)
|
||||
assert(target_state.is_composite == false)
|
||||
|
||||
_state.exit()
|
||||
self._state = target_state
|
||||
_state.enter(msg)
|
||||
Events.emit_signal("player_state_changed", _state.name)
|
||||
|
||||
|
||||
func set_is_active(value):
|
||||
is_active = value
|
||||
set_physics_process(value)
|
||||
set_process_unhandled_input(value)
|
||||
set_block_signals(not value)
|
||||
|
||||
|
||||
func set_state(value):
|
||||
_state = value
|
||||
_state_name = _state.name
|
||||
|
||||
|
||||
func _on_state_changed(previous, new):
|
||||
print("state changed")
|
||||
emit_signal("state_changed")
|
||||
|
||||
.. _formatting:
|
||||
|
||||
Formatting
|
||||
----------
|
||||
|
||||
Encoding and special characters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Use line feed (**LF**) characters to break lines, not CRLF or CR. *(editor default)*
|
||||
* Use one line feed character at the end of each file. *(editor default)*
|
||||
* Use **UTF-8** encoding without a `byte order mark <https://en.wikipedia.org/wiki/Byte_order_mark>`_. *(editor default)*
|
||||
* Use **Tabs** instead of spaces for indentation. *(editor default)*
|
||||
|
||||
Indentation
|
||||
~~~~~~~~~~~
|
||||
|
||||
Each indent level should be one greater than the block containing it.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
for i in range(10):
|
||||
print("hello")
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
for i in range(10):
|
||||
print("hello")
|
||||
|
||||
for i in range(10):
|
||||
print("hello")
|
||||
|
||||
Use 2 indent levels to distinguish continuation lines from
|
||||
regular code blocks.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
effect.interpolate_property(sprite, "transform/scale",
|
||||
sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
|
||||
Tween.TRANS_QUAD, Tween.EASE_OUT)
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
effect.interpolate_property(sprite, "transform/scale",
|
||||
sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
|
||||
Tween.TRANS_QUAD, Tween.EASE_OUT)
|
||||
|
||||
Exceptions to this rule are arrays, dictionaries, and enums. Use a single
|
||||
indentation level to distinguish continuation lines:
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
var party = [
|
||||
"Godot",
|
||||
"Godette",
|
||||
"Steve",
|
||||
]
|
||||
|
||||
var character_dict = {
|
||||
"Name": "Bob",
|
||||
"Age": 27,
|
||||
"Job": "Mechanic",
|
||||
}
|
||||
|
||||
enum Tiles {
|
||||
TILE_BRICK,
|
||||
TILE_FLOOR,
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT,
|
||||
}
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
var party = [
|
||||
"Godot",
|
||||
"Godette",
|
||||
"Steve",
|
||||
]
|
||||
|
||||
var character_dict = {
|
||||
"Name": "Bob",
|
||||
"Age": 27,
|
||||
"Job": "Mechanic",
|
||||
}
|
||||
|
||||
enum Tiles {
|
||||
TILE_BRICK,
|
||||
TILE_FLOOR,
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT,
|
||||
}
|
||||
|
||||
Trailing comma
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Use a trailing comma on the last line in arrays, dictionaries, and enums. This
|
||||
results in easier refactoring and better diffs in version control as the last
|
||||
line doesn't need to be modified when adding new elements.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
enum Tiles {
|
||||
TILE_BRICK,
|
||||
TILE_FLOOR,
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT,
|
||||
}
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
enum Tiles {
|
||||
TILE_BRICK,
|
||||
TILE_FLOOR,
|
||||
TILE_SPIKE,
|
||||
TILE_TELEPORT
|
||||
}
|
||||
|
||||
Trailing commas are unnecessary in single-line lists, so don't add them in this case.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}
|
||||
|
||||
Blank lines
|
||||
~~~~~~~~~~~
|
||||
|
||||
Surround functions and class definitions with two blank lines:
|
||||
|
||||
::
|
||||
|
||||
func heal(amount):
|
||||
health += amount
|
||||
health = min(health, max_health)
|
||||
emit_signal("health_changed", health)
|
||||
|
||||
|
||||
func take_damage(amount, effect=null):
|
||||
health -= amount
|
||||
health = max(0, health)
|
||||
emit_signal("health_changed", health)
|
||||
|
||||
Use one blank line inside functions to separate logical sections.
|
||||
|
||||
Line length
|
||||
~~~~~~~~~~~
|
||||
|
||||
Keep individual lines of code under 100 characters.
|
||||
|
||||
If you can, try to keep lines under 80 characters. This helps to read the code
|
||||
on small displays and with two scripts opened side-by-side in an external text
|
||||
editor. For example, when looking at a differential revision.
|
||||
|
||||
One statement per line
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Never combine multiple statements on a single line. No, C programmers,
|
||||
not even with a single line conditional statement.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
if position.x > width:
|
||||
position.x = 0
|
||||
|
||||
if flag:
|
||||
print("flagged")
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
if position.x > width: position.x = 0
|
||||
|
||||
if flag: print("flagged")
|
||||
|
||||
The only exception to that rule is the ternary operator:
|
||||
|
||||
::
|
||||
|
||||
next_state = "fall" if not is_on_floor() else "idle"
|
||||
|
||||
Format multiline statements for readability
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When you have particularly long ``if`` statements or nested ternary expressions,
|
||||
wrapping them over multiple lines improves readability. Since continuation lines
|
||||
are still part of the same expression, 2 indent levels should be used instead of one.
|
||||
|
||||
GDScript allows wrapping statements using multiple lines using parentheses or
|
||||
backslashes. Parentheses are favored in this style guide since they make for
|
||||
easier refactoring. With backslashes, you have to ensure that the last line
|
||||
never contains a backslash at the end. With parentheses, you don't have to
|
||||
worry about the last line having a backslash at the end.
|
||||
|
||||
When wrapping a conditional expression over multiple lines, the ``and``/``or``
|
||||
keywords should be placed at the beginning of the line continuation, not at the
|
||||
end of the previous line.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
var angle_degrees = 135
|
||||
var quadrant = (
|
||||
"northeast" if angle_degrees <= 90
|
||||
else "southeast" if angle_degrees <= 180
|
||||
else "southwest" if angle_degrees <= 270
|
||||
else "northwest"
|
||||
)
|
||||
|
||||
var position = Vector2(250, 350)
|
||||
if (
|
||||
position.x > 200 and position.x < 400
|
||||
and position.y > 300 and position.y < 400
|
||||
):
|
||||
pass
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
var angle_degrees = 135
|
||||
var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest"
|
||||
|
||||
var position = Vector2(250, 350)
|
||||
if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400:
|
||||
pass
|
||||
|
||||
Avoid unnecessary parentheses
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Avoid parentheses in expressions and conditional statements. Unless
|
||||
necessary for order of operations or wrapping over multiple lines,
|
||||
they only reduce readability.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
if is_colliding():
|
||||
queue_free()
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
if (is_colliding()):
|
||||
queue_free()
|
||||
|
||||
Boolean operators
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Prefer the plain English versions of boolean operators, as they are the most accessible:
|
||||
|
||||
- Use ``and`` instead of ``&&``.
|
||||
- Use ``or`` instead of ``||``.
|
||||
|
||||
You may also use parentheses around boolean operators to clear any ambiguity.
|
||||
This can make long expressions easier to read.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
if (foo and bar) or baz:
|
||||
print("condition is true")
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
if foo && bar || baz:
|
||||
print("condition is true")
|
||||
|
||||
Comment spacing
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Regular comments should start with a space, but not code that you comment out.
|
||||
This helps differentiate text comments from disabled code.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
# This is a comment.
|
||||
#print("This is disabled code")
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
#This is a comment.
|
||||
# print("This is disabled code")
|
||||
|
||||
.. note::
|
||||
|
||||
In the script editor, to toggle the selected code commented, press
|
||||
:kbd:`Ctrl + K`. This feature adds a single # sign at the start
|
||||
of the selected lines.
|
||||
|
||||
Whitespace
|
||||
~~~~~~~~~~
|
||||
|
||||
Always use one space around operators and after commas. Also, avoid extra spaces
|
||||
in dictionary references and function calls.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
position.x = 5
|
||||
position.y = target_position.y + 10
|
||||
dict["key"] = 5
|
||||
my_array = [4, 5, 6]
|
||||
print("foo")
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
position.x=5
|
||||
position.y = mpos.y+10
|
||||
dict ["key"] = 5
|
||||
myarray = [4,5,6]
|
||||
print ("foo")
|
||||
|
||||
Don't use spaces to align expressions vertically:
|
||||
|
||||
::
|
||||
|
||||
x = 100
|
||||
y = 100
|
||||
velocity = 500
|
||||
|
||||
Quotes
|
||||
~~~~~~
|
||||
|
||||
Use double quotes unless single quotes make it possible to escape fewer
|
||||
characters in a given string. See the examples below:
|
||||
|
||||
::
|
||||
|
||||
# Normal string.
|
||||
print("hello world")
|
||||
|
||||
# Use double quotes as usual to avoid escapes.
|
||||
print("hello 'world'")
|
||||
|
||||
# Use single quotes as an exception to the rule to avoid escapes.
|
||||
print('hello "world"')
|
||||
|
||||
# Both quote styles would require 2 escapes; prefer double quotes if it's a tie.
|
||||
print("'hello' \"world\"")
|
||||
|
||||
Numbers
|
||||
~~~~~~~
|
||||
|
||||
Don't omit the leading or trailing zero in floating-point numbers. Otherwise,
|
||||
this makes them less readable and harder to distinguish from integers at a
|
||||
glance.
|
||||
|
||||
**Good**::
|
||||
|
||||
var float_number = 0.234
|
||||
var other_float_number = 13.0
|
||||
|
||||
**Bad**::
|
||||
|
||||
var float_number = .234
|
||||
var other_float_number = 13.
|
||||
|
||||
Use lowercase for letters in hexadecimal numbers, as their lower height makes
|
||||
the number easier to read.
|
||||
|
||||
**Good**::
|
||||
|
||||
var hex_number = 0xfb8c0b
|
||||
|
||||
**Bad**::
|
||||
|
||||
var hex_number = 0xFB8C0B
|
||||
|
||||
Take advantage of GDScript's underscores in literals to make large numbers more
|
||||
readable.
|
||||
|
||||
**Good**::
|
||||
|
||||
var large_number = 1_234_567_890
|
||||
var large_hex_number = 0xffff_f8f8_0000
|
||||
var large_bin_number = 0b1101_0010_1010
|
||||
# Numbers lower than 1000000 generally don't need separators.
|
||||
var small_number = 12345
|
||||
|
||||
**Bad**::
|
||||
|
||||
var large_number = 1234567890
|
||||
var large_hex_number = 0xfffff8f80000
|
||||
var large_bin_number = 0b110100101010
|
||||
# Numbers lower than 1000000 generally don't need separators.
|
||||
var small_number = 12_345
|
||||
|
||||
.. _naming_conventions:
|
||||
|
||||
Naming conventions
|
||||
------------------
|
||||
|
||||
These naming conventions follow the Godot Engine style. Breaking these will make
|
||||
your code clash with the built-in naming conventions, leading to inconsistent
|
||||
code.
|
||||
|
||||
File names
|
||||
~~~~~~~~~~
|
||||
|
||||
Use snake_case for file names. For named classes, convert the PascalCase class
|
||||
name to snake_case::
|
||||
|
||||
# This file should be saved as `weapon.gd`.
|
||||
class_name Weapon
|
||||
extends Node
|
||||
|
||||
::
|
||||
|
||||
# This file should be saved as `yaml_parser.gd`.
|
||||
class_name YAMLParser
|
||||
extends Object
|
||||
|
||||
This is consistent with how C++ files are named in Godot's source code. This
|
||||
also avoids case sensitivity issues that can crop up when exporting a project
|
||||
from Windows to other platforms.
|
||||
|
||||
Classes and nodes
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Use PascalCase for class and node names:
|
||||
|
||||
::
|
||||
|
||||
extends KinematicBody
|
||||
|
||||
Also use PascalCase when loading a class into a constant or a variable:
|
||||
|
||||
::
|
||||
|
||||
const Weapon = preload("res://weapon.gd")
|
||||
|
||||
Functions and variables
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Use snake\_case to name functions and variables:
|
||||
|
||||
::
|
||||
|
||||
var particle_effect
|
||||
func load_level():
|
||||
|
||||
Prepend a single underscore (\_) to virtual methods functions the user must
|
||||
override, private functions, and private variables:
|
||||
|
||||
::
|
||||
|
||||
var _counter = 0
|
||||
func _recalculate_path():
|
||||
|
||||
Signals
|
||||
~~~~~~~
|
||||
|
||||
Use the past tense to name signals:
|
||||
|
||||
::
|
||||
|
||||
signal door_opened
|
||||
signal score_changed
|
||||
|
||||
Constants and enums
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Write constants with CONSTANT\_CASE, that is to say in all caps with an
|
||||
underscore (\_) to separate words:
|
||||
|
||||
::
|
||||
|
||||
const MAX_SPEED = 200
|
||||
|
||||
Use PascalCase for enum *names* and CONSTANT\_CASE for their members, as they
|
||||
are constants:
|
||||
|
||||
::
|
||||
|
||||
enum Element {
|
||||
EARTH,
|
||||
WATER,
|
||||
AIR,
|
||||
FIRE,
|
||||
}
|
||||
|
||||
|
||||
Code order
|
||||
----------
|
||||
|
||||
This first section focuses on code order. For formatting, see
|
||||
:ref:`formatting`. For naming conventions, see :ref:`naming_conventions`.
|
||||
|
||||
We suggest to organize GDScript code this way:
|
||||
|
||||
::
|
||||
|
||||
01. tool
|
||||
02. class_name
|
||||
03. extends
|
||||
04. # docstring
|
||||
|
||||
05. signals
|
||||
06. enums
|
||||
07. constants
|
||||
08. exported variables
|
||||
09. public variables
|
||||
10. private variables
|
||||
11. onready variables
|
||||
|
||||
12. optional built-in virtual _init method
|
||||
13. built-in virtual _ready method
|
||||
14. remaining built-in virtual methods
|
||||
15. public methods
|
||||
16. private methods
|
||||
|
||||
We optimized the order to make it easy to read the code from top to bottom, to
|
||||
help developers reading the code for the first time understand how it works, and
|
||||
to avoid errors linked to the order of variable declarations.
|
||||
|
||||
This code order follows four rules of thumb:
|
||||
|
||||
1. Properties and signals come first, followed by methods.
|
||||
2. Public comes before private.
|
||||
3. Virtual callbacks come before the class's interface.
|
||||
4. The object's construction and initialization functions, ``_init`` and
|
||||
``_ready``, come before functions that modify the object at runtime.
|
||||
|
||||
|
||||
Class declaration
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
If the code is meant to run in the editor, place the ``tool`` keyword on the
|
||||
first line of the script.
|
||||
|
||||
Follow with the `class_name` if necessary. You can turn a GDScript file into a
|
||||
global type in your project using this feature. For more information, see
|
||||
:ref:`doc_gdscript`.
|
||||
|
||||
Then, add the `extends` keyword if the class extends a built-in type.
|
||||
|
||||
Following that, you should have the class's optional docstring as comments. You
|
||||
can use that to explain the role of your class to your teammates, how it works,
|
||||
and how other developers should use it, for example.
|
||||
|
||||
::
|
||||
|
||||
class_name MyNode
|
||||
extends Node
|
||||
# A brief description of the class's role and functionality.
|
||||
# Longer description.
|
||||
|
||||
Signals and properties
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Write signal declarations, followed by properties, that is to say, member
|
||||
variables, after the docstring.
|
||||
|
||||
Enums should come after signals, as you can use them as export hints for other
|
||||
properties.
|
||||
|
||||
Then, write constants, exported variables, public, private, and onready
|
||||
variables, in that order.
|
||||
|
||||
::
|
||||
|
||||
signal spawn_player(position)
|
||||
|
||||
enum Jobs {KNIGHT, WIZARD, ROGUE, HEALER, SHAMAN}
|
||||
|
||||
const MAX_LIVES = 3
|
||||
|
||||
export(Jobs) var job = Jobs.KNIGHT
|
||||
export var max_health = 50
|
||||
export var attack = 5
|
||||
|
||||
var health = max_health setget set_health
|
||||
|
||||
var _speed = 300.0
|
||||
|
||||
onready var sword = get_node("Sword")
|
||||
onready var gun = get_node("Gun")
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The GDScript compiler evaluates onready variables right before the ``_ready``
|
||||
callback. You can use that to cache node dependencies, that is to say, to get
|
||||
child nodes in the scene that your class relies on. This is what the example
|
||||
above shows.
|
||||
|
||||
Member variables
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Don't declare member variables if they are only used locally in a method, as it
|
||||
makes the code more difficult to follow. Instead, declare them as local
|
||||
variables in the method's body.
|
||||
|
||||
Local variables
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Declare local variables as close as possible to their first use. This makes it
|
||||
easier to follow the code, without having to scroll too much to find where the
|
||||
variable was declared.
|
||||
|
||||
Methods and static functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
After the class's properties come the methods.
|
||||
|
||||
Start with the ``_init()`` callback method, that the engine will call upon
|
||||
creating the object in memory. Follow with the ``_ready()`` callback, that Godot
|
||||
calls when it adds a node to the scene tree.
|
||||
|
||||
These functions should come first because they show how the object is
|
||||
initialized.
|
||||
|
||||
Other built-in virtual callbacks, like ``_unhandled_input()`` and
|
||||
``_physics_process``, should come next. These control the object's main loop and
|
||||
interactions with the game engine.
|
||||
|
||||
The rest of the class's interface, public and private methods, come after that,
|
||||
in that order.
|
||||
|
||||
::
|
||||
|
||||
func _init():
|
||||
add_to_group("state_machine")
|
||||
|
||||
|
||||
func _ready():
|
||||
connect("state_changed", self, "_on_state_changed")
|
||||
_state.enter()
|
||||
|
||||
|
||||
func _unhandled_input(event):
|
||||
_state.unhandled_input(event)
|
||||
|
||||
|
||||
func transition_to(target_state_path, msg={}):
|
||||
if not has_node(target_state_path):
|
||||
return
|
||||
|
||||
var target_state = get_node(target_state_path)
|
||||
assert(target_state.is_composite == false)
|
||||
|
||||
_state.exit()
|
||||
self._state = target_state
|
||||
_state.enter(msg)
|
||||
Events.emit_signal("player_state_changed", _state.name)
|
||||
|
||||
|
||||
func _on_state_changed(previous, new):
|
||||
print("state changed")
|
||||
emit_signal("state_changed")
|
||||
|
||||
|
||||
Static typing
|
||||
-------------
|
||||
|
||||
Since Godot 3.1, GDScript supports :ref:`optional static typing<doc_gdscript_static_typing>`.
|
||||
|
||||
Declared types
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
To declare a variable's type, use ``<variable>: <type>``:
|
||||
|
||||
::
|
||||
|
||||
var health: int = 0
|
||||
|
||||
To declare the return type of a function, use ``-> <type>``:
|
||||
|
||||
::
|
||||
|
||||
func heal(amount: int) -> void:
|
||||
|
||||
Inferred types
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
In most cases you can let the compiler infer the type, using ``:=``:
|
||||
|
||||
::
|
||||
|
||||
var health := 0 # The compiler will use the int type.
|
||||
|
||||
However, in a few cases when context is missing, the compiler falls back to
|
||||
the function's return type. For example, ``get_node()`` cannot infer a type
|
||||
unless the scene or file of the node is loaded in memory. In this case, you
|
||||
should set the type explicitly.
|
||||
|
||||
**Good**:
|
||||
|
||||
::
|
||||
|
||||
onready var health_bar: ProgressBar = get_node("UI/LifeBar")
|
||||
|
||||
Alternatively, you can use the ``as`` keyword to cast the return type, and
|
||||
that type will be used to infer the type of the var.
|
||||
|
||||
.. rst-class:: code-example-good
|
||||
|
||||
::
|
||||
|
||||
onready var health_bar := get_node("UI/LifeBar") as ProgressBar
|
||||
# health_bar will be typed as ProgressBar
|
||||
|
||||
This option is also considered more :ref:`type-safe<doc_gdscript_static_typing_safe_lines>` than the first.
|
||||
|
||||
**Bad**:
|
||||
|
||||
::
|
||||
|
||||
# The compiler can't infer the exact type and will use Node
|
||||
# instead of ProgressBar.
|
||||
onready var health_bar := get_node("UI/LifeBar")
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 42 KiB |
19
tutorials/scripting/gdscript/index.rst
Normal file
@@ -0,0 +1,19 @@
|
||||
GDScript
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:name: toc-learn-scripting-gdscript
|
||||
|
||||
gdscript_basics
|
||||
gdscript_advanced
|
||||
gdscript_exports
|
||||
gdscript_styleguide
|
||||
static_typing
|
||||
warning_system
|
||||
gdscript_format_string
|
||||
|
||||
.. seealso::
|
||||
|
||||
See :ref:`doc_gdscript_grammar` if you are interested in writing a third-party
|
||||
tool that interacts with GDScript, such as a linter or formatter.
|
||||
386
tutorials/scripting/gdscript/static_typing.rst
Normal file
@@ -0,0 +1,386 @@
|
||||
.. _doc_gdscript_static_typing:
|
||||
|
||||
Static typing in GDScript
|
||||
=========================
|
||||
|
||||
In this guide, you will learn:
|
||||
|
||||
- **How to use types in GDScript**
|
||||
- That **static types can help you avoid bugs**
|
||||
|
||||
Where and how you use this new language feature is entirely up to you:
|
||||
you can use it only in some sensitive GDScript files, use it everywhere,
|
||||
or write code like you always did!
|
||||
|
||||
Static types can be used on variables, constants, functions, parameters,
|
||||
and return types.
|
||||
|
||||
.. note::
|
||||
|
||||
Typed GDScript is available since Godot 3.1.
|
||||
|
||||
A brief look at static typing
|
||||
-----------------------------
|
||||
|
||||
With typed GDScript, Godot can detect even more errors as you write
|
||||
code! It gives you and your teammates more information as you're
|
||||
working, as the arguments' types show up when you call a method.
|
||||
|
||||
Imagine you're programming an inventory system. You code an ``Item``
|
||||
node, then an ``Inventory``. To add items to the inventory, the people
|
||||
who work with your code should always pass an ``Item`` to the
|
||||
``Inventory.add`` method. With types, you can enforce this:
|
||||
|
||||
::
|
||||
|
||||
# In 'Item.gd'.
|
||||
class_name Item
|
||||
# In 'Inventory.gd'.
|
||||
class_name Inventory
|
||||
|
||||
|
||||
func add(reference: Item, amount: int = 1):
|
||||
var item = find_item(reference)
|
||||
if not item:
|
||||
item = _instance_item_from_db(reference)
|
||||
|
||||
item.amount += amount
|
||||
|
||||
Another significant advantage of typed GDScript is the new **warning
|
||||
system**. From version 3.1, Godot gives you warnings about your code as
|
||||
you write it: the engine identifies sections of your code that may lead
|
||||
to issues at runtime, but lets you decide whether or not you want to
|
||||
leave the code as it is. More on that in a moment.
|
||||
|
||||
Static types also give you better code completion options. Below, you
|
||||
can see the difference between a dynamic and a static typed completion
|
||||
options for a class called ``PlayerController``.
|
||||
|
||||
You've probably stored a node in a variable before, and typed a dot to
|
||||
be left with no autocomplete suggestions:
|
||||
|
||||
.. figure:: img/typed_gdscript_code_completion_dynamic.png
|
||||
:alt: code completion options for dynamic
|
||||
|
||||
This is due to dynamic code. Godot cannot know what node or value type
|
||||
you're passing to the function. If you write the type explicitly
|
||||
however, you will get all public methods and variables from the node:
|
||||
|
||||
.. figure:: img/typed_gdscript_code_completion_typed.png
|
||||
:alt: code completion options for typed
|
||||
|
||||
In the future, typed GDScript will also increase code performance:
|
||||
Just-In-Time compilation and other compiler improvements are already
|
||||
on the roadmap!
|
||||
|
||||
Overall, typed programming gives you a more structured experience. It
|
||||
helps prevent errors and improves the self-documenting aspect of your
|
||||
scripts. This is especially helpful when you're working in a team or on
|
||||
a long-term project: studies have shown that developers spend most of
|
||||
their time reading other people's code, or scripts they wrote in the
|
||||
past and forgot about. The clearer and the more structured the code, the
|
||||
faster it is to understand, the faster you can move forward.
|
||||
|
||||
How to use static typing
|
||||
------------------------
|
||||
|
||||
To define the type of a variable or a constant, write a colon after the
|
||||
variable's name, followed by its type. E.g. ``var health: int``. This
|
||||
forces the variable's type to always stay the same:
|
||||
|
||||
::
|
||||
|
||||
var damage: float = 10.5
|
||||
const MOVE_SPEED: float = 50.0
|
||||
|
||||
Godot will try to infer types if you write a colon, but you omit the
|
||||
type:
|
||||
|
||||
::
|
||||
|
||||
var life_points := 4
|
||||
var damage := 10.5
|
||||
var motion := Vector2()
|
||||
|
||||
Currently you can use three types of… types:
|
||||
|
||||
1. :ref:`Built-in <doc_gdscript_builtin_types>`
|
||||
2. Core classes and nodes (``Object``, ``Node``, ``Area2D``,
|
||||
``Camera2D``, etc.)
|
||||
3. Your own, custom classes. Look at the new :ref:`class_name <doc_scripting_continued_class_name>`
|
||||
feature to register types in the editor.
|
||||
|
||||
.. note::
|
||||
|
||||
You don't need to write type hints for constants, as Godot sets it automatically from the assigned value. But you can still do so to make the intent of your code clearer.
|
||||
|
||||
Custom variable types
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can use any class, including your custom classes, as types. There
|
||||
are two ways to use them in scripts. The first method is to preload the
|
||||
script you want to use as a type in a constant:
|
||||
|
||||
::
|
||||
|
||||
const Rifle = preload("res://player/weapons/Rifle.gd")
|
||||
var my_rifle: Rifle
|
||||
|
||||
The second method is to use the ``class_name`` keyword when you create.
|
||||
For the example above, your Rifle.gd would look like this:
|
||||
|
||||
::
|
||||
|
||||
extends Node2D
|
||||
class_name Rifle
|
||||
|
||||
If you use ``class_name``, Godot registers the Rifle type globally in
|
||||
the editor, and you can use it anywhere, without having to preload it
|
||||
into a constant:
|
||||
|
||||
::
|
||||
|
||||
var my_rifle: Rifle
|
||||
|
||||
Variable casting
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Type casting is a key concept in typed languages.
|
||||
Casting is the conversion of a value from one type to another.
|
||||
|
||||
Imagine an Enemy in your game, that ``extends Area2D``. You want it to
|
||||
collide with the Player, a ``KinematicBody2D`` with a script called
|
||||
``PlayerController`` attached to it. You use the ``on_body_entered``
|
||||
signal to detect the collision. With typed code, the body you detect is
|
||||
going to be a generic ``PhysicsBody2D``, and not your
|
||||
``PlayerController`` on the ``_on_body_entered`` callback.
|
||||
|
||||
You can check if this ``PhysicsBody2D`` is your Player with the ``as``
|
||||
casting keyword, and using the colon ``:`` again to force the variable
|
||||
to use this type. This forces the variable to stick to the
|
||||
``PlayerController`` type:
|
||||
|
||||
::
|
||||
|
||||
func _on_body_entered(body: PhysicsBody2D) -> void:
|
||||
var player := body as PlayerController
|
||||
if not player:
|
||||
return
|
||||
|
||||
player.damage()
|
||||
|
||||
As we're dealing with a custom type, if the ``body`` doesn't extend
|
||||
``PlayerController``, the ``player``\ variable will be set to ``null``.
|
||||
We can use this to check if the body is the player or not. We will also
|
||||
get full autocompletion on the player variable thanks to that cast.
|
||||
|
||||
.. note::
|
||||
|
||||
If you try to cast with a built-in type and it fails, Godot will throw an error.
|
||||
|
||||
.. _doc_gdscript_static_typing_safe_lines:
|
||||
|
||||
Safe lines
|
||||
^^^^^^^^^^
|
||||
|
||||
You can also use casting to ensure safe lines. Safe lines are a new
|
||||
tool in Godot 3.1 to tell you when ambiguous lines of code are
|
||||
type-safe. As you can mix and match typed and dynamic code, at times,
|
||||
Godot doesn't have enough information to know if an instruction will trigger
|
||||
an error or not at runtime.
|
||||
|
||||
This happens when you get a child node. Let's take a timer for example:
|
||||
with dynamic code, you can get the node with ``$Timer``. GDScript
|
||||
supports `duck-typing <https://stackoverflow.com/a/4205163/8125343>`__,
|
||||
so even if your timer is of type ``Timer``, it is also a ``Node`` and an
|
||||
``Object``, two classes it extends. With dynamic GDScript, you also
|
||||
don't care about the node's type as long as it has the methods you need
|
||||
to call.
|
||||
|
||||
You can use casting to tell Godot the type you expect when you get a
|
||||
node: ``($Timer as Timer)``, ``($Player as KinematicBody2D)``, etc.
|
||||
Godot will ensure the type works and if so, the line number will turn
|
||||
green at the left of the script editor.
|
||||
|
||||
.. figure:: img/typed_gdscript_safe_unsafe_line.png
|
||||
:alt: Unsafe vs Safe Line
|
||||
|
||||
Unsafe line (line 7) vs Safe Lines (line 6 and 8)
|
||||
|
||||
.. note::
|
||||
|
||||
You can turn off safe lines or change their color in the editor settings.
|
||||
|
||||
Define the return type of a function with the arrow ->
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To define the return type of a function, write a dash and a right angle
|
||||
bracket ``->`` after its declaration, followed by the return type:
|
||||
|
||||
::
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
|
||||
The type ``void`` means the function does not return anything. You can
|
||||
use any type, as with variables:
|
||||
|
||||
::
|
||||
|
||||
func hit(damage: float) -> bool:
|
||||
health_points -= damage
|
||||
return health_points <= 0
|
||||
|
||||
You can also use your own nodes as return types:
|
||||
|
||||
::
|
||||
|
||||
# Inventory.gd
|
||||
|
||||
# Adds an item to the inventory and returns it.
|
||||
func add(reference: Item, amount: int) -> Item:
|
||||
var item: Item = find_item(reference)
|
||||
if not item:
|
||||
item = ItemDatabase.get_instance(reference)
|
||||
|
||||
item.amount += amount
|
||||
return item
|
||||
|
||||
Typed or dynamic: stick to one style
|
||||
------------------------------------
|
||||
|
||||
Typed GDScript and dynamic GDScript can coexist in the same project. But
|
||||
it's recommended to stick to either style for consistency in your codebase,
|
||||
and for your peers. It's easier for everyone to work together if you
|
||||
follow the same guidelines, and faster to read and understand other
|
||||
people's code.
|
||||
|
||||
Typed code takes a little more writing, but you get the benefits we
|
||||
discussed above. Here's an example of the same, empty script, in a
|
||||
dynamic style:
|
||||
|
||||
::
|
||||
|
||||
extends Node
|
||||
|
||||
|
||||
func _ready():
|
||||
pass
|
||||
|
||||
|
||||
func _process(delta):
|
||||
pass
|
||||
|
||||
And with static typing:
|
||||
|
||||
::
|
||||
|
||||
extends Node
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
|
||||
As you can see, you can also use types with the engine's virtual
|
||||
methods. Signal callbacks, like any methods, can also use types. Here's
|
||||
a ``body_entered`` signal in a dynamic style:
|
||||
|
||||
::
|
||||
|
||||
func _on_Area2D_body_entered(body):
|
||||
pass
|
||||
|
||||
And the same callback, with type hints:
|
||||
|
||||
::
|
||||
|
||||
func _on_area_entered(area: CollisionObject2D) -> void:
|
||||
pass
|
||||
|
||||
You're free to replace, e.g. the ``CollisionObject2D``, with your own type,
|
||||
to cast parameters automatically:
|
||||
|
||||
::
|
||||
|
||||
func _on_area_entered(bullet: Bullet) -> void:
|
||||
if not bullet:
|
||||
return
|
||||
|
||||
take_damage(bullet.damage)
|
||||
|
||||
The ``bullet`` variable could hold any ``CollisionObject2D`` here, but
|
||||
we make sure it is our ``Bullet``, a node we created for our project. If
|
||||
it's anything else, like an ``Area2D``, or any node that doesn't extend
|
||||
``Bullet``, the ``bullet`` variable will be ``null``.
|
||||
|
||||
Warning system
|
||||
--------------
|
||||
|
||||
.. note::
|
||||
|
||||
Documentation about the GDScript warning system has been moved to
|
||||
:ref:`doc_gdscript_warning_system`.
|
||||
|
||||
Cases where you can't specify types
|
||||
-----------------------------------
|
||||
|
||||
To wrap up this introduction, let's cover a few cases where you can't
|
||||
use type hints. All the examples below **will trigger errors**.
|
||||
|
||||
You can't use Enums as types:
|
||||
|
||||
::
|
||||
|
||||
enum MoveDirection {UP, DOWN, LEFT, RIGHT}
|
||||
var current_direction: MoveDirection
|
||||
|
||||
You can't specify the type of individual members in an array. This will
|
||||
give you an error:
|
||||
|
||||
::
|
||||
|
||||
var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]
|
||||
|
||||
You can't force the assignment of types in a ``for`` loop, as each
|
||||
element the ``for`` keyword loops over already has a different type. So you
|
||||
**cannot** write:
|
||||
|
||||
::
|
||||
|
||||
var names = ["John", "Marta", "Samantha", "Jimmy"]
|
||||
for name: String in names:
|
||||
pass
|
||||
|
||||
Two scripts can't depend on each other in a cyclic fashion:
|
||||
|
||||
::
|
||||
|
||||
# Player.gd
|
||||
|
||||
extends Area2D
|
||||
class_name Player
|
||||
|
||||
|
||||
var rifle: Rifle
|
||||
|
||||
::
|
||||
|
||||
# Rifle.gd
|
||||
|
||||
extends Area2D
|
||||
class_name Rifle
|
||||
|
||||
|
||||
var player: Player
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
Typed GDScript is a powerful tool. Available as of version 3.1 of Godot, it
|
||||
helps you write more structured code, avoid common errors, and
|
||||
create scalable systems. In the future, static types will also bring you
|
||||
a nice performance boost thanks to upcoming compiler optimizations.
|
||||
51
tutorials/scripting/gdscript/warning_system.rst
Normal file
@@ -0,0 +1,51 @@
|
||||
.. _doc_gdscript_warning_system:
|
||||
|
||||
GDScript warning system
|
||||
=======================
|
||||
|
||||
The GDScript warning system complements :ref:`static typing <doc_gdscript_static_typing>`
|
||||
(but it can work without static typing too). It's here to help you avoid
|
||||
mistakes that are hard to spot during development, and that may lead
|
||||
to runtime errors.
|
||||
|
||||
You can configure warnings in the Project Settings under the section
|
||||
called **Gdscript**:
|
||||
|
||||
.. figure:: img/typed_gdscript_warning_system_settings.png
|
||||
:alt: Warning system project settings
|
||||
|
||||
Warning system project settings
|
||||
|
||||
You can find a list of warnings for the active GDScript file in the
|
||||
script editor's status bar. The example below has 3 warnings:
|
||||
|
||||
.. figure:: img/typed_gdscript_warning_example.png
|
||||
:alt: Warning system example
|
||||
|
||||
Warning system example
|
||||
|
||||
To ignore specific warnings in one file, insert a special comment of the
|
||||
form ``# warning-ignore:warning-id``, or click on the ignore link to the
|
||||
right of the warning's description. Godot will add a comment above the
|
||||
corresponding line and the code won't trigger the corresponding warning
|
||||
anymore:
|
||||
|
||||
.. figure:: img/typed_gdscript_warning_system_ignore.png
|
||||
:alt: Warning system ignore example
|
||||
|
||||
Warning system ignore example
|
||||
|
||||
You can also choose to ignore not just one but all warnings of a certain
|
||||
type in this file with ``# warning-ignore-all:warning-id``. To ignore all
|
||||
warnings of all types in a file add the comment ``# warnings-disable`` to it.
|
||||
|
||||
Warnings won't prevent the game from running, but you can turn them into
|
||||
errors if you'd like. This way your game won't compile unless you fix
|
||||
all warnings. Head to the ``GDScript`` section of the Project Settings to
|
||||
turn on this option. Here's the same file as the previous example with
|
||||
warnings as errors turned on:
|
||||
|
||||
.. figure:: img/typed_gdscript_warning_system_errors.png
|
||||
:alt: Warnings as errors
|
||||
|
||||
Warnings as errors
|
||||