Up to date

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

Preferências de lógica

Já se perguntou se devemos abordar o problema X com a estratégia Y ou Z? Este artigo cobre uma variedade de tópicos relacionados a esses dilemas.

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.

Carregamento vs. pré-carregamento

No GDScript, existe o método global preload. Ele carrega recursos o mais cedo possível para antecipar as operações de "carregamento" e evitar o carregamento de recursos no meio de um código sensível ao desempenho.

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.

Então, quando exatamente ocorre o pré-carregamento versus carregamento, e quando se deve usar qualquer algum deles? Vejamos um exemplo:

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

O pré-carregamento permite que o script manuseie todo o carregamento no momento em que se carrega o script. Pré-carregar é útil, mas também há momentos em que não se deseja. Para distinguir estas situações há algumas coisas que podemos considerar:

  1. Se não for possível determinar quando o script pode carregar, então pré-carregar um recurso, especialmente uma cena ou script, pode resultar em carregamentos adicionais inesperados. Isso pode levar a tempos de carregamento não intencionais e de comprimento variável em cima das operações de carregamento do script.

  2. Se algo mais pudesse substituir o valor (como a inicialização exportada de uma cena), então pre-carregar o valor não teria sentido. Este ponto não é um fator significativo se a intenção é sempre criar o script por conta própria.

  3. Se alguém deseja apenas 'importar' outro recurso de classe (script ou cena), usar uma constante pré-carregada é geralmente o melhor curso de ação. No entanto, em casos excepcionais, não se deseja fazer isso:

    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).

Fases grandes: estática vs. dinâmica

Se alguém está criando uma fase grande, quais circunstâncias são mais apropriadas? Eles deveriam criar a fase como um espaço estático? Ou eles deveriam carregar a fase em pedaços e mudar o conteúdo do mundo conforme necessário?

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?

A resposta ingênua é usar uma fase estática que carrega tudo ao mesmo tempo. Mas, dependendo do projeto, isto pode consumir uma grande quantidade de memória. Desperdiçar a RAM dos usuários leva a programas executarem devagar ou travamento total de tudo o que o computador tenta fazer ao mesmo tempo.

Não importa o que aconteça. deve-se quebrar cenas maiores em cenas menores (para ajudar na reutilização de assets). Desenvolvedores podem então projetar um nó que gerencia criação/carregamento e exclusão/descarregamento de recursos e nós em tempo real. Jogos com ambientes variáveis e grandes ou elementos gerados proceduralmente geralmente implementam estas estratégias para evitar o desperdício de memória.

Por outro lado, codificar um sistema dinâmico é mais complexo, ou seja, usa lógica mais programada, o que resulta em oportunidades para erros e bugs. Se não tomar cuidado, é possível desenvolver um sistema que incha a dívida técnica da aplicação.

Sendo assim, as melhores opções seriam...

  1. Para usar uma fase estática para jogos menores.

  2. Se alguém tiver tempo/recursos em um jogo médio/grande, crie uma biblioteca ou plugin que possa codificar a gestão de nós e recursos. Se aperfeiçoada ao longo do tempo, de modo a melhorar a usabilidade e estabilidade, então ela pode evoluir para uma ferramenta confiável em todos os projetos.

  3. Codificar a lógica dinâmica para um jogo médio/grande porque se tem as habilidades de codificação, mas não o tempo ou recursos para refinar o código (o jogo tem que ser feito). Poderia potencialmente refatorar mais tarde para terceirizar o código em um plugin.

Para um exemplo das várias formas de trocar cenas durante a execução, por favor veja a documentação "Mudar cenas manualmente".