Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

ロジックの設定

問題Xにアプローチするときに、戦略をYにするかZするかについて悩んだことはありませんか?この記事では、これらのジレンマに関連するさまざまなトピックについて説明します。

ノードの追加とプロパティの変更はどちらが先?

実行時にスクリプトからノードを初期化する場合、ノードの名前や位置などのプロパティを変更する必要がある場合があります。よくあるジレンマは、いつそれらの値を変更すべきか?

It is the best practice to change values on a node before adding it to the scene tree. Some properties' 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 usually best practice to set the initial values of a node before adding it to the scene tree. There are some exceptions where values can't be set before being added to the scene tree, like setting global position.

ロード(load) 対 プリロード(preload)

GDScript には、グローバル preload メソッドが存在します。"読み込み" 処理を前もって行い、パフォーマンスに敏感なコードの途中で、リソースの読み込が発生することを回避するために、できるだけ早くリソースを読み込みます。

対応する load メソッドは、load ステートメントに到達したときにのみリソースをロードします。つまりリソースをその場でロードするため、リアルタイム性の高いプロセスの途中で行うと速度低下を引き起こす可能性があります。 load() 関数は、すべてのスクリプト言語からアクセスできる ResourceLoader.load(path) のエイリアスでもあります。

それでは、load と比較して preload が発生するのは正確にはいつですか?例を見てみましょう:

# 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 provide any benefit.
#
# 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. Instantiating the script on its own with .new() triggers
#    `load("office.tscn")`, ignoring any value set through the export.
@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")

Preloading allows the script to handle all the loading the moment one loads the script. Preloading is useful, but there are also times when one doesn't wish to use it. Here are a few considerations when determining which to use:

  1. If one cannot determine when the script might load, then preloading a resource (especially a scene or script) could result in additional loads one does not expect. This could lead to unintentional, variable-length load times on top of the original script's load operations.

  2. シーンのエクスポートされた初期化など、他の何かが値を置き換えることができる場合、値をプリロードしても意味がありません。スクリプトを常に独自に作成する場合、この点は重要な要素ではありません。

  3. 別のクラスリソース (スクリプトまたはシーン) を「インポート」するだけの場合は、プリロードされた定数を使用するのが最善の策となることがよくあります。ただし例外的なケースでは、これを行わない方がよい場合があります:

    1. 「インポートされた」クラスが変更される可能性がある場合は、代わりにプロパティとして @export または load() を使用して初期化する必要があります (場合によっては、後から初期化されることもあります)。

    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 as properties, then one can set these properties to null and remove all references to the resource (which, as a RefCounted-extending type, will cause the resources to delete themselves from memory).

大きなレベル(ステージ): 静的レベル 対 動的レベル

If one is creating a large level, which circumstances are most appropriate? Is it better to create the level as one static space? Or is it better to load the level in pieces and shift the world's content as needed?

答えは「パフォーマンスが必要な場合」です。この2つのオプションに関連するジレンマは、昔からあるプログラミング上の選択の1つです。速度よりもメモリを最適化するのか、それともその逆なのか?

素朴な答えは、すべてを一度にロードする静的レベルを使用することです。ただし、プロジェクトによっては、大量のメモリを消費する可能性があります。ユーザーのRAMを浪費すると、コンピュータが同時に実行しようとする他のすべてのプログラムが、低速化または完全にクラッシュしてしまいます。

何があっても、大きなシーンを小さなシーンに分割する必要があります(資産の再利用性を高めるため)。開発者は、リソースとノードの作成/ロード、削除/アンロードをリアルタイムで管理するノードを設計できます。大規模で多様な環境や手続き的に生成された要素を持つゲームは、多くの場合、メモリの無駄を避けるためにこれらの戦略を実装します。

On the flip side, coding a dynamic system is more complex; it uses more programmed logic which results in opportunities for errors and bugs. If one isn't careful, they can develop a system that bloats the technical debt of the application.

そのため、最良のオプションは...

  1. Use static levels for smaller games.

  2. If one has the time/resources on a medium/large game, create a library or plugin that can manage nodes and resources with code. If refined over time so as to improve usability and stability, then it could evolve into a reliable tool across projects.

  3. Use dynamic logic for a medium/large game because one has the coding skills, but not the time or resources to refine the code (game's gotta get done). Could potentially refactor later to outsource the code into a plugin.

実行時にシーンを入れ替えるさまざまな方法の例については、「手動でシーンを変更する」 ドキュメントを参照してください。