ロジックの設定
問題Xにアプローチするときに、戦略をYにするかZするかについて悩んだことはありませんか?この記事では、これらのジレンマに関連するさまざまなトピックについて説明します。
ノードの追加とプロパティの変更はどちらが先?
実行時にスクリプトからノードを初期化する場合、ノードの名前や位置などのプロパティを変更する必要がある場合があります。よくあるジレンマは、いつそれらの値を変更すべきか?
シーンツリーに追加する前にノードの値を変更するのがベストプラクティスです。一部のプロパティのセッターには、他の対応する値を更新するコードがあり、そのコードは遅くなる可能性があります。ほとんどの場合、このコードはゲームのパフォーマンスに影響を与えませんが、プロシージャル生成などの使用頻度の高いケースでは、ゲームが遅くなる可能性があります。
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 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")
using Godot;
// C# and other languages have no concept of "preloading".
public partial 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");
}
}
using namespace godot;
class MyBuildings : public Node {
GDCLASS(MyBuildings, Node)
public:
const Ref<PackedScene> building = ResourceLoader::get_singleton()->load("res://building.tscn");
Ref<PackedScene> a_building;
virtual void _ready() override {
// Can assign the value during initialization.
a_building = ResourceLoader::get_singleton()->load("res://office.tscn");
}
};
preload により、スクリプトは、スクリプトを読み込んだ瞬間にすべての読み込みを処理できます。preload は便利ですが、それを望まない場合もあります。これらの状況を区別するために、考慮できることがいくつかあります。
スクリプトがいつロードされるかを判断できない場合、リソース、特にシーンまたはスクリプトをプリロードすると、予期しない追加のロードが発生する可能性があります。これにより、元のスクリプトのロード操作に加えて、意図しない可変長のロード時間が発生する可能性があります。
シーンのエクスポートされた初期化など、他の何かが値を置き換えることができる場合、値をプリロードしても意味がありません。スクリプトを常に独自に作成する場合、この点は重要な要素ではありません。
別のクラスリソース (スクリプトまたはシーン) を「インポート」するだけの場合は、プリロードされた定数を使用するのが最善の策となることがよくあります。ただし例外的なケースでは、これを行わない方がよい場合があります:
「インポートされた」クラスが変更される可能性がある場合は、代わりにプロパティとして
@exportまたはload()を使用して初期化する必要があります (場合によっては、後から初期化されることもあります)。スクリプトに非常に多くの依存関係が必要で、メモリをあまり消費したくない場合は、状況の変化に応じて実行時にさまざまな依存関係をロードおよびアンロードしたい場合があります。リソースを定数にプリロードする場合、これらのリソースをアンロードする唯一の方法は、スクリプト全体をアンロードすることです。代わりにロードされたプロパティの場合は、それらを
nullに設定して、リソースへのすべての参照を完全に削除できます (RefCounted 拡張型として、リソースがメモリから削除されます)。
大きなレベル(ステージ): 静的レベル 対 動的レベル
大きなレベルを作成している場合、どの状況が最も適切ですか?レベルを1つの静的な空間として作成する必要がありますか?または、レベルを断片的にロードし、必要に応じてワールドのコンテンツをシフトする必要がありますか?
答えは「パフォーマンスが必要な場合」です。この2つのオプションに関連するジレンマは、昔からあるプログラミング上の選択の1つです。速度よりもメモリを最適化するのか、それともその逆なのか?
素朴な答えは、すべてを一度にロードする静的レベルを使用することです。ただし、プロジェクトによっては、大量のメモリを消費する可能性があります。ユーザーのRAMを浪費すると、コンピュータが同時に実行しようとする他のすべてのプログラムが、低速化または完全にクラッシュしてしまいます。
何があっても、大きなシーンを小さなシーンに分割する必要があります(資産の再利用性を高めるため)。開発者は、リソースとノードの作成/ロード、削除/アンロードをリアルタイムで管理するノードを設計できます。大規模で多様な環境や手続き的に生成された要素を持つゲームは、多くの場合、メモリの無駄を避けるためにこれらの戦略を実装します。
裏を返せば、動的システムのコーディングはより複雑です。つまり、より多くのプログラムされたロジックを使用するため、エラーやバグが発生する可能性があります。注意しないと、アプリケーションの技術的負債を膨らませるシステムを開発してしまいます。
そのため、最良のオプションは...
小さなゲームには静的レベルを使用する。
中規模または大規模のゲームでは、時間/リソースが確保できる場合は、ノードとリソースの管理をコーディングできるライブラリまたはプラグインを作成します。使いやすさと安定性を改善するために時間の経過とともに改良されれば、プロジェクト全体で信頼性の高いツールに進化する可能性があります。
コーディングスキルはあるが、コードを絞り込むための時間やリソースは持っていないため、中規模/大規模ゲームの動的ロジックをコーディングします(ゲームを完成させる必要があるので)。後でリファクタリングして、コードをプラグインにアウトソースできます。
実行時にシーンを入れ替えるさまざまな方法の例については、「手動でシーンを変更する」 ドキュメントを参照してください。