scenes_versus_scripts.rst 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. .. _doc_scenes_versus_scripts:
  2. When to use scenes versus scripts
  3. =================================
  4. We've already covered how scenes and scripts are different. Scripts
  5. define an engine class extension with imperative code, scenes with
  6. declarative code.
  7. Each system's capabilities are different as a result.
  8. Scenes can define how an extended class initializes, but not what its
  9. behavior actually is. Scenes are often used in conjunction with a script,
  10. the scene declaring a composition of nodes, and the script adding behaviour with imperative code.
  11. Anonymous types
  12. ---------------
  13. It *is* possible to completely define a scenes' contents using a script alone.
  14. This is, in essence, what the Godot Editor does, only in the C++ constructor
  15. of its objects.
  16. But, choosing which one to use can be a dilemma. Creating script instances
  17. is identical to creating in-engine classes whereas handling scenes requires
  18. a change in API:
  19. .. tabs::
  20. .. code-tab:: gdscript GDScript
  21. const MyNode = preload("my_node.gd")
  22. const MyScene = preload("my_scene.tscn")
  23. var node = Node.new()
  24. var my_node = MyNode.new() # Same method call.
  25. var my_scene = MyScene.instantiate() # Different method call.
  26. var my_inherited_scene = MyScene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene.
  27. .. code-tab:: csharp
  28. using Godot;
  29. public partial class Game : Node
  30. {
  31. public static CSharpScript MyNode { get; } =
  32. GD.Load<CSharpScript>("res://Path/To/MyNode.cs");
  33. public static PackedScene MyScene { get; } =
  34. GD.Load<PackedScene>("res://Path/To/MyScene.tscn");
  35. private Node _node;
  36. private Node _myNode;
  37. private Node _myScene;
  38. private Node _myInheritedScene;
  39. public Game()
  40. {
  41. _node = new Node();
  42. _myNode = MyNode.New().As<Node>();
  43. // Different than calling new() or MyNode.New(). Instantiated from a PackedScene.
  44. _myScene = MyScene.Instantiate();
  45. // Create scene inheriting from MyScene.
  46. _myInheritedScene = MyScene.Instantiate(PackedScene.GenEditState.Main);
  47. }
  48. }
  49. Also, scripts will operate a little slower than scenes due to the
  50. speed differences between engine and script code. The larger and more complex
  51. the node, the more reason there is to build it as a scene.
  52. Named types
  53. -----------
  54. Scripts can be registered as a new type within the editor
  55. itself. This displays it as a new type in the node or resource creation dialog
  56. with an optional icon. This way, the user's ability to use the script
  57. is much more streamlined. Rather than having to...
  58. 1. Know the base type of the script they would like to use.
  59. 2. Create an instance of that base type.
  60. 3. Add the script to the node.
  61. With a registered script, the scripted type instead becomes a creation option
  62. like the other nodes and resources in the system.
  63. The creation dialog even has a search bar to look up the type by
  64. name.
  65. There are two systems for registering types:
  66. - :ref:`Custom Types <doc_making_plugins>`
  67. - Editor-only. Typenames are not accessible at runtime.
  68. - Does not support inherited custom types.
  69. - An initializer tool. Creates the node with the script. Nothing more.
  70. - Editor has no type-awareness of the script or its relationship
  71. to other engine types or scripts.
  72. - Allows users to define an icon.
  73. - Works for all scripting languages because it deals with Script resources in abstract.
  74. - Set up using :ref:`EditorPlugin.add_custom_type <class_EditorPlugin_method_add_custom_type>`.
  75. - :ref:`Script Classes <doc_gdscript_basics_class_name>`
  76. - Editor and runtime accessible.
  77. - Displays inheritance relationships in full.
  78. - Creates the node with the script, but can also change types
  79. or extend the type from the editor.
  80. - Editor is aware of inheritance relationships between scripts,
  81. script classes, and engine C++ classes.
  82. - Allows users to define an icon.
  83. - Engine developers must add support for languages manually (both name exposure and
  84. runtime accessibility).
  85. - The Editor scans project folders and registers any exposed names for all
  86. scripting languages. Each scripting language must implement its own
  87. support for exposing this information.
  88. Both methodologies add names to the creation dialog, but script classes, in
  89. particular, also allow for users to access the typename without loading the
  90. script resource. Creating instances and accessing constants or static methods
  91. is viable from anywhere.
  92. With features like these, one may wish their type to be a script without a
  93. scene due to the ease of use it grants users. Those developing plugins or
  94. creating in-house tools for designers to use will find an easier time of things
  95. this way.
  96. On the downside, it also means having to use largely imperative programming.
  97. Performance of Script vs PackedScene
  98. ------------------------------------
  99. One last aspect to consider when choosing scenes and scripts is execution speed.
  100. As the size of objects increases, the scripts' necessary size to create and
  101. initialize them grows much larger. Creating node hierarchies demonstrates this.
  102. Each Node's logic could be several hundred lines of code in length.
  103. The code example below creates a new ``Node``, changes its name, assigns a
  104. script to it, sets its future parent as its owner so it gets saved to disk along
  105. with it, and finally adds it as a child of the ``Main`` node:
  106. .. tabs::
  107. .. code-tab:: gdscript GDScript
  108. # main.gd
  109. extends Node
  110. func _init():
  111. var child = Node.new()
  112. child.name = "Child"
  113. child.script = preload("child.gd")
  114. add_child(child)
  115. child.owner = self
  116. .. code-tab:: csharp
  117. using Godot;
  118. public partial class Main : Node
  119. {
  120. public Node Child { get; set; }
  121. public Main()
  122. {
  123. Child = new Node();
  124. Child.Name = "Child";
  125. var childID = Child.GetInstanceId();
  126. Child.SetScript(GD.Load<Script>("res://Path/To/Child.cs"));
  127. // SetScript() causes the C# wrapper object to be disposed, so obtain a new
  128. // wrapper for the Child node using its instance ID before proceeding.
  129. Child = (Node)GodotObject.InstanceFromId(childID);
  130. AddChild(Child);
  131. Child.Owner = this;
  132. }
  133. }
  134. Script code like this is much slower than engine-side C++ code. Each instruction
  135. makes a call to the scripting API which leads to many "lookups" on the back-end
  136. to find the logic to execute.
  137. Scenes help to avoid this performance issue. :ref:`PackedScene
  138. <class_PackedScene>`, the base type that scenes inherit from, defines resources
  139. that use serialized data to create objects. The engine can process scenes in
  140. batches on the back-end and provide much better performance than scripts.
  141. Conclusion
  142. ----------
  143. In the end, the best approach is to consider the following:
  144. - If one wishes to create a basic tool that is going to be re-used in several
  145. different projects and which people of all skill levels will likely use
  146. (including those who don't label themselves as "programmers"), then chances
  147. are that it should probably be a script, likely one with a custom name/icon.
  148. - If one wishes to create a concept that is particular to their game, then it
  149. should always be a scene. Scenes are easier to track/edit and provide more
  150. security than scripts.
  151. - If one would like to give a name to a scene, then they can still sort of do
  152. this by declaring a script class and giving it a scene as a constant.
  153. The script becomes, in effect, a namespace:
  154. .. tabs::
  155. .. code-tab:: gdscript GDScript
  156. # game.gd
  157. class_name Game # extends RefCounted, so it won't show up in the node creation dialog.
  158. extends RefCounted
  159. const MyScene = preload("my_scene.tscn")
  160. # main.gd
  161. extends Node
  162. func _ready():
  163. add_child(Game.MyScene.instantiate())
  164. .. code-tab:: csharp
  165. // Game.cs
  166. public partial class Game : RefCounted
  167. {
  168. public static PackedScene MyScene { get; } =
  169. GD.Load<PackedScene>("res://Path/To/MyScene.tscn");
  170. }
  171. // Main.cs
  172. public partial class Main : Node
  173. {
  174. public override void _Ready()
  175. {
  176. AddChild(Game.MyScene.Instantiate());
  177. }
  178. }