Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Préférences de logique

Vous êtes-vous déjà demandé si vous deviez approcher un problème X avec une stratégie Y ou Z ? Cet article couvre une grande variété de sujets relatifs à ce dilemme.

Adding nodes and changing properties: which first?

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

It is the best practice to change values on a node before adding it to the scene tree. Some property's setters have code to update other corresponding values, and that code can be slow! For most cases, this code has no impact on your game's performance, but in heavy use cases such as procedural generation, it can bring your game to a crawl.

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

Chargement vs préchargement

Dans GDScript, il existe la méthode globale preload. elle charge les ressources le plus tôt possible pour charger en amont les opérations de "chargement" et éviter de charger les ressources au milieu d'un code sensible aux performances.

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

Donc, quand exactement y a-t-il un préchargement plutôt qu'un chargement, et quand utiliser l'un ou l'autre ? Voyons un exemple :

# 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")

Le préchargement permet au script de gérer tout le chargement dès le chargement du script lui-même. Le préchargement est utile, mais il y a aussi des moments où on ne le souhaite pas. Pour distinguer ces situations, il y a quelques points à considérer :

  1. S'il est impossible de déterminer le moment du chargement du script, le préchargement d'une ressource, en particulier d'une scène ou d'un script, peut entraîner des charges supplémentaires auxquelles on ne s'attend pas. Cela pourrait entraîner des temps de chargement non intentionnels de longueur variable en plus des opérations de chargement du script d'origine.

  2. Si quelque chose d'autre pourrait remplacer la valeur (comme l'initialisation d'une scène exportée), alors le préchargement de la valeur n'a aucune intérêt. Ce point n'est pas un facteur important si l'on a l'intention de toujours créer le script tout seul.

  3. Si l'on souhaite seulement 'importer' une autre ressource de classe (script ou scène), alors l'utilisation d'une constante préchargée est souvent la meilleure solution. Cependant, dans des cas exceptionnels, il n'est pas souhaitable de le faire :

    1. If the 'imported' class is liable to change, then it should be a property instead, initialized either using an export or a load() (and perhaps not even initialized until later).

    2. If the script requires a great many dependencies, and one does not wish to consume so much memory, then one may wish to, load and unload various dependencies at runtime as circumstances change. If one preloads resources into constants, then the only way to unload these resources would be to unload the entire script. If they are instead loaded properties, then one can set them to null and remove all references to the resource entirely (which, as a RefCounted-extending type, will cause the resources to delete themselves from memory).

Grands niveaux : statique vs dynamique

Dans le cadre de la création d'un niveau de grande taille, quelle solution est la plus appropriée ? Créer le niveau d'une seule pièce, ou bien scinder ce niveau en fragments qui seront chargés au fur et à mesure des besoins ?

Well, the simple answer is, "when the performance requires it." The dilemma associated with the two options is one of the age-old programming choices: does one optimize memory over speed, or vice versa?

La réponse naïve est d'utiliser un seul niveau et charger toutes les ressources en une fois. Toutefois, dans le cadre de certains projets, cette solution peut mener à l'utilisation d'une importante quantité de mémoire. Gaspiller la mémoire de l'utilisateur risque de ralentir, voire de faire planter les autres programmes en cours d’exécution sur son système.

Il est vivement recommandé de séparer les scènes de grande taille en plus petites scènes (aidant ainsi à la ré utilisabilité des assets). Les développeurs peuvent ensuite concevoir un nœud dont la tâche est de gérer la création/le chargement et la suppression/le déchargement des ressources et nœuds correspondants en temps-réel. Les jeux disposant d'un environnement étendu et varié, ou générés de façon procédurale implémentent souvent ces stratégies dans un soucis d'optimisation de la mémoire.

Néanmoins, la conception d'un système dynamique de ce type est plus complexe car il nécessite plus de programmation, ce qui favorise l'apparition de bugs. Il faudra donc prendre garde à ne pas développer un système dont la complexité pourrait mener à l'opposé du résultat recherché.

Ainsi, les meilleures options seraient....

  1. Utiliser un niveau unique pour les petits jeux.

  2. En fonction du temps et des ressources disponibles pour la création d'un jeu de taille moyenne à étendue, la création d'une librairie ou d'un plugin pour simplifier la gestion des nœuds et ressources. Cette librairie/plugin pourrait être amélioré et évoluer en un outil fiable au fil des projets.

  3. Implémenter la logique de chargement dynamique directement dans le jeu dans le cadre d'un projet de taille moyenne/étendue dont les délais ne permettent pas l'élaboration d'une solution plus élégante. Cette implémentation pourrait par la suite être externalisée sous forme de plugin, lorsque le temps nécessaire est disponible.

Pour trouver des exemples des différentes façons existantes pour charger les scènes de façon dynamique à l’exécution, on peut se référer à la documentation suivante : "Change scenes manually".