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 існує глобальний метод попереднє завантаження. Він завантажує ресурси якомога раніше, щоб попередньо завантажити операції "завантаження" і уникати завантаження ресурсів, перебуваючи в середині коду, що чутливий до продуктивності.

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

Попереднє завантаження дозволяє скрипту обробляти все завантаження в момент завантаження скрипта. Попереднє завантаження корисно, але бувають випадки, коли ніхто цього не бажає. Щоб розрізнити ці ситуації, можна розглянути кілька ситуацій:

  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. Кодуйте динамічну логіку для середньої/великої гри, якщо маєте навички кодування, але не час або ресурси для вдосконалення коду (гру потрібно завершити). Пізніше це може потенційно рефакторировать, щоб передати код на плагін.

Для прикладу різних способів обміну сценами під час виконання дивіться документацію "Зміна сцен вручну".