logic_preferences.rst 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. .. _doc_logic_preferences:
  2. Logic preferences
  3. =================
  4. Ever wondered whether one should approach problem X with strategy Y or Z?
  5. This article covers a variety of topics related to these dilemmas.
  6. Adding nodes and changing properties: which first?
  7. --------------------------------------------------
  8. When initializing nodes from a script at runtime, you may need to change
  9. properties such as the node's name or position. A common dilemma is, when
  10. should you change those values?
  11. It is the best practice to change values on a node before adding it to the
  12. scene tree. Some property's setters have code to update other
  13. corresponding values, and that code can be slow! For most cases, this code
  14. has no impact on your game's performance, but in heavy use cases such as
  15. procedural generation, it can bring your game to a crawl.
  16. For these reasons, it is usually best practice to set the initial values
  17. of a node before adding it to the scene tree. There are some exceptions where
  18. values *can't* be set before being added to the scene tree, like setting global
  19. position.
  20. Loading vs. preloading
  21. ----------------------
  22. In GDScript, there exists the global
  23. :ref:`preload <class_@GDScript_method_preload>` method. It loads resources as
  24. early as possible to front-load the "loading" operations and avoid loading
  25. resources while in the middle of performance-sensitive code.
  26. Its counterpart, the :ref:`load <class_@GDScript_method_load>` method, loads a
  27. resource only when it reaches the load statement. That is, it will load a
  28. resource in-place which can cause slowdowns when it occurs in the middle of
  29. sensitive processes. The ``load()`` function is also an alias for
  30. :ref:`ResourceLoader.load(path) <class_ResourceLoader_method_load>` which is
  31. accessible to *all* scripting languages.
  32. So, when exactly does preloading occur versus loading, and when should one use
  33. either? Let's see an example:
  34. .. tabs::
  35. .. code-tab:: gdscript GDScript
  36. # my_buildings.gd
  37. extends Node
  38. # Note how constant scripts/scenes have a different naming scheme than
  39. # their property variants.
  40. # This value is a constant, so it spawns when the Script object loads.
  41. # The script is preloading the value. The advantage here is that the editor
  42. # can offer autocompletion since it must be a static path.
  43. const BuildingScn = preload("res://building.tscn")
  44. # 1. The script preloads the value, so it will load as a dependency
  45. # of the 'my_buildings.gd' script file. But, because this is a
  46. # property rather than a constant, the object won't copy the preloaded
  47. # PackedScene resource into the property until the script instantiates
  48. # with .new().
  49. #
  50. # 2. The preloaded value is inaccessible from the Script object alone. As
  51. # such, preloading the value here actually does not benefit anyone.
  52. #
  53. # 3. Because the user exports the value, if this script stored on
  54. # a node in a scene file, the scene instantiation code will overwrite the
  55. # preloaded initial value anyway (wasting it). It's usually better to
  56. # provide null, empty, or otherwise invalid default values for exports.
  57. #
  58. # 4. It is when one instantiates this script on its own with .new() that
  59. # one will load "office.tscn" rather than the exported value.
  60. @export var a_building : PackedScene = preload("office.tscn")
  61. # Uh oh! This results in an error!
  62. # One must assign constant values to constants. Because `load` performs a
  63. # runtime lookup by its very nature, one cannot use it to initialize a
  64. # constant.
  65. const OfficeScn = load("res://office.tscn")
  66. # Successfully loads and only when one instantiates the script! Yay!
  67. var office_scn = load("res://office.tscn")
  68. .. code-tab:: csharp
  69. using Godot;
  70. // C# and other languages have no concept of "preloading".
  71. public partial class MyBuildings : Node
  72. {
  73. //This is a read-only field, it can only be assigned when it's declared or during a constructor.
  74. public readonly PackedScene Building = ResourceLoader.Load<PackedScene>("res://building.tscn");
  75. public PackedScene ABuilding;
  76. public override void _Ready()
  77. {
  78. // Can assign the value during initialization.
  79. ABuilding = GD.Load<PackedScene>("res://Office.tscn");
  80. }
  81. }
  82. .. code-tab:: cpp C++
  83. using namespace godot;
  84. class MyBuildings : public Node {
  85. GDCLASS(MyBuildings, Node)
  86. public:
  87. const Ref<PackedScene> building = ResourceLoader::get_singleton()->load("res://building.tscn");
  88. Ref<PackedScene> a_building;
  89. virtual void _ready() override {
  90. // Can assign the value during initialization.
  91. a_building = ResourceLoader::get_singleton()->load("res://office.tscn");
  92. }
  93. };
  94. Preloading allows the script to handle all the loading the moment one loads the
  95. script. Preloading is useful, but there are also times when one doesn't wish
  96. for it. To distinguish these situations, there are a few things one can
  97. consider:
  98. 1. If one cannot determine when the script might load, then preloading a
  99. resource, especially a scene or script, could result in further loads one
  100. does not expect. This could lead to unintentional, variable-length
  101. load times on top of the original script's load operations.
  102. 2. If something else could replace the value (like a scene's exported
  103. initialization), then preloading the value has no meaning. This point isn't
  104. a significant factor if one intends to always create the script on its own.
  105. 3. If one wishes only to 'import' another class resource (script or scene),
  106. then using a preloaded constant is often the best course of action. However,
  107. in exceptional cases, one may wish not to do this:
  108. 1. If the 'imported' class is liable to change, then it should be a property
  109. instead, initialized either using an ``@export`` or a ``load()`` (and
  110. perhaps not even initialized until later).
  111. 2. If the script requires a great many dependencies, and one does not wish
  112. to consume so much memory, then one may wish to, load and unload various
  113. dependencies at runtime as circumstances change. If one preloads
  114. resources into constants, then the only way to unload these resources
  115. would be to unload the entire script. If they are instead loaded
  116. properties, then one can set them to ``null`` and remove all references
  117. to the resource entirely (which, as a
  118. :ref:`RefCounted <class_RefCounted>`-extending type, will cause the
  119. resources to delete themselves from memory).
  120. Large levels: static vs. dynamic
  121. --------------------------------
  122. If one is creating a large level, which circumstances are most appropriate?
  123. Should they create the level as one static space? Or should they load the
  124. level in pieces and shift the world's content as needed?
  125. Well, the simple answer is, "when the performance requires it." The
  126. dilemma associated with the two options is one of the age-old programming
  127. choices: does one optimize memory over speed, or vice versa?
  128. The naive answer is to use a static level that loads everything at once.
  129. But, depending on the project, this could consume a large amount of
  130. memory. Wasting users' RAM leads to programs running slow or outright
  131. crashing from everything else the computer tries to do at the same time.
  132. No matter what, one should break larger scenes into smaller ones (to aid
  133. in reusability of assets). Developers can then design a node that manages the
  134. creation/loading and deletion/unloading of resources and nodes in real-time.
  135. Games with large and varied environments or procedurally generated
  136. elements often implement these strategies to avoid wasting memory.
  137. On the flip side, coding a dynamic system is more complex, i.e. uses more
  138. programmed logic, which results in opportunities for errors and bugs. If one
  139. isn't careful, they can develop a system that bloats the technical debt of
  140. the application.
  141. As such, the best options would be...
  142. 1. To use a static level for smaller games.
  143. 2. If one has the time/resources on a medium/large game, create a library or
  144. plugin that can code the management of nodes and resources. If refined
  145. over time, so as to improve usability and stability, then it could evolve
  146. into a reliable tool across projects.
  147. 3. Code the dynamic logic for a medium/large game because one has the coding
  148. skills, but not the time or resources to refine the code (game's
  149. gotta get done). Could potentially refactor later to outsource the code
  150. into a plugin.
  151. For an example of the various ways one can swap scenes around at runtime,
  152. please see the :ref:`"Change scenes manually" <doc_change_scenes_manually>`
  153. documentation.