逻辑首选项

有没有想过应该用策略y还是z来处理问题x?本文涵盖了与这些困境相关的各种主题。

加载与预加载

在gdscript中,存在全局 preload 方法。它尽可能早地加载资源,以预先加载“加载”操作,并避免在性能敏感的代码中间加载资源。

它的对应物 load 方法,只在资源到达LOAD语句时加载该资源。也就是说,它将在适当的位置加载一个资源,这会导致速度变慢,然后发生在敏感进程的中间。这个 load 函数也是的别名 ResourceLoader.load(path) 可供 all 脚本语言。

那么,预加载与加载的确切时间是什么时候,应该在什么时候使用它们呢?让我们看一个例子:

# 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 instantation 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");
    }
}

预加载允许脚本在加载脚本时处理所有加载。预加载是有用的,但有时也有人不希望这样做。要区分这些情况,可以考虑以下几点:

  1. 如果无法确定何时加载脚本,则预加载资源(尤其是场景或脚本)可能会导致无法预期的进一步加载。这可能会导致在原始脚本的加载操作之上出现意外的、可变长度的加载时间。

  2. 如果其他东西可以替换该值(如场景的导出初始化),则预加载该值没有意义。如果一个人总是打算自己创建脚本,那么这一点不是一个重要的因素。

  3. 如果只希望“导入”另一个类资源(脚本或场景),那么使用预加载常量通常是最佳的操作过程。但是,在特殊情况下,我不希望这样做:

    1. 如果“imported”类易于更改,则它应该是一个属性,可以使用 export 或A load (可能直到后来才初始化)。

    2. 如果脚本需要大量依赖项,并且不希望消耗这么多内存,那么您可能希望在运行时随着环境的变化加载和卸载各种依赖项。如果将资源预加载到常量中,那么卸载这些资源的唯一方法就是卸载整个脚本。如果它们是加载的属性,则可以将它们设置为 null 并完全删除对资源的所有引用(作为 Reference -扩展类型,将导致资源从内存中删除自己)。

大级别:静态与动态

如果一个人正在创造一个大的层次,哪种情况最合适?他们应该将级别创建为一个静态空间吗?或者他们应该把这个级别分块加载,并根据需要改变世界的内容吗?

好吧,简单的答案是,“当性能需要它的时候。”这两个选项所带来的困境是一个由来已久的编程选择:一个优化内存,还是相反?

天真的答案是使用一个静态级别,一次加载所有内容。但是,根据项目的不同,这可能会消耗大量的内存。浪费用户的RAM会导致程序运行缓慢或完全崩溃,这是计算机试图同时执行的其他操作的结果。

不管怎样,应该将较大的场景分成较小的场景(以帮助资产的可重用性)。然后,开发人员可以设计一个节点来实时管理资源和节点的创建/加载和删除/卸载。具有大而多样的环境或程序生成元素的游戏通常执行这些策略以避免浪费内存。

另一方面,对动态系统进行编码更为复杂,即使用更多的编程逻辑,这会导致出现错误和错误的机会。如果不小心,他们可以开发一个系统来增加应用程序的技术债务。

因此,最好的选择是…

  1. 对较小的游戏使用静态级别。

  2. 如果一个人在中型/大型游戏中有时间/资源,那么就创建一个库或插件,可以对节点和资源的管理进行编码。如果随着时间的推移而改进,以提高可用性和稳定性,那么它就可以演变为跨项目的可靠工具。

  3. 为中型/大型游戏编写动态逻辑代码,因为人们有编码技能,但没有时间或资源来完善代码(游戏必须完成)。可能稍后重构以将代码外包到插件中。

有关在运行时交换场景的各种方法的示例,请参见 "Change scenes manually" 文档。