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.
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.
Sua contraparte, o método load, carrega um recurso somente quando ele atinge a instrução de carregamento. Ou seja, ele carregará um recurso no local que pode causar lentidão quando ele ocorre no meio de processos sensíveis. A função load
também é um apelido para ResourceLoader.load(path) que é acessível a todas as linguagens de scripting.
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")
using System;
using Godot;
// C# and other languages have no concept of "preloading".
public class MyBuildings : Node
{
//This is a read-only field, it can only be assigned when it's declared or during a constructor.
public readonly PackedScene Building = ResourceLoader.Load<PackedScene>("res://building.tscn");
public PackedScene ABuilding;
public override void _Ready()
{
// Can assign the value during initialization.
ABuilding = GD.Load<PackedScene>("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:
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.
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.
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:
Se a classe 'importada' estiver sujeita a mudanças, então ela deve ser uma propriedade, inicializada utilizando um
export
ou umload
(e talvez nem mesmo inicializada até mais tarde).Se o script requer muitas dependências, e não se deseja consumir tanta memória, então pode-se desejar carregar e descarregar várias dependências durante a execução, conforme as circunstâncias mudam. Se alguém pré-carregar recursos em constantes, então a única maneira de descarregar estes recursos seria descarregar o script inteiro. Se ao invés disso eles são carregados, então pode-se defini-los como
null
e remover todas as referências ao recurso por completo (o que, como um tipo de extensão Referência, fará com que os recursos se apaguem da memória).
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?
Bem, a resposta simples é "quando o desempenho assim o exigir." O dilema associado às duas opções é uma das antigas opções de programação: a memória é otimizada em relação à velocidade ou 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...
Para usar uma fase estática para jogos menores.
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.
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".