Godot Engine 4.0 Documentation - Manual - Best Practices

This article is the result of Google Translate's English translation, and DrGraph added some corrections on this basis. English original page: Best practices — Godot Engine (stable) documentation in English

 

Introduction¶ _

This series is a collection of best practices to help you use Godot efficiently.

Godot offers great flexibility in structuring a project's code base and breaking it down into scenes. Each approach has its pros and cons, and it can be hard to weigh them all until you've been using the engine long enough.

There are always many ways to structure code and solve specific programming problems. It is impossible to cover everything here.

That's why every article starts with a real-world problem. We'll break down each of the basic problems, propose solutions, analyze the pros and cons of each option, and highlight the best course of action for the problem at hand.

You should start by reading Applying Object-Oriented Principles in Godot . It explains how Godot's nodes and scenes relate to classes and objects in other object-oriented programming languages. It will help you understand the rest of this series.

Note: Best practices in Godot rely on object-oriented design principles. We use tools like the Single Responsibility Principle and  encapsulation .

Applying Object Oriented Principles in Godot¶

The engine provides two main ways of creating reusable objects: scripts and scenes. None of these technically define the classes under the hood.

However, many best practices for using Godot involve applying object-oriented programming principles to the scripts and scenes that make up your game. That's why it's useful to understand how we think of them as classes.

This guide briefly explains how scripts and scenes work in the core of the engine to help you understand how they work under the hood.

How scripts work in the engine¶

该引擎提供内置类,如Node。您可以扩展它们以使用脚本创建派生类型。

这些脚本在技术上不是类。相反,它们是告诉引擎要对引擎的一个内置类执行一系列初始化的资源。

Godot 的内部类具有将类的数据注册到ClassDB的方法。该数据库提供对类信息的运行时访问。ClassDB包含有关类的信息,例如:

  • 特性。

  • 方法。

  • 常数。

  • 信号。

ClassDB是对象在执行访问属性或调用方法等操作时要检查的内容。它检查数据库的记录和对象的基本类型的记录,以查看对象是否支持该操作。

将脚本附加到您的对象可扩展ClassDB.

注:即使不使用extends关键字的脚本也隐式继承自引擎的基 RefCounted类。因此,您可以在没有 extends代码关键字的情况下实例化脚本。由于它们会扩展RefCounted,因此您不能将它们附加到Node

场景

场景的行为与类有很多相似之处,因此将场景视为类是有道理的。场景是可重用、可实例化和可继承的节点组。创建场景类似于创建节点并将它们添加为子节点的脚本add_child()

我们经常将场景与使用场景节点的脚本化根节点配对。因此,脚本通过命令式代码添加行为来扩展场景。

场景的内容有助于定义:

  • 脚本可以使用哪些节点

  • 他们是如何组织的

  • 它们是如何初始化的

  • 他们之间有什么信号连接

为什么这些对场景组织很重要?因为场景的实例对象。因此,许多适用于编写代码的面向对象原则也适用于场景:单一职责、封装等。

A scene is always an extension of a script attached to its root node , so you can interpret it as part of a class.

Most of the techniques presented in this best practices series build on this point.

Scene Organization¶

This article covers topics related to the effective organization of scene content. Which nodes should be used? Where should they be placed? How should they interact?

How to Build Relationships Efficiently¶

When Godot users start making their own scenes, they often run into the following problems:

They created their first scene and filled it with content, but ended up saving branches of their scene into separate scenes because the nagging sense that they should keep things separate started to build up. However, they then noticed that the hard references they were able to rely on before were no longer possible. Reusing a scene in multiple places creates problems as node paths cannot find their targets and signal connections made in the editor are broken.

To solve these problems, subscenes must be instantiated without detailed information about their environment. People need to be able to trust that the subscene will create itself without picking on how people use it.

One of the most important things to consider in OOP is maintaining centralized, single-purpose classes that are loosely coupled with the rest of the code base . This keeps the size of objects small (for maintainability) and increases their reusability.

These OOP best practices have several implications for best practices in scene structure and script usage .

Scenarios should be designed to have no dependencies if possible.  That is, people should create scenarios that keep everything they need within themselves.

If a scene has to interact with an external context, experienced developers recommend using  dependency injection . This technique involves having a high-level API provide the dependencies of a low-level API. Why do you want to do this? Because classes that depend on the external environment can inadvertently trigger bugs and unexpected behavior.

To do this, the data must be exposed, and then rely on the parent context to initialize it:

  1. connected to the signal. Very safe, but should only be used for "response" behavior, not to initiate it. Note that signal names are usually past tense verbs, such as "entered", "skill_activated", or "item_collected".

    # Parent
    $Child.signal_name.connect(method_on_the_object)
    
    # Child
    signal_name.emit() # Triggers parent-defined behavior.
    
  2. Call a method. Used to start the behavior.

    # Parent
    $Child.method_name = "do"
    
    # Child, assuming it has String property 'method_name' and method 'do'.
    call(method_name) # Call parent-defined method (which child must own).
    
  3. Initialize the Callable property. Safer than methods because no ownership of methods is required. Used to start the behavior.

    # Parent
    $Child.func_property = object_with_method.method_on_the_object
    
    # Child
    func_property.call() # Call parent-defined method (can come from anywhere).
    
  4. Initialize a node or other object reference.

    # Parent
    $Child.target = self
    
    # Child
    print(target) # Use parent-defined node.
    
  5. Initialize a node path.

    # Parent
    $Child.target_path = ".."
    
    # Child
    get_node(target_path) # Use parent-defined NodePath.
    

These options hide access points for child nodes. This in turn makes the child loosely coupled to its environment. It can be reused in another context without any additional changes to its API.

Note: Although the examples above illustrate parent-child relationships, the same principles apply to all object relationships. Nodes that are siblings should only know about their hierarchy, while ancestors mediate their communication and references.

# Parent
$Left.target = $Right.get_node("Receiver")

# Left
var target: Node
func execute():
    # Do something with 'target'.

# Right
func _init():
    var receiver = Receiver.new()
    add_child(receiver)

The same principle applies to non-node objects that maintain dependencies on other objects. Whichever object actually owns these objects should manage the relationship between them.

Note: One should favor keeping data internal (inside the scene), although relying on an external context, even a loosely coupled context, still means that a node will expect something in its environment to be true. The project's design philosophy should prevent this from happening. Otherwise, the inherent responsibility of the code will force developers to use documentation to track object relationships on a microscopic scale; this is also known as development hell. By default, writing code that relies on external documentation to use it safely is error-prone.

To avoid creating and maintaining such documentation, turn dependent nodes (the "children" above) into implementations  _get_configuration_warning(). Returning a non-empty string from this will cause the scene dock to generate an alert icon that takes the string as a tooltip. When the Area2D node does not define child  CollisionShape2D nodes, this icon is the same as the icon displayed by the Area2D node and other nodes. The editor then self-records the scene via script code. No need to copy content through the document.

A GUI like this can better inform project users of key information about nodes. Does it have external dependencies? Are these dependencies met? Other programmers, especially designers and writers, will need clear instructions in the message telling them how to configure it.

So, why do all these complicated switches work? Well, because scenes work best when run individually. Working anonymously with others (with minimal hard dependencies, i.e. loose coupling) is the next best option if working alone is not possible. Inevitably, changes may need to be made to the class, and if those changes cause it to interact with other scenes in unforeseen ways, things start to break down. The whole point of all this indirection is to avoid situations where changing one class would adversely affect other classes that depend on it.

Scripts and scenes, as extensions of the engine class, should follow all OOP principles. Examples include...

As a result, developers start working on games only to stop at the enormous possibilities that lie before them. They probably know what they want to do, what kind of system they want, but where to put it all ? Well, how one makes their game is always up to them. A node tree can be constructed in an infinite number of ways. However, for those who are unsure, this helpful guide can provide them with a decent structure sample to start with.

Games should always have a sort of "point of entry"; a place where developers can clearly track where things start so they can follow logic when continuing elsewhere. This place also serves as a bird's-eye view of all other data and logic in the program. For legacy applications, this would be the "main" function. In this case it will be a master node.

  • Node "main" (main.gd)

The script will then main.gdact as the main controller for the game.

One then has their actual game "world" (2D or 3D world). This can be a child of Main. Additionally, their game requires a main GUI to manage the various menus and widgets needed for the project.

  • Node "main" (main.gd)

    • Node2D/Node3D “world” (game_world.gd)

    • Control "GUI" (gui.gd)

When changing levels, children of the "world" node can be swapped out. Manually changing scenes gives users complete control over how their game world transforms.

The next step is to consider what kind of game system the project requires. If one has a system...

  1. Track all its data internally

  2. Should be globally accessible

  3. should exist in isolation

...and then an autoloading "singleton" node should be created .

notes

For smaller games, a simpler alternative with less control is to have a "game" singleton that just calls the SceneTree.change_scene_to_file  () method to swap out the contents of the main scene. This structure more or less keeps the "world" as the main game node.

Any GUI also needs to be a singleton; be a transient part of the "world"; or manually added as a direct child of the root. Otherwise, GUI nodes also delete themselves during scene transitions.

If one system modifies the data of other systems, those systems should be defined as their own scripts or scenes, not autoloaded. See  the autoloading vs. regular node  documentation for more information on why.

Every subsystem in the game should have its own section in SceneTree. Parent-child relationships should only be used when a node is a valid element of its parent. Does removing the parent reasonably mean that the child should also be removed? If not, then it should have its own place in the hierarchy as a sibling or other relationship.

notes

In some cases it is desirable that these separate nodes also position themselves relative to each other. For this,  RemoteTransform  /  RemoteTransform2D nodes can be used. They will allow the target node to conditionally inherit selected transform elements from the Remote* node. To assign target a NodePath , use one of the following:

  1. A reliable third party, possibly the parent node, mediates the assignment.

  2. A group that makes it easy to extract references to desired nodes (assuming there is only one target).

When should this be done? Well, this is subjective. Dilemmas arise when nodes have to move around the SceneTree to protect themselves, when micromanagement has to be done. For example...

  • Add a "Player" node to the "Room".

  • The room needs to be changed, so the current room must be deleted.

  • Players must be retained and/or moved before a room can be deleted.

    Is memory an issue?

    • If not, one can just create two rooms, move the player and delete the old one. no problem.

    If so, one will need to...

    • Move the player elsewhere in the tree.

    • Delete room.

    • Instantiate and add a new room.

    • Add the player again.

The problem is that the player here is a "special case"; the developer must know that they need to handle the player this way for the project. Therefore, the only way to reliably share this information as a team is to document it . However, it is dangerous to keep implementation details in the documentation. This is a maintenance burden that affects the readability of the code and unnecessarily bloats the intellectual content of the project.

In more complex games with larger assets, it might be a better idea to keep the player elsewhere in the SceneTree entirely. This results in:

  1. More consistency.

  2. There are no "special cases" that have to be documented and maintained somewhere.

  3. There is no chance of error because these details are not considered.

Instead, if you want a child node that doesn't inherit the parent's transform, you have the following options:

  1. Declarative solution: put a node between them . As nodes without transitions, nodes do not pass such information to their children.

  2. Imperative solution: use properties of CanvasItem or Node3D  nodes . This will make the node ignore its inherited transforms.top_level

notes

If building an online game, keep in mind which nodes and game systems are relevant to all players and which are only relevant to the authoritative server. For example, users don't all need to have a copy of the "PlayerController" logic for each player. Instead, they just need their own. So keeping them in a different branch than "world" can help simplify management of game connections etc.

The key to scene organization is to think of SceneTree in relational terms rather than spatial terms. Does the node depend on the existence of its parent node? If not, then they can thrive elsewhere on their own. If they're dependents, then arguably they should be children of that parent (and likely part of that parent's scene if they're not already).

Does this mean that nodes themselves are components? not at all. Godot's node tree forms an aggregation relationship, not a composition relationship. However, while there is still the flexibility to move nodes around, it would be nice not to require such moves by default.

When to Use Scenes and Scripts¶

We've covered the differences between scenarios and scripts. Scripts define engine class extensions using imperative code and scenarios using declarative code.

Therefore, each system functions differently. A scene can define how an extended class is initialized, but not its actual behavior. Scenes are often used in conjunction with scripts, where scenes declare combinations of nodes and scripts add behavior using imperative code.

Anonymous types¶

The content of the scene can be fully defined using only scripts . Essentially, this is what the Godot editor does, just in the C++ constructor of its objects.

However, choosing which one to use can be a dilemma. Creating a script instance is the same as creating an in-engine class, while handling scenarios requires changing the API:

const MyNode = preload("my_node.gd")
const MyScene = preload("my_scene.tscn")
var node = Node.new()
var my_node = MyNode.new() # Same method call
var my_scene = MyScene.instantiate() # Different method call
var my_inherited_scene = MyScene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene

Also, scripts will run a bit slower than scenes due to speed differences between the engine and script code. The larger and more complex the node, the more reason to build it into a scene.

Named types¶

Scripts can be registered as new types in the editor itself. This will show it as a new type in the node or resource creation dialog with an optional icon. In this way, the user's ability to use scripts is greatly simplified. rather than having to...

  1. Know the basic types of scripts they want to use.

  2. Create an instance of this base type.

  3. Add the script to the node.

With registered scripts, the script type instead becomes a creation option like other nodes and resources in the system. The creation dialog even has a search bar to look up types by name.

There are two types of registration systems:

  • custom type

    • Editor only. Type names are not accessible at runtime.

    • Inherited custom types are not supported.

    • An initialization tool. Create nodes using scripts. That's all.

    • The editor has no type awareness of scripts or their relationship to other engine types or scripts.

    • Allows user to define icons.

    • Works with all scripting languages ​​because it handles scripting resources abstractly.

    • Use EditorPlugin.add_custom_type to set it.

  • Script class

    • Editor and runtime accessible.

    • Full display of inheritance relationships.

    • Nodes are created using scripts, but types can also be changed or extended from the editor.

    • The editor is aware of the inheritance relationship between scripts, script classes, and engine C++ classes.

    • Allows user to define icons.

    • Engine developers have to manually add support for the language (name exposure and runtime accessibility).

    • Godot 3.1+ only.

    • The editor scans the project folder and registers any exposed names for all scripting languages. Each scripting language must implement its own support for exposing this information.

Both of these methods add the name to the creation dialog, but the script class in particular also allows the user to access the type name without loading the script resource. Creating instances and accessing constants or static methods is possible from anywhere.

With these features, one might wish their type was a script without scenarios, as it confers ease of use to the user. Those developing plugins or creating in-house tools for use by designers will find it easier this way.

On the downside, this also means that imperative programming has to be heavily used.

Performance of scripts and PackedScene¶

A final aspect to consider when selecting scenes and scripts is speed of execution.

As the size of objects increases, the size required for scripts to create and initialize them also becomes larger. Creating a node hierarchy demonstrates this. The logic for each node can be hundreds of lines of code.

The following code example creates a new node Node, changes its name, assigns it a script, sets its future parent as its owner so it is saved to disk with it, and finally adds it as a child of the node Main:

# main.gd
extends Node

func _init():
    var child = Node.new()
    child.name = "Child"
    child.script = preload("child.gd")
    child.owner = self
    add_child(child)

Script code like this is much slower than C++ code on the engine side. Each instruction calls the scripting API, which causes multiple "lookups" on the backend to find the logic to execute.

Scenes help avoid this performance issue. PackedScene is the base type that Scene inherits from, and it defines resources that create objects with serialized data. The engine can batch scenes in the backend and provide better performance than scripting.

Conclusion¶ _

In the end, the best approach is to consider the following:

  • If one wishes to create a basic tool that will be reused across several different projects and is likely to be used by people of all skill levels (including those who don't label themselves "programmers"), then it's probably possible Should be a script, possibly one with a custom name/icon.

  • If someone wishes to create a concept specific to their game, it should always be a scene. Scenarios are easier to track/edit than scripts and provide greater security.

  • If someone wants to name the scene, they can still do it in 3.1 by declaring the script class and giving it the scene as a constant. The script actually becomes a namespace:

    # game.gd
    class_name Game # extends RefCounted, so it won't show up in the node creation dialog
    extends RefCounted
    
    const MyScene = preload("my_scene.tscn")
    
    # main.gd
    extends Node
    func _ready():
        add_child(Game.MyScene.instantiate())

Autoloading vs regular nodes¶

Godot provides the function of automatically loading the root nodes of the project, allowing you to access them globally, and can complete the role of singletons: Singletons (  Autoload) . These autoloaded nodes are not released when you change the scene from code using SceneTree.change_scene_to_file .

In this guide, you'll learn when to use autoload, and the tricks you can use to avoid it.

Cutting audio issues¶

Other engines may encourage the use of creating manager classes, organizing large amounts of functionality into singletons in globally accessible objects. Godot provides many ways to avoid global state thanks to node trees and signals.

For example, let's say we're building a platformer and want to collect coins that play sound effects. There is one node: AudioStreamPlayer . But if we call it when it is already playing a sound AudioStreamPlayer, the new sound will interrupt the first one.

One solution is to write a global, autoloaded sound manager class. It spawns a pool of nodes AudioStreamPlayerthat it cycles through as each new sound effect request comes in. Assuming we call that class Sound, you can use it from anywhere in your project by calling it Sound.play("coin_pickup.ogg"). This solves the problem in the short term, but causes more problems:

  1. Global state : one object is now responsible for all objects' data. If this class  Soundis buggy or there is no AudioStreamPlayer available, all nodes calling it may break.

  2. Global Access : Now that any object can Sound.play(sound_path) be called from anywhere, there is no longer an easy way to find the source of an error.

  3. Global resource allocationAudioStreamPlayer : with a pool of nodes stored from the start, you either have too little and face errors, or too much and use more memory than you need.

NOTE: The problem with global access is that any code anywhere could pass wrong data to Soundautoload in our example. Therefore, the area to explore to fix bugs spans the entire project.

When you save code in a scene, there may only be a script or two involved in the audio.

Contrast this with AudioStreamPlayerkeeping as many nodes as possible inside each scene, and all these problems go away:

  1. Each scene manages its own state information. If there is a problem with the data, it will only cause problems in that scene.

  2. Each scene only accesses its own node. Now, if there is an error, it is easy to find which node is at fault.

  3. Each scene allocates exactly the amount of resources it needs.

Manage shared functionality or data¶

Another reason to use autoloading might be that you want to reuse the same method or data in multiple scenarios.

In the case of functions, you can use the class_name keywordNode in GDScript to create a new type that provides that functionality for a single scene.

On the data side, you can:

  1. Create a new type of resource to share data.

  2. Store the data in an object that every node can access, for example using a property ownerto access the scene's root node.

When should you use autoloading¶

Autoloading nodes can simplify your code in some cases:

  • Static data : If you need data unique to a class, like a database, then autoloading is a great tool. There is no scripting API in Godot to create and manage static data.

  • Static functions : Create a library of functions that simply return a value.

  • Wide-ranging systems : If a singleton manages its own information without encroaching on other objects' data, then this is a good way to create systems that handle a wide range of tasks. For example, a quest or dialogue system.

Before Godot 3.1, another use was just for convenience: autoloads had a global variable with a name generated in GDScript, allowing you to call them from any script file in your project. But now, you can use class_namekeywords to get autocompletion for types throughout your project.

Note: Autoload is not exactly a singleton. There's nothing stopping you from instantiating a copy of an autoload node. It's just a tool to make nodes automatically load as children of the scene tree root, regardless of the game's node structure or which scene is running, e.g. by pressing F6.

So you can  Soundget autoloaded nodes by calling, for example, named get_node("/root/Sound")autoload.

When and how to avoid using nodes for everything¶

Nodes are cheap to produce, but even they have their limitations. A project may have tens of thousands of nodes doing things. However, the more complex their behavior, the more pressure each one puts on project performance.

Godot provides more lightweight objects to create APIs used by nodes. It's important to keep these in mind as options when designing how you want your project's functionality to be structured.

  1. Object  : The ultimate lightweight object, the original Object must use manual memory management. Having said that, it's not too hard to create your own custom data structures, and even the node structure is lighter than the Node class .

    • Example: View tree nodes. It supports advanced customization of directories with arbitrary number of rows and columns. The data it uses to generate the visualization is actually a tree of TreeItem objects.

    • Pros: Simplifying the API to a smaller scope of objects helps improve its accessibility and reduces iteration time. Rather than using the entire node library, create a simplified set of objects from which nodes can generate and manage appropriate child nodes.

    NOTE: Care should be taken when handling them. Objects can be stored into variables, but these references may become invalid without warning. For example, if the object's creator decides to delete it inexplicably, this will trigger an error state the next time it is accessed.

  2. RefCounted : Just a little more complicated than Object. They keep track of references to themselves, and only delete loaded memory when there are no further references to themselves. These are useful in most cases where data from a custom class is required.

    • Example: See the FileAccess object. It functions just like a normal object, except you don't need to delete it yourself.

    • Advantages: Same as Object.

  3. Resources : Only slightly more complex than RefCounted. They have an innate ability to serialize/deserialize (ie save and load) object properties to/from Godot resource files.

    • Example: Script, PackedScene (for scene files) and other types such as each AudioEffect class. Each of these can be saved and loaded, so they extend from resources.

    • Advantages: Much has been said  about the advantages of Resource over traditional data storage methods . However, in the context of using Resources over Nodes, their main advantage is Inspector compatibility. While almost as lightweight as Object/RefCounted, they can still display and export properties in the inspector. This allows them to serve a purpose similar to child nodes in terms of availability, but also improves performance if you plan to have many such resources/nodes in your scene.

Godot interface¶

One often needs to depend on scripts of other objects to get properties. There are two parts to this process:

  1. Get a reference to an object that may have these characteristics.

  2. Access data or logic from an object.

The rest of this tutorial outlines various ways to do all of this.

Get object reference¶

As with all Objects , the most basic way to refer to them is to obtain a reference to an existing object from another obtained instance.

var obj = node.object # Property access.
var obj = node.get_object() # Method access.

The same principle applies to RefCounted objects. Although users often access Node and  Resource in this way , alternative measures are available.

Instead of accessing properties or methods, resources can be obtained by loading access.

var preres = preload(path) # Load resource during scene load
var res = load(path) # Load resource when program reaches statement

# Note that users load scenes and scripts, by convention, with PascalCase
# names (like typenames), often into constants.
const MyScene : = preload("my_scene.tscn") as PackedScene # Static load
const MyScript : = preload("my_script.gd") as Script

# This type's value varies, i.e. it is a variable, so it uses snake_case.
export(Script) var script_type: Script

# If need an "export const var" (which doesn't exist), use a conditional
# setter for a tool script that checks if it's executing in the editor.
tool # Must place at top of file.

# Must configure from the editor, defaults to null.
export(Script) var const_script setget set_const_script
func set_const_script(value):
    if Engine.is_editor_hint():
        const_script = value

# Warn users if the value hasn't been set.
func _get_configuration_warning():
    if not const_script:
        return "Must initialize property 'const_script'."
    return ""

Please note the following:

  1. A language can load such resources in several ways.

  2. When designing the way objects access data, don't forget that resources can also be passed as references.

  3. Remember that loading a resource fetches a cached resource instance maintained by the engine. To get a new object, an existing reference must  be copied or used new().

Nodes also have an alternative access point: SceneTree.

extends Node

# Slow.
func dynamic_lookup_with_dynamic_nodepath():
    print(get_node("Child"))

# Faster. GDScript only.
func dynamic_lookup_with_cached_nodepath():
    print($Child)

# Fastest. Doesn't break if node moves later.
# Note that `@onready` annotation is GDScript only.
# Other languages must do...
#     var child
#     func _ready():
#         child = get_node("Child")
@onready var child = $Child
func lookup_and_cache_for_future_access():
    print(child)

# Delegate reference assignment to an external source.
# Con: need to perform a validation check.
# Pro: node makes no requirements of its external structure.
#      'prop' can come from anywhere.
var prop
func call_me_after_prop_is_initialized_by_parent():
    # Validate prop in one of three ways.

    # Fail with no notification.
    if not prop:
        return

    # Fail with an error message.
    if not prop:
        printerr("'prop' wasn't initialized")
        return

    # Fail and terminate.
    # Note: Scripts run from a release export template don't
    # run `assert` statements.
    assert(prop, "'prop' wasn't initialized")

# Use an autoload.
# Dangerous for typical nodes, but useful for true singleton nodes
# that manage their own data and don't interfere with other objects.
func reference_a_global_autoloaded_variable():
    print(globals)
    print(globals.prop)
    print(globals.my_getter())

Accessing data or logic from objects¶

Godot's scripting API is DUCK. This means that if a script performs an operation, Godot will not verify that it supports the type operation. Instead, it checks whether the object implements the individual methods.

For example, the CanvasItem class has a visible property. All properties exposed to the scripting API are actually setter and getter pairs bound to names. If someone tries to access  CanvasItem.visible , Godot does the following checks, in order:

  • If the object has a script attached, it will attempt to set the property via the script. This gives scripts the opportunity to override properties defined on the base object by overriding the property's setter methods.

  • If the script doesn't have that attribute, it does a HashMap lookup in ClassDB against the CanvasItem class and all of its inherited types to find the "visible" attribute. If found, it calls the bound setter or getter. For more information on HashMap, see  the Data Preferences documentation.

  • If not found, it does an explicit check to see if the user intended to access a "script" or "meta" attribute.

  • If not, it checks for /implementation in CanvasItem and its derived types _set(depending on the access type). _getThese methods can perform logic to give the impression that the object has properties. The same is true for methods _get_property_list.

    • Note that this happens even for illegal symbol names such as the "1/tile_name" attribute of a TileSet . This refers to the name of the tile with ID 1, ie  TileSet.tile_get_name(1).

As a result, this DUCK-type system can locate properties in scripts, in the object's class, or in any class the object inherits from, but is limited to things that extend the object.

Godot provides various options for performing runtime checks on these accesses:

  • Duck property access. These will be property inspections (mentioned above). If the object does not support the operation, execution will stop.

    # All Objects have duck-typed get, set, and call wrapper methods.
    get_parent().set("visible", false)
    
    # Using a symbol accessor, rather than a string in the method call,
    # will implicitly call the `set` method which, in turn, calls the
    # setter method bound to the property through the property lookup
    # sequence.
    get_parent().visible = false
    
    # Note that if one defines a _set and _get that describe a property's
    # existence, but the property isn't recognized in any _get_property_list
    # method, then the set() and get() methods will work, but the symbol
    # access will claim it can't find the property.
    
  • method check. In the case of CanvasItem.visible , these methods can be accessed set_visiblejust like is_visibleany other method.

    var child = get_child(0)
    
    # Dynamic lookup.
    child.call("set_visible", false)
    
    # Symbol-based dynamic lookup.
    # GDScript aliases this into a 'call' method behind the scenes.
    child.set_visible(false)
    
    # Dynamic lookup, checks for method existence first.
    if child.has_method("set_visible"):
        child.set_visible(false)
    
    # Cast check, followed by dynamic lookup.
    # Useful when you make multiple "safe" calls knowing that the class
    # implements them all. No need for repeated checks.
    # Tricky if one executes a cast check for a user-defined type as it
    # forces more dependencies.
    if child is CanvasItem:
        child.set_visible(false)
        child.show_on_top = true
    
    # If one does not wish to fail these checks without notifying users,
    # one can use an assert instead. These will trigger runtime errors
    # immediately if not true.
    assert(child.has_method("set_visible"))
    assert(child.is_in_group("offer"))
    assert(child is CanvasItem)
    
    # Can also use object labels to imply an interface, i.e. assume it
    # implements certain methods.
    # There are two types, both of which only exist for Nodes: Names and
    # Groups.
    
    # Assuming...
    # A "Quest" object exists and 1) that it can "complete" or "fail" and
    # that it will have text available before and after each state...
    
    # 1. Use a name.
    var quest = $Quest
    print(quest.text)
    quest.complete() # or quest.fail()
    print(quest.text) # implied new text content
    
    # 2. Use a group.
    for a_child in get_children():
        if a_child.is_in_group("quest"):
            print(quest.text)
            quest.complete() # or quest.fail()
            print(quest.text) # implied new text content
    
    # Note that these interfaces are project-specific conventions the team
    # defines (which means documentation! But maybe worth it?).
    # Any script that conforms to the documented "interface" of the name or
    # group can fill in for it.
    
  • Outsource access to Callable. These can be useful in situations where maximum freedom from dependencies is required. In this case, rely on the external context to set the method.

# child.gd
extends Node
var fn = null

func my_method():
    if fn:
        fn.call()

# parent.gd
extends Node

@onready var child = $Child

func _ready():
    child.fn = print_me
    child.my_method()

func print_me():
    print(name)

These strategies contribute to Godot's flexible design. Between them, users have a wide range of tools to meet their specific needs.

Godot Notifications¶

Every object in Godot implements a  _notification method. Its purpose is for the object to respond to various engine-level callbacks that may be associated with it. For example, if the engine tells  the CanvasItem to "draw", it will call the  _notification(NOTIFICATION_DRAW).

Some of these notifications, like paint, are useful to override in scripts. So much so that Godot exposes a number of functions with dedicated functions:

  • _ready():NOTIFICATION_READY

  • _enter_tree():NOTIFICATION_ENTER_TREE

  • _exit_tree():NOTIFICATION_EXIT_TREE

  • _process(delta):NOTIFICATION_PROCESS

  • _physics_process(delta):NOTIFICATION_PHYSICS_PROCESS

  • _draw():NOTIFICATION_DRAW

What users may not realize is that notifications exist for other types besides Node, for example:

Many of the callbacks that do exist in Nodes don't have any dedicated methods, but are still very useful.

_notificationAll these custom notifications can be accessed from common  methods.

NOTE: Methods marked as "virtual" in the documentation are also intended to be overridden by scripts.

A classic example is the _init method in Object . Although there is no  NOTIFICATION_*equivalent, the engine still calls the method. Most languages ​​(except C#) rely on it as a constructor.

So, when should these notifications or virtual functions be used?

_process and _physics_process and * _input¶

_processUsed when framerate-dependent delta time between frames is required. This is the right place if the code that updates the object's data needs to be updated as often as possible. Loop logic checks and data caching are usually performed here, but it comes down to how often updates need to be evaluated. If they don't need to execute every frame, then implementing a timer-yield-timeout loop is another option.

# Infinitely loop, but only execute whenever the Timer fires.
# Allows for recurring operations that don't trigger script logic
# every frame (or even every fixed frame).
while true:
    my_method()
    $Timer.start()
    yield($Timer, "timeout")

_physics_processUse when framerate-independent delta time between frames is desired. This is the right place if the code needs to be updated consistently over time, no matter how fast or slow time advances. Cyclic kinematics and object transformation operations should be performed here.

While possible, for best performance you should avoid input checking during these callbacks. _processand  _physics_processwill fire on every chance (they don't "rest" by default). Instead, *_inputthe callback will only fire on frames where the engine actually detects input.

The input action in the input callback can be checked similarly. If delta time is to be used, it can be obtained from the associated delta time method as needed.

# Called every frame, even when the engine detects no input.
func _process(delta):
    if Input.is_action_just_pressed("ui_select"):
        print(delta)

# Called during every input event.
func _unhandled_input(event):
    match event.get_class():
        "InputEventKey":
            if Input.is_action_just_pressed("ui_accept"):
                print(get_process_delta_time())

_init and initialization and export¶

If the script initializes its own node subtree without a scene, that code should execute here. Other properties or initializations independent of SceneTree should also run here. _readyThis fires before the or _enter_tree, but after the script is created and its properties initialized.

Three types of property assignments can occur during script instantiation:

# "one" is an "initialized value". These DO NOT trigger the setter.
# If someone set the value as "two" from the Inspector, this would be an
# "exported value". These DO trigger the setter.
export(String) var test = "one" setget set_test

func _init():
    # "three" is an "init assignment value".
    # These DO NOT trigger the setter, but...
    test = "three"
    # These DO trigger the setter. Note the `self` prefix.
    self.test = "three"

func set_test(value):
    test = value
    print("Setting: ", test)

When instantiating a scene, property values ​​are set in the following order:

  1. Initial value assignment: Instantiation will assign an initialization value or initial assignment value. Initialization assignments take precedence over initialization values.

  2. Export Value Assignment: If instantiated from a scene instead of a script, Godot will assign an export value to replace the initial value defined in the script.

Therefore, instantiating scripts and scenes will affect both initialization and the number of times the engine calls the setters.

_ready and _enter_tree and NOTIFICATION_PARENTED¶

When instantiating a scene connected to the first executing scene, Godot will instantiate nodes down the tree (make calls _init) and build the tree from the root down. This causes _enter_treecalls to cascade down the tree. Called by leaf nodes once the tree is complete _ready. Nodes will call this method once all child nodes have finished calling their methods. This then causes a reverse cascade back to the root of the tree.

When instantiating a script or stand-alone scene, nodes are not added to the SceneTree on creation, so _enter_treecallbacks are not fired. Instead, just _initcall . _enter_treeOccurs and is called when a scene is added to the SceneTree _ready.

If you need to trigger behavior that occurs as a parent of another node, whether or not it occurs as part of the main/active scene, you can use PARENTED notifications . For example, here's a snippet that connects a node's method to a custom signal on the parent node without failing. Useful for data-centric nodes that may be created at runtime.

extends Node

var parent_cache

func connection_check():
    return parent_cache.has_user_signal("interacted_with")

func _notification(what):
    match what:
        NOTIFICATION_PARENTED:
            parent_cache = get_parent()
            if connection_check():
                parent_cache.interacted_with.connect(_on_parent_interacted_with)
        NOTIFICATION_UNPARENTED:
            if connection_check():
                parent_cache.interacted_with.disconnect(_on_parent_interacted_with)

func _on_parent_interacted_with():
    print("I'm reacting to my parent's interaction!")

Data Preferences¶

Ever wondered whether data structure Y or Z should be used to solve problem X? This article covers a variety of topics related to these dilemmas.

Note: This article refers to the "[something]-time" operation. This term comes from Big O Notation of algorithm analysis  .

Long story short, it describes the worst case for runtime length. In layman's terms:

"As the size of the problem domain increases, the length of time the algorithm takes to run..."

  • Constant time, O(1): "...does not increase."

  • Logarithmic time,: "...increases at a slow rate."O(log n)

  • Linear time, O(n): "...increases at the same rate."

  • ETC。

Imagine if you had to process 3 million data points in one frame. It is not possible to use linear time algorithms to make features, because the sheer size of the data increases the running time far beyond the allocated time. In contrast, the operation can be handled without problems using constant-time algorithms.

In general, developers want to avoid engaging in linear time operations as much as possible. However, if the size of the linear time operation is kept small, and the operation does not need to be performed very often, then it may be acceptable. Balancing these requirements and choosing the right algorithm/data structure for the job is part of what makes a programmer's skill valuable.

Arrays vs Dictionaries vs Objects¶

Godot stores all variables in the scripting API in  the Variant class. Variants can store Variant-compatible data structures, such as  Array and Dictionary , and Objects .

Godot implements Array as Vector<Variant>. The engine stores array contents in contiguous parts of memory, i.e. they are next to each other in a row.

Note: For those unfamiliar with C++, Vector is the name for an array object in the traditional C++ library. It is a "templated" type, which means its records can only contain certain types (indicated by angle brackets). So, for example,  PackedStringArray is similar to Vector<String>.

Contiguous memory storage means the following operational performance:

  • Iteration: Fastest. Great for loops.

    • Op: All it does is increment a counter to get the next record.

  • Insert, erase, move: position dependent. Generally slower.

    • Op: Adding/deleting/moving content involves moving adjacent records (to make space/fill space).

    • Quickly add/remove from the end .

    • Slowly add/remove from any position .

    • Adding/removing from the front is the slowest.

    • If you do multiple insertions/removals from the front, then...

      1. Reverse the array.

      2. Do a loop and perform the array changes at the end .

      3. Reverse the array.

      This will only make 2 copies of the array (still constant time, but slower), while copying about 1/2 the array, on average, N times (linear time).

  • Get, Set: Fastest by location . For example, you can request records 0, 2, 10, etc., but you cannot specify which records you want.

    • Op: 1 addition operation from the beginning of the array to the desired index.

  • Lookup: Slowest. The index/position of the identity value.

    • Op: Have to iterate through the array and compare values ​​until a match is found.

      • Performance also depends on whether an exhaustive search is required.

    • A custom search operation can make it to logarithmic time (relatively fast) if kept in order. Lay users won't be happy with this, though. Do it by reordering the array after each edit and writing an order-aware search algorithm.

Godot implements Dictionary as a small array (initialized to 2^3 or 8 records) where the engine stores key-value pairs. When a person tries to access a value, they provide it with a key. Then it hashes the key, i.e. turns it into a number. "Hash" is used to calculate the index in the array. As an array, OHM then does a quick lookup in a "table" of keys mapped to values. When the HashMap gets too full, it increments to the next power of 2 (so, 16 records, then 32, etc.) and rebuilds the structure.OrderedHashMap<Variant, Variant>

Hashing is done to reduce the chance of key collisions. If this happens, the table has to recompute another index to get the value taking into account the previous position. Altogether, this results in constant-time access to all records at the expense of memory and some minor operational efficiencies.

  1. Each key is hashed any number of times.

    • Hashing is constant time, so even if an algorithm has to be done more than once, as long as the number of hashes is not too dependent on the density of the table, things will stay fast. This leads to...

  2. Keep the size of the table growing.

    • HashMaps intentionally keep gaps of unused memory scattered across the table to reduce hash collisions and maintain access speed. That's why its size keeps growing quadratically to the power of 2.

Dictionaries, as one might say, specialize in tasks that arrays are not good at. The details of their operation are outlined below:

  • Iterations: Fast.

    • Op: The internal hash vector of the iterated map. returns each key. Afterwards, the user then uses the key to jump to and return to the desired value.

  • Insert, erase, move: fastest.

    • Op: Hash the given key. Perform 1 addition to find the appropriate value (array start + offset). Moves are two of them (one insert, one erase). The map must undergo some maintenance to maintain its functionality:

      • Update an ordered list of records.

      • Determine if table density requirements require extended table capacity.

    • The dictionary remembers the order in which the user inserted its keys. This enables it to perform reliable iterations.

  • Get, Set: Fastest. Same as lookup by key .

    • Op: Same as Insert/Erase/Move.

  • Lookup: Slowest. A key that identifies a value.

    • Op: Have to iterate through records and compare values ​​until a match is found.

    • Note that Godot does not provide this functionality out of the box (as they are not suitable for this task).

Godot implements objects as dumb but dynamic data content containers. Objects query data sources when asking questions. For example, to answer the question "Do you have a property called 'position'?" it might ask its script or ClassDB . One can find out more about what objects are and how they work in the article Applying Object-Oriented Principles in Godot .

The important detail here is the complexity of the object task. Each time one of these multi-source queries is executed, it runs several  iteration loops and HashMap lookups. Also, queries are linear time operations, depending on the object's inheritance hierarchy size. If the class queried by Object (its current class) doesn't find anything, the request is deferred to the next base class, all the way to the original Object class. While these are fast operations in isolation, the fact that it has to do so many checks makes them slower than both alternatives for finding data.

Note: When developers mention how slow the scripting API is, they are referring to this query chain. Scripting API operations inevitably take longer than compiled C++ code where the application knows exactly where to look for anything. They must find the source of any relevant data before attempting to access it.

GDScript is slow because every operation it performs goes through this system.

C# can handle some things at a higher speed with more optimized bytecode. However, if a C# script calls the content of the engine class, or if the script tries to access content outside of it, it will go through this pipe.

NativeScript C++ goes a step further and keeps everything internal by default. Calls to external structures will be through the scripting API. In NativeScript C++, registering methods to expose them to the scripting API is a manual task. It is at this point that external non-C++ classes will use the API to locate them.

So, assuming one extends from reference to create a data structure like an array or a dictionary, why choose an object over the other two options?

  1. Control: With objects comes the ability to create more complex structures. Data can be abstracted hierarchically to ensure that external APIs do not change as internal data structures change. What's more, objects can have signals, allowing reactive behavior.

  2. Clarity: Objects are reliable sources of data when it comes to the data that script and engine classes define for them. A property might not contain the value we expect, but you don't need to worry about whether the property exists.

  3. Convenience: If one already has a similar data structure in mind, extending from an existing class makes the task of building the data structure much easier. In contrast, arrays and dictionaries don't satisfy every use case one might have.

Objects also give users the opportunity to create more specialized data structures. With it, one can design one's own lists, binary search trees, heaps, splay trees, graphs, disjoint sets, and any other options.

"Why not use Node for tree structures?" one might ask. Well, the Node class contains stuff that has nothing to do with custom data structures. Therefore, it can be helpful to build your own node types when building tree structures.

extends Object
class_name TreeNode

var _parent: TreeNode = null
var _children: = [] setget

func _notification(p_what):
    match p_what:
        NOTIFICATION_PREDELETE:
            # Destructor.
            for a_child in _children:
                a_child.free()

From here, people can create their own structures with specific functions, limited only by their imagination.

Enums: int and string¶

Most languages ​​provide options for enumerated types. GDScript is no different, but unlike most other languages, it allows integers or strings as enumeration values ​​(the latter only when using exportkeywords in GDScript). The question then becomes, "Which one should I use?"

The short answer is, "which one is more comfortable for you". This is a feature specific to GDScript, not Godot script in general; these languages ​​prioritize usability over performance.

On a technical level, integer comparisons (constant time) will happen faster than string comparisons (linear time). If you want to keep the conventions of other languages, you should use integers.

 The main problem with using integers arises when wanting to print enum values. As integers, trying to print MY_ENUM will print  5or what have you, instead of something like. "MyEnum"To print integer enums, a dictionary must be written to map each enum's corresponding string value.

If the main purpose of using enums is to print values, and you want to group them together as related concepts, then it makes sense to use them as strings. This way, there is no need for a separate data structure to perform printing.

AnimatedTexture vs. AnimatedSprite2D vs. AnimationPlayer vs. AnimationTree

When should each of Godot's animation classes be used? The answer may not be immediately clear to new Godot users.

AnimatedTextures are textures that the engine draws as animation loops rather than static images. Users can manipulate...

  1. The rate (fps) at which it moves through each part of the texture.

  2. The number of regions contained in the texture (frame).

Godot's RenderingServer then renders regions sequentially at a specified rate. The good news is that this involves no extra logic on the part of the engine. The bad news is that users have very little control.

Note also that, unlike the other node objects discussed here , AnimatedTexture is a resource . It is possible to create a Sprite2D node that uses an AnimatedTexture as its texture . Or (something no one else can do) you can add AnimatedTextures as tiles in a TileSet and integrate it with  a TileMap for many automatically animated backgrounds, all rendered in a single batch draw call.

The AnimatedSprite2D node combined with  the SpriteFrames asset allows one to create various animation sequences through spritesheets, flip between animations, and control their speed, area offset, and orientation. This makes them ideal for controlling 2D frame-based animations.

If you need to trigger other effects related to animation changes (such as creating particle effects, calling functions, or manipulating other peripheral elements besides frame-based animations), you need to use the AnimationPlayer node with AnimatedSprite2D .

If you want to design a more complex 2D animation system, for example, AnimationPlayer is also the tool you need to use.

  1. Cut Animation: Edit the transform of a sprite at runtime.

  2. 2D Mesh Animation: Define regions and rig a skeleton for a sprite's texture. Then animate the skeleton, stretching and bending the texture according to the relationship between the bones.

  3. A mix of the above.

While an AnimationPlayer is required for each individual animation sequence designed for the game, it is also useful to combine animations for blending, that is, to achieve smooth transitions between them. There may also be a hierarchy between animations designed for objects. These are where AnimationTree shines. An in-depth guide on using AnimationTree can be found here .

Logical Preferences¶

Ever wondered whether strategy Y or strategy Z should be used to solve problem X? This article covers a variety of topics related to these dilemmas.

Adding nodes and changing properties: which comes first?

When initializing a node from a script at runtime, you may need to change properties such as the node name or location. A common dilemma is when should you change these values?

It is best practice to change the value on a node before adding it to the scene tree. Some properties' setters have code that updates other corresponding values, and that code can be slow! In most cases this code will not affect your game performance, but in heavy use cases like procedural generation it can slow down your game.

For these reasons, it is always best practice to set a node's initial value before adding it to the scene tree.

Loading and preloading¶

In GDScript, there is a global  preload method. It loads resources early to advance "load" operations and avoid loading resources in the middle of performance-sensitive code.

Its counterpart, the load method, loads the resource only when a load statement is reached. That is, it will load resources in-place, which can cause slowdowns when it happens in the middle of sensitive processes. This function is also an alias for ResourceLoader.load(path) accessible by all scripting languages .load

So, when exactly do preloading and loading happen, and when should one of them be used? Let's look at an example:

# my_buildings.gd
extends Node

# Note how constant scripts/scenes have a different naming scheme than
# their property variants.

# This value is a constant, so it spawns when the Script object loads.
# The script is preloading the value. The advantage here is that the editor
# can offer autocompletion since it must be a static path.
const BuildingScn = preload("res://building.tscn")

# 1. The script preloads the value, so it will load as a dependency
#    of the 'my_buildings.gd' script file. But, because this is a
#    property rather than a constant, the object won't copy the preloaded
#    PackedScene resource into the property until the script instantiates
#    with .new().
#
# 2. The preloaded value is inaccessible from the Script object alone. As
#    such, preloading the value here actually does not benefit anyone.
#
# 3. Because the user exports the value, if this script stored on
#    a node in a scene file, the scene instantiation code will overwrite the
#    preloaded initial value anyway (wasting it). It's usually better to
#    provide null, empty, or otherwise invalid default values for exports.
#
# 4. It is when one instantiates this script on its own with .new() that
#    one will load "office.tscn" rather than the exported value.
export(PackedScene) var a_building = preload("office.tscn")

# Uh oh! This results in an error!
# One must assign constant values to constants. Because `load` performs a
# runtime lookup by its very nature, one cannot use it to initialize a
# constant.
const OfficeScn = load("res://office.tscn")

# Successfully loads and only when one instantiates the script! Yay!
var office_scn = load("res://office.tscn")

Preloading allows the script to handle all loading when the script is loaded. Preloading is useful, but there are times when it's not desirable. To differentiate these situations, consider the following:

  1. Preloading assets (especially scenes or scripts) can cause further loads than expected if there is no way to determine when the script is loaded. This can cause unintentional, variable-length load times on top of the original script's load operation.

  2. There's no point in preloading the value if something else can replace it (like a scene's export initialization). This is not an important factor if you plan to always create the scripts yourself.

  3. If you just want to "import" another class asset (script or scene), then using preload constants is usually the best practice. However, there are special cases where one might not want to do this:

    1. If the "imported" class is subject to change, then it should be a property, exportinitialized with an or a load(maybe not even initialized until later).

    2. If your script needs a lot of dependencies and you don't want to consume so much memory, you may want to load and unload various dependencies at runtime as the situation changes. If resources are preloaded into constants, the only way to unload those resources is to unload the entire script. If they are loaded properties, you can set them nulland completely remove all references to the resource (as a  RefCounted extension type, this will cause the resource to remove themselves from memory).

Big Levels: Static vs. Dynamic¶

If you want to create a large level, which situation is the most appropriate? Should they create the level as a static space? Or should they load the level in pieces and move the world content as needed?

Well, the short answer is, "when performance demands it". The dilemma associated with these two options is an ancient programming choice: does one optimize for memory over speed, or the other way around?

The naive answer is to use static levels that load everything at once. However, depending on the project, this can consume a lot of memory. Wasting the user's RAM can cause programs to run slowly or crash completely with all the other things the computer is trying to do at the same time.

Regardless, larger scenes should be broken down into smaller ones (to aid reusability of assets). Developers can then design a node to manage the creation/loading and deletion/unloading of resources and nodes in real-time. Games with large and varied environments or procedurally generated elements will often implement these strategies to avoid wasting memory.

On the other hand, dynamic systems are more complex to code, i.e. use more programming logic, which leads to bugs and opportunities for errors. If they're not careful, they can develop a system that bloats the application's technical debt.

Therefore, your best bet is to...

  1. Use static levels for small games.

  2. If anyone has the time/resources to play a medium/large game, please create a library or plugin to code the management of nodes and resources. If improved over time to improve usability and stability, it can evolve into a reliable tool across projects.

  3. Writing dynamic logic code for a medium/large game, because one has coding skills but no time or resources to perfect the code (the game has to be finished). Might be refactored later to outsource the code into a plugin.

See the Manually Changing Scenes documentation for examples of the various ways to swap scenes at runtime  .

Project Organization¶

Introduction¶ _

Since Godot has no restrictions on project structure or usage of the file system, organizing files when learning the engine can seem challenging. The workflow suggested by this tutorial should be a good starting point. We'll also cover how to use Godot for version control.

Organization¶ _

Godot is inherently scene-based and uses the filesystem as-is, with no metadata or asset database.

Unlike other engines, many assets are contained within the scene itself, so the number of files in the file system is much smaller.

With this in mind, the most common approach is to group assets as close to the scene as possible; it makes it easier to maintain as the project grows.

For example, people can often put their basic assets (such as sprite images, 3D model meshes, materials and music, etc.) in a folder. They can then use a separate folder to store build levels that use them.

/project.godot
/docs/.gdignore  # See "Ignoring specific folders" below
/docs/learning.html
/models/town/house/house.dae
/models/town/house/window.png
/models/town/house/door.png
/characters/player/cubio.dae
/characters/player/cubio.png
/characters/enemies/goblin/goblin.dae
/characters/enemies/goblin/goblin.png
/characters/npcs/suzanne/suzanne.dae
/characters/npcs/suzanne/suzanne.png
/levels/riverdale/riverdale.scn

Fashion Guide¶

For consistency across projects, we recommend following these guidelines:

  • Use snake_case for folder and file names (except for C# scripts). This avoids case sensitivity issues that can arise after exporting projects on Windows. C# scripts are an exception to this rule, because the convention is to name them after the class names that should be in PascalCase.

  • Use PascalCase for node names , as this matches the built-in node case.

  • In general, keep third-party resources in the top-level addons/folder, even if they are not editor plugins. This makes it easier to track which files are third-party files. There are some exceptions to this rule; for example, if you're using third-party game assets for your character, it makes more sense to include them in the same folder as your character's scenes and scripts.

Input¶ _

Versions of Godot prior to 3.0 performed the import process from files outside the project. While this is useful in larger projects, it creates an organizational headache for most developers.

As a result, assets can now be transparently imported from the project folder.

Ignore specific folders¶

To prevent Godot from importing files contained in a specific folder, create an empty file in that folder called (requires .gdignorethe leading). .This is useful for speeding up the initial project import.

notes

To create a file with a name starting with a dot on Windows, you can use a text editor such as Notepad++ or use the following command at the command prompt:type nul > .gdignore

Once a folder is ignored, resources in that folder can no longer be used load()and preload()method loaded. Ignoring a folder also automatically hides it from the file system dock, which is useful for reducing clutter.

Note that .gdignorethe contents of the file will be ignored, which is why the file should be empty. It does not .gitignoresupport schemas like files do.

Case Sensitivity¶

Windows and recent macOS versions use a case-insensitive file system by default, while Linux distributions use a case-sensitive file system by default. This can cause problems after exporting the project, since Godot's PCK virtual file system is case sensitive. To avoid this, it is recommended to stick to  snake_casenaming all files in the project (usually with lowercase characters).

Note: You can violate this rule when the style guide states otherwise (such as the C# style guide). Still, be consistent to avoid mistakes.

On Windows 10, to further avoid errors related to case sensitivity, you can also make project folders case sensitive. After enabling the Windows Subsystem for Linux feature, run the following command in a PowerShell window:

# To enable case-sensitivity:
fsutil file setcasesensitiveinfo <path to project folder> enable

# To disable case-sensitivity:
fsutil file setcasesensitiveinfo <path to project folder> disable

If you haven't enabled the Windows Subsystem for Linux, you can enter the following line into a PowerShell window run as administrator , then reboot when asked:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

Version control system¶

Introduction¶ _

Godot aims to be VCS friendly and produce mostly readable and mergeable files. Godot also supports version control systems in the editor. However, VCS in the editor requires a plugin for the specific VCS you are using. VCS can be set or turned off in the editor under Project > Version Control.

​​

Official Git Plugins¶

The official plugin supports using Git from within the editor. You can find the latest version here . Documentation on how to use the Git plugin can be found here .

Files to exclude from VCS¶

Godot automatically creates some files and folders. You should add them to your VCS ignore:

  • .godot/: This folder stores various project cache data. .godot/imported/All files automatically imported by the storage engine based on your source assets and their import flags. .godot/editor/Holds data about the state of the editor, such as currently opened script files and recently used nodes.

  • *.translation: These files are binary import translations generated from CSV files.

  • export_presets.cfg: This file contains all export presets for the project, including sensitive information such as Android keystore credentials.

  • .mono/: This folder stores automatically generated mono files. It only exists in projects using the Mono version of Godot.

NOTE: Save this .gitignore file in the root folder of your project to automatically set file exclusions.

Using Git on Windows¶

Most Git for Windows clients are configured core.autocrlfwith true. This can cause files to be unnecessarily marked as modified by Git because their line endings are automatically converted. It is best to set this option to:

git config --global core.autocrlf input

Known Issues¶

Always close the editorgit pull before running ! Otherwise,  you may lose data if you sync the file while the editor is open .

Guess you like

Origin blog.csdn.net/drgraph/article/details/130815182