diff --git a/_extensions/gdscript.py b/_extensions/gdscript.py index 59c99cc3f3a0..664e8416cd6f 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 bd6ca5d9fc7b..925bf365856c 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,13 @@ 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 an inner trait. See `Inner traits`_. | ++------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ +| trait_name | Defines the script as a globally accessible trait with the specified name. See `Registering named 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. | +------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -853,7 +875,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. :: @@ -861,7 +883,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. @@ -1036,9 +1059,10 @@ 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. +- Traits declared with the ``trait_name`` keyword. - Autoloads registered as singletons. .. note:: @@ -1324,7 +1348,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, but is not always required (and should *not* be sent as the function's first @@ -1941,6 +1965,11 @@ A class (stored as a file) can inherit from: Multiple inheritance is not allowed. +.. note:: + Godot 4.x introduces `traits `_ to GDScript, which may cover many of + the use cases for multiple inheritance. See their section for more information + about how they work. + Inheritance uses the ``extends`` keyword:: # Inherit/extend a globally available class. @@ -1957,7 +1986,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:: # Cache the enemy class. @@ -1969,6 +1998,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:: @@ -2121,6 +2159,136 @@ class resource is done by calling the ``new`` function on the class object:: var a = MyClass.new() a.some_function() +Traits +------ + +Traits 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. + +Like classes, by default all ``.gdt`` files are unnamed traits, and you must reference +them using a relative or absolute path. +:: + # Use the trait 'interactable.gdt'. + uses "res://path/to/interactable.gdt" + +Note that traits on their own *cannot* be instantiated the same way that classes can. + +.. _doc_gdscript_basics_trait_name: + +Registering named traits +~~~~~~~~~~~~~~~~~~~~~~~~ + +Traits can be given a global name by using the ``trait_name`` keyword. +:: + trait_name MyTrait + +Using traits in a class +~~~~~~~~~~~~~~~~~~~~~~~ + +For a class to use a trait, use the ``uses`` keyword: +:: + class_name MyScript + uses MyTrait + + +Traits may also extend classes. If a trait extends a class, then any class +that uses that trait must also have that class as an ancestor. +:: + # movable.gdt + trait_name Movable + extends PhysicsBody2D + + # character.gd + class_name Character + extends CharacterBody2D + uses Movable # Allowed, since CharacterBody2D inherits from PhysicsBody2D. + +The ``is`` keyword can be used to determine if a given instance uses a particular trait. +:: + if entity is Movable: + entity.move() + +If a trait provides a method signature, but no body, then the using class must implement +a body for the method. +:: + # explosive.gdt + trait_name Explosive + + func explode() # Body is not defined here, so it must be defined in each class that uses it. + + + # exploding_barrel.gd + class_name ExplodingBarrel + extends Sprite2D + uses Explosive + + func explode(): # If this definition of Explosive.explode isn't provided, we will get an error. + print("Kaboom!") + queue_free() + +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. +:: + # damageable.gdt + trait_name Damageable + + func take_damage(): + print("Ouch!") + + + # invincible_npc.gd + class_name InvincibleNPC + extends Sprite2D + uses Damageable + + # Allowed, and will run instead of Damageable's original take_damage method. + func take_damage(): + print("You can't hurt me!") + +.. + TODO: Confirm these behaviors + +Other class members have similar rules: + +- Variables and constants can be overridden, as long as the type is compatible and the value is changed. +- Signals can be overriden, as long as the parameter count is maintained and the parameter types are compatible. +- Named enums can be overriden and have new enum values. + +.. _doc_gdscript_basics_inner_traits: + +Inner traits +~~~~~~~~~~~~ + +Like inner classes, a class or trait file may contain inner traits, defined with the ``trait`` +keyword. Unlike inner classes, they cannot be instantiated directly, but their name can be +referenced for using or checking use of themselves. +:: + # An inner trait in this class file. + trait SomeInnerTrait: + func do_something(): + print("I did something!") + + + # An inner class in this class file, which uses the inner trait. + class SomeInnerClass: + uses SomeInnerTrait + + + func _init(): + var c = SomeInnerClass.new() + if c is SomeInnerTrait: + c.do_something() + +.. _doc_gdscript_basics_traits_as_resources: + +Traits as resources +~~~~~~~~~~~~~~~~~~~ + +Traits stored as files are treated as :ref:`GDTraits `, and must +be loaded similarly to classes (see `Classes as resources`_). + Exports -------