逻辑偏好

有没有想过应该用数据结构Y还是Z,来处理问题X ?本文涵盖了与这些困境有关的各种主题.

加载VS预加载

在GDScript中,存在全局 preload 方法.它尽可能早地加载资源,以便提前进行 加载 操作,并避免在执行性能敏感的代码时加载资源.

其对应的 load 方法,只有在到达load语句时才会加载资源.也就是说,它将立即加载资源,当它发生在敏感进程中时,会造成速度减慢. load 函数也是 ResourceLoader.load(path) 的别名, 所有 脚本语言都可以访问.

那么,预加载和加载到底在什么时候发生,又应该什么时候使用这两种方法呢?我们来看一个例子:

GDScript

C#

  1. # my_buildings.gd
  2. extends Node
  3. # Note how constant scripts/scenes have a different naming scheme than
  4. # their property variants.
  5. # This value is a constant, so it spawns when the Script object loads.
  6. # The script is preloading the value. The advantage here is that the editor
  7. # can offer autocompletion since it must be a static path.
  8. const BuildingScn = preload("res://building.tscn")
  9. # 1. The script preloads the value, so it will load as a dependency
  10. # of the 'my_buildings.gd' script file. But, because this is a
  11. # property rather than a constant, the object won't copy the preloaded
  12. # PackedScene resource into the property until the script instantiates
  13. # with .new().
  14. #
  15. # 2. The preloaded value is inaccessible from the Script object alone. As
  16. # such, preloading the value here actually does not benefit anyone.
  17. #
  18. # 3. Because the user exports the value, if this script stored on
  19. # a node in a scene file, the scene instantiation code will overwrite the
  20. # preloaded initial value anyway (wasting it). It's usually better to
  21. # provide null, empty, or otherwise invalid default values for exports.
  22. #
  23. # 4. It is when one instantiates this script on its own with .new() that
  24. # one will load "office.tscn" rather than the exported value.
  25. export(PackedScene) var a_building = preload("office.tscn")
  26. # Uh oh! This results in an error!
  27. # One must assign constant values to constants. Because `load` performs a
  28. # runtime lookup by its very nature, one cannot use it to initialize a
  29. # constant.
  30. const OfficeScn = load("res://office.tscn")
  31. # Successfully loads and only when one instantiates the script! Yay!
  32. var office_scn = load("res://office.tscn")
  1. using System;
  2. using Godot;
  3. // C# and other languages have no concept of "preloading".
  4. public class MyBuildings : Node
  5. {
  6. //This is a read-only field, it can only be assigned when it's declared or during a constructor.
  7. public readonly PackedScene Building = ResourceLoader.Load<PackedScene>("res://building.tscn");
  8. public PackedScene ABuilding;
  9. public override void _Ready()
  10. {
  11. // Can assign the value during initialization.
  12. ABuilding = GD.Load<PackedScene>("res://office.tscn");
  13. }
  14. }

预加载允许脚本在加载脚本时处理所有加载.预加载是有用的,但也有一些时候,人们并不希望这样.为了区分这些情况,我们可以考虑以下几点:

  1. 如果无法确定何时可以加载脚本,则预加载资源,尤其是场景或脚本,可能会导致进一步加载,这是人们所不希望的.这可能会导致无意中,在原始脚本的加载操作之上的可变长度加载时间.在原始脚本的加载操作之上,这可能导致意外的、可变长度的加载时间.

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

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

    1. 如果 ‘导入’ 的类有可能发生变化,那么它应该是一个属性,使用 exportload 进行初始化(甚至可能以后才初始化).

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

大型关卡:静态VS动态

如果正在创建一个大型关卡,哪种情况是最合适的?他们应该将关卡创建为一个静态空间吗?还是他们应该分阶段加载关卡,并根据需要改变世界的内容?

答案很简单,”当性能需要的时候.”与这两种选择有关的困境,是一种古老的编程选择:是否会优化内存而不是速度,反之亦然?

最简单的方法是使用静态关卡,它可以一次加载所有内容.但是,这取决于项目,这可能会消耗大量内存.浪费用户的运行内存会导致程序运行缓慢,或者计算机在同一时间尝试做的所有其他事情都会崩溃.

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

另一方面,对动态系统进行编码更复杂,即,使用更多的编程逻辑,这会导致出现错误和bug的机会.如果不小心的话,开发的系统,会增加应用程序的技术成本.

因此,最好的选择是…

  1. 在小型游戏中使用静态关卡.

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

  3. 为一款中/大型游戏编写动态逻辑代码,因为你拥有编程技能,但却没有时间或资源去完善代码(必须要完成游戏).以后可能会进行重构,将代码外包到插件中.

有关在运行时中,可以交换场景的各种方式的示例,请参见文档 手动更改场景 .