ロジックの設定

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

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

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.

それでは、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(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");
    }
}

preload により、スクリプトは、スクリプトを読み込んだ瞬間にすべての読み込みを処理できます。preload は便利ですが、それを望まない場合もあります。これらの状況を区別するために、考慮できることがいくつかあります。

  1. スクリプトがいつロードされるかを判断できない場合、リソース、特にシーンまたはスクリプトをプリロードすると、予期しない追加のロードが発生する可能性があります。これにより、元のスクリプトのロード操作に加えて、意図しない可変長のロード時間が発生する可能性があります。
  2. シーンのエクスポートされた初期化など、他の何かが値を置き換えることができる場合、値をプリロードしても意味がありません。スクリプトを常に独自に作成する場合、この点は重要な要素ではありません。
  3. 別のクラスリソース(スクリプトまたはシーン)のみを「インポート」したい場合は、多くの場合、プリロードされた定数を使用することが最善のアクションです。ただし、例外的なケースでは、これを行いたくない場合もあります:
    1. 'imported' クラスが変更される可能性がある場合は、代わりにプロパティとして export または load を使用して初期化する必要があります(そして、後で初期化されることはありません)。
    2. スクリプトが非常に多くの依存関係を必要とし、あまり多くのメモリを消費したくない場合、状況の変化に応じて実行時にさまざまな依存関係をロードおよびアンロードすることができます。リソースを定数にプリロードする場合、これらのリソースをアンロードする唯一の方法は、スクリプト全体をアンロードすることです。それらが代わりにプロパティとしてロードされた場合、それらを null に設定し、リソースへのすべての参照を完全に削除できます(これは Reference 拡張型として、リソースがメモリから自分自身を削除します)。

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

大きなレベルを作成している場合、どの状況が最も適切ですか?レベルを1つの静的な空間として作成する必要がありますか?または、レベルを断片的にロードし、必要に応じてワールドのコンテンツをシフトする必要がありますか?

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

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

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

裏を返せば、動的システムのコーディングはより複雑です。つまり、より多くのプログラムされたロジックを使用するため、エラーやバグが発生する可能性があります。注意しないと、アプリケーションの技術的負債を膨らませるシステムを開発してしまいます。

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

  1. 小さなゲームには静的レベルを使用する。
  2. 中規模または大規模のゲームでは、時間/リソースが確保できる場合は、ノードとリソースの管理をコーディングできるライブラリまたはプラグインを作成します。使いやすさと安定性を改善するために時間の経過とともに改良されれば、プロジェクト全体で信頼性の高いツールに進化する可能性があります。
  3. コーディングスキルはあるが、コードを絞り込むための時間やリソースは持っていないため、中規模/大規模ゲームの動的ロジックをコーディングします(ゲームを完成させる必要があるので)。後でリファクタリングして、コードをプラグインにアウトソースできます。

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