diff --git a/_extensions/gdscript.py b/_extensions/gdscript.py index 533732158af..c634b52def1 100644 --- a/_extensions/gdscript.py +++ b/_extensions/gdscript.py @@ -151,7 +151,9 @@ def innerstring_rules(ttype): "namespace", # Reserved for potential future use. "signal", "static", - "trait", # Reserved for potential future use. + "trait", + "trait_name", + "uses", "var", # Other keywords. "await", diff --git a/tutorials/scripting/gdscript/gdscript_basics.rst b/tutorials/scripting/gdscript/gdscript_basics.rst index b34aef5797f..55cbc67ae22 100644 --- a/tutorials/scripting/gdscript/gdscript_basics.rst +++ b/tutorials/scripting/gdscript/gdscript_basics.rst @@ -42,6 +42,9 @@ here's an example of how GDScript looks. # Inheritance: extends BaseClass + # Trait usage: + uses Talkable, Flammable + # Member variables. var a = 5 @@ -103,6 +106,19 @@ here's an example of how GDScript looks. super.something(p1, p2) + # When traits are used, they may have to implement abstract + # methods defined by the trait: + func talk_to(): + print("Hi, you just talked to me!") + + + # Traits can also implement methods that the concrete class + # can use without defining itself: + func yet_another_something(): + print("I'm going to light on fire!") + light_on_fire() + + # Inner class class Something: var a = 10 @@ -174,7 +190,11 @@ in case you want to take a look under the hood. +------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ | extends | Defines what class to extend with the current class. | +------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ -| is | Tests whether a variable extends a given class, or is of a given built-in type. | +| trait | Defines a trait. See `Traits`_. | ++------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ +| uses | Defines what trait(s) the current class should use. | ++------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ +| is | Tests whether a variable extends a given class, uses a given trait, or is of a given built-in type. | +------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ | in | Tests whether a value is within a string, array, range, dictionary, or node. When used with ``for``, it iterates through them instead of testing. | +------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -865,7 +885,7 @@ The GDScript static analyzer takes typed arrays into account, however array meth ``front()`` and ``back()`` still have the ``Variant`` return type. Typed arrays have the syntax ``Array[Type]``, where ``Type`` can be any ``Variant`` type, -native or user class, or enum. Nested array types (like ``Array[Array[int]]``) are not supported. +native or user class, trait, or enum. Nested array types (like ``Array[Array[int]]``) are not supported. :: @@ -873,7 +893,8 @@ native or user class, or enum. Nested array types (like ``Array[Array[int]]``) a var b: Array[Node] var c: Array[MyClass] var d: Array[MyEnum] - var e: Array[Variant] + var e: Array[MyTrait] + var f: Array[Variant] ``Array`` and ``Array[Variant]`` are the same thing. @@ -1081,8 +1102,8 @@ Valid types are: - Built-in types (Array, Vector2, int, String, etc.). - Engine classes (Node, Resource, RefCounted, etc.). -- Constant names if they contain a script resource (``MyScript`` if you declared ``const MyScript = preload("res://my_script.gd")``). -- Other classes in the same script, respecting scope (``InnerClass.NestedClass`` if you declared ``class NestedClass`` inside the ``class InnerClass`` in the same scope). +- Constant names if they contain a script or trait resource (``MyScript`` if you declared ``const MyScript = preload("res://my_script.gd")``). +- Other classes or traits in the same file, respecting scope (``InnerClass.NestedClass`` if you declared ``class NestedClass`` inside the ``class InnerClass`` in the same scope). - Script classes declared with the ``class_name`` keyword. - Autoloads registered as singletons. @@ -1155,11 +1176,11 @@ A class member variable can be declared static: static var a -Static variables belong to the class, not instances. This means that static variables +Static variables belong to the class or trait, not instances. This means that static variables share values between multiple instances, unlike regular member variables. -From inside a class, you can access static variables from any function, both static and non-static. -From outside the class, you can access static variables using the class or an instance +From inside a class or trait, you can access static variables from any function, both static and non-static. +From outside the class or trait, you can access static variables using the class or an instance (the second is not recommended as it is less readable). .. note:: @@ -1263,15 +1284,15 @@ Values assigned to typed variables must have a compatible type. If it's needed t coerce a value to be of a certain type, in particular for object types, you can use the casting operator ``as``. -Casting between object types results in the same object if the value is of the -same type or a subtype of the cast type. +Casting between object types or traits results in the same object if the value is of the +same type or a subtype of the cast type, or if the value uses the trait. :: var my_node2D: Node2D my_node2D = $Sprite2D as Node2D # Works since Sprite2D is a subtype of Node2D. -If the value is not a subtype, the casting operation will result in a ``null`` value. +If the value is not a subtype or does not use the trait, the casting operation will result in a ``null`` value. :: @@ -1383,7 +1404,7 @@ or ``0`` if it is the first entry in the enum. Multiple keys with the same value Functions --------- -Functions always belong to a `class `_. The scope priority for +Functions always belong to a `class `_ or a `trait `_. The scope priority for variable look-up is: local → class member → global. The ``self`` variable is always available and is provided as an option for accessing class members (see `self`_), but is not always required (and should *not* be sent as the @@ -2143,6 +2164,7 @@ A class (stored as a file) can inherit from: Multiple inheritance is not allowed. +Multiple inheritance is not allowed, but Godot does support `traits `_, which cover many of the same use cases. Inheritance uses the ``extends`` keyword:: # Inherit/extend a globally available class. @@ -2159,7 +2181,7 @@ Inheritance uses the ``extends`` keyword:: If inheritance is not explicitly defined, the class will default to inheriting :ref:`class_RefCounted`. -To check if a given instance inherits from a given class, +To check if a given instance inherits from a given class or uses a given trait, the ``is`` keyword can be used: :: @@ -2173,6 +2195,15 @@ the ``is`` keyword can be used: if entity is Enemy: entity.apply_damage() + # Cache the Flammable trait. + const Flammable = preload("flammable.gdt") + + # [...] + + # Use 'is' to check usage of the trait. + if entity is Flammable: + entity.light_on_fire() + To call a function in a *super class* (i.e. one ``extend``-ed in your current class), use the ``super`` keyword:: @@ -2335,6 +2366,75 @@ class resource is done by calling the ``new`` function on the class object: var a = MyClass.new() a.some_function() + +.. _doc_gdscript_basics_traits: + +Traits +------ + +Since Godot 4.x, GDScript supports traits, which are collections of behaviors and attributes +that classes can use to guarantee +functionality to themselves and other objects that may be attempting to use them. + +Note that traits on their own *cannot* be instantiated the same way that classes can. + +Registering traits +~~~~~~~~~~~~~~~~~~~~~~~~ + +Traits can be created inside of a GDScript class by using the ``trait`` keyword. +:: + + trait Damageable: + signal died + + const MAX_HEALTH = 100 + var health = MAX_HEALTH + + func take_damage(amount): # Will automatically exist in any class using this trait. + health -= amount + on_hit() + if health <= 0: + on_death() + died.emit() + + func on_hit() # Unimplemented method - Must be overriden in any class using this trait. + +Using traits in a class +~~~~~~~~~~~~~~~~~~~~~~~ + +For a class to use a trait, include the ``uses`` keyword: +:: + class_name Player + uses Damageable + +The ``is`` keyword can be used to determine if a given instance uses a particular trait. +:: + if entity is Damageable: + entity.take_damage(1) + +If a trait provides a method signature, but no body, then the using class must implement +a body for the method. +:: + class_name Player + extends CharacterBody2D + uses Damageable + + func on_hit(): + print("Ouch, that hurt!") + +If a trait provides a method signature *and* a body, then the using class inherits it by default +and doesn't need to provide its own implementation. It still can override the trait's +implementation if desired, but the parameter count must stay the same, and the parameter and return +types must be compatible. +:: + class_name InvincibleNPC + extends Sprite2D + uses Damageable + + # Allowed, and will run instead of Damageable's original take_damage method. + func take_damage(amount): + print("Ah ha ha! You can't hurt me!") + Exports ------- diff --git a/tutorials/scripting/gdscript/gdscript_styleguide.rst b/tutorials/scripting/gdscript/gdscript_styleguide.rst index 1db7a5cc65f..41272f4fd9c 100644 --- a/tutorials/scripting/gdscript/gdscript_styleguide.rst +++ b/tutorials/scripting/gdscript/gdscript_styleguide.rst @@ -30,6 +30,7 @@ Here is a complete class example based on these guidelines: class_name StateMachine extends Node + uses Activatable ## Hierarchical State machine for the player. ## ## Initializes states and delegates engine callbacks ([method Node._physics_process], @@ -650,6 +651,8 @@ code. As a summary table: +---------------+----------------+----------------------------------------------------+ | Class names | PascalCase | ``class_name YAMLParser`` | +---------------+----------------+----------------------------------------------------+ +| Trait names | PascalCase | ``trait Interactable`` | ++---------------+----------------+----------------------------------------------------+ | Node names | PascalCase | ``Camera3D``, ``Player`` | +---------------+----------------+----------------------------------------------------+ | Functions | snake_case | ``func load_level():`` | @@ -668,8 +671,7 @@ code. As a summary table: File names ~~~~~~~~~~ -Use snake_case for file names. For named classes, convert the PascalCase class -name to snake_case:: +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 @@ -688,13 +690,13 @@ from Windows to other platforms. Classes and nodes ~~~~~~~~~~~~~~~~~ -Use PascalCase for class and node names: +Use PascalCase for class, trait, and node names: :: extends CharacterBody3D -Also use PascalCase when loading a class into a constant or a variable: +Also use PascalCase when loading a class or trait into a constant or a variable: :: @@ -787,28 +789,29 @@ We suggest to organize GDScript code this way: 01. @tool, @icon, @static_unload 02. class_name 03. extends - 04. ## doc comment - - 05. signals - 06. enums - 07. constants - 08. static variables - 09. @export variables - 10. remaining regular variables - 11. @onready variables - - 12. _static_init() - 13. remaining static methods - 14. overridden built-in virtual methods: + 04. uses + 05. ## doc comment + + 06. signals + 07. enums + 08. constants + 09. static variables + 10. @export variables + 11. remaining regular variables + 12. @onready variables + + 13. _static_init() + 14. remaining static methods + 15. overridden built-in virtual methods: 1. _init() 2. _enter_tree() 3. _ready() 4. _process() 5. _physics_process() 6. remaining virtual methods - 15. overridden custom methods - 16. remaining methods - 17. subclasses + 16. overridden custom methods + 17. remaining methods + 18. subclasses And put the class methods and variables in the following order depending on their access modifiers: @@ -842,6 +845,8 @@ to be an :ref:`abstract class `, add ``abstract`` *before* the ``class_name`` keyword, but on the same line. Then, add the ``extends`` keyword if the class extends a built-in type. +If the class uses any traits, add the ``uses`` keyword along with all of the trait +names (or filepaths, if the traits are unnamed) of the traits it uses. Following that, you should have the class's optional :ref:`documentation comments `. @@ -852,6 +857,7 @@ and how other developers should use it, for example. abstract class_name MyNode extends Node + uses MyTrait ## A brief description of the class's role and functionality. ## ## The description of the script, what it can do, diff --git a/tutorials/scripting/gdscript/static_typing.rst b/tutorials/scripting/gdscript/static_typing.rst index 1dad8e2eb2a..ba587835b44 100644 --- a/tutorials/scripting/gdscript/static_typing.rst +++ b/tutorials/scripting/gdscript/static_typing.rst @@ -122,8 +122,9 @@ Here is a complete list of what can be used as a type hint: 7. Global, native and custom named enums. Note that an enum type is just an ``int``, there is no guarantee that the value belongs to the set of enum values. 8. Constants (including local ones) if they contain a preloaded class or enum. +9. :ref:`Traits `. -You can use any class, including your custom classes, as types. There are two ways +You can use any class or trait, including your custom classes and traits, 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: @@ -146,6 +147,7 @@ and you can use it anywhere, without having to preload it into a constant: :: var my_rifle: Rifle + Specify the return type of a function with the arrow ``->`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -167,7 +169,7 @@ as with variables: health_points -= damage return health_points <= 0 -You can also use your own classes as return types: +You can also use your own classes or traits as return types: :: @@ -217,7 +219,7 @@ To define the type of an ``Array``, enclose the type name in ``[]``. An array's type applies to ``for`` loop variables, as well as some operators like ``[]``, ``[]=``, and ``+``. Array methods (such as ``push_back``) and other operators -(such as ``==``) are still untyped. Built-in types, native and custom classes, +(such as ``==``) are still untyped. Built-in types, native and custom classes and traits, and enums may be used as element types. Nested array types (like ``Array[Array[int]]``) are not supported.