Up to date

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

Настройки логики

Вы когда-нибудь задумывались, следует ли подходить к проблеме X со стратегией Y или Z? В этой статье рассматриваются различные темы, связанные с этими дилеммами.

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.

Загрузка и предварительная загрузка

В GDScript существует глобальный метод preload. Он загружает ресурсы как можно раньше, чтобы предварительно загрузить «загрузочные» операции и избежать загрузки ресурсов в середине кода, чувствительного к производительности.

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.

Итак, когда именно происходит предварительная загрузка во время загрузки и когда следует ее использовать? Посмотрим на пример:

# 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 var a_building : PackedScene = 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")

Предварительная загрузка позволяет сценарию обрабатывать всю загрузку в момент загрузки сценария. Предварительная загрузка полезна, но бывают случаи, когда этого не хочется. Чтобы различать эти ситуации, следует учесть несколько вещей:

  1. Если невозможно определить, когда может загрузиться сценарий, то предварительная загрузка ресурса, особенно сцены или сценария, может привести к дальнейшим загрузкам, которых нельзя было ожидать. Это может привести к непреднамеренной загрузке переменной длины поверх операций загрузки исходного сценария.

  2. Если что-то еще может заменить значение (например, экспортированная инициализация сцены), то предварительная загрузка значения не имеет смысла. Этот момент не является существенным фактором, если кто-то намеревается всегда создавать сценарий самостоятельно.

  3. Если кто-то хочет только 'импортировать' ресурс другого класса (скрипт или сцену), то использование предварительно загруженной константы часто является лучшим способом действий. Однако в исключительных случаях одно мое желание не делать этого:

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

Большие уровни: статические против динамических

Если кто-нибудь создает большой уровень, какие обстоятельства являются самыми подходящими? Должны ли они создавать уровень как одно статичное пространство? Или они должны загружать уровень по частям и перемещать содержимое мира по мере необходимости?

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?

Наивный ответ - использовать статический уровень, который загружает все сразу. Но, в зависимости от проекта, это может потреблять большой объем памяти. Исчерпание оперативной памяти пользователей приводит к тому, что программы работают медленно или полностью вылетают из-за всего остального, что компьютер пытается делать одновременно.

Несмотря ни на что, нужно разбивать большие сцены на более мелкие (чтобы способствовать повторному использованию ресурсов). Затем разработчики могут проектировать узел, который управляет созданием/загрузкой и удалением/выгрузкой ресурсов и узлов в режиме реального времени. Игры с большими и разнообразными средами или процедурно генерируемыми элементами часто реализуют эти стратегии, чтобы избежать траты памяти.

С другой стороны, кодирование динамической системы является более сложным, т.к. использует больше программируемой логики, которая приводит к возможностям для ошибок и багов. Если кто-то не будет осторожен, они могут разработать систему, которая раздувает технический долг приложения.

Таким образом, лучшими вариантами были бы ...

  1. Использовать статические уровни для небольших игр.

  2. Если у вас есть время / ресурсы для средней / большой игры, создайте библиотеку или плагин, который может кодировать управление узлами и ресурсами. Если со временем доработать, чтобы улучшить удобство использования и стабильность, то он может превратиться в надежный инструмент для всех проектов.

  3. Кодируйте динамическую логику для средней / большой игры, потому что у вас есть навыки кодирования, но нет времени или ресурсов для уточнения кода (игра должна быть завершена). Позже потенциально можно провести рефакторинг, чтобы передать код в плагин.

Примеры различных способов, которыми можно менять местами сцены во время выполнения, можно найти в документации «Изменить сцены вручную».