2
0

saving_games.rst 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. .. _doc_saving_games:
  2. Saving games
  3. ============
  4. Introduction
  5. ------------
  6. Save games can be complicated. It can be desired to store more
  7. information than the current level or number of stars earned on a level.
  8. More advanced save games may need to store additional information about
  9. an arbitrary number of objects. This will allow the save function to
  10. scale as the game grows more complex.
  11. Identify persistent objects
  12. ---------------------------
  13. Firstly, we should identify what objects we want to keep between game
  14. sessions and what information we want to keep from those objects. For
  15. this tutorial, we will use groups to mark and handle objects to be saved,
  16. but other methods are certainly possible.
  17. We will start by adding objects we wish to save to the "Persist" group.
  18. As in the :ref:`doc_scripting_continued` tutorial, we can do this through
  19. either the GUI or script. Let's add the relevant nodes using the GUI:
  20. .. image:: img/groups.png
  21. Once this is done, when we need to save the game, we can get all objects
  22. to save them and then tell them all to save with this script:
  23. .. tabs::
  24. .. code-tab:: gdscript GDScript
  25. var save_nodes = get_tree().get_nodes_in_group("Persist")
  26. for i in save_nodes:
  27. # Now, we can call our save function on each node.
  28. .. code-tab:: csharp
  29. var saveNodes = GetTree().GetNodesInGroup("Persist");
  30. foreach (Node saveNode in saveNodes)
  31. {
  32. // Now, we can call our save function on each node.
  33. }
  34. Serializing
  35. -----------
  36. The next step is to serialize the data. This makes it much easier to
  37. read from and store to disk. In this case, we're assuming each member of
  38. group Persist is an instanced node and thus has a path. GDScript
  39. has helper functions for this, such as :ref:`to_json()
  40. <class_@GDScript_method_to_json>` and :ref:`parse_json()
  41. <class_@GDScript_method_parse_json>`, so we will use a dictionary. Our node needs to
  42. contain a save function that returns this data. The save function will look
  43. like this:
  44. .. tabs::
  45. .. code-tab:: gdscript GDScript
  46. func save():
  47. var save_dict = {
  48. "filename" : get_filename(),
  49. "parent" : get_parent().get_path(),
  50. "pos_x" : position.x, # Vector2 is not supported by JSON
  51. "pos_y" : position.y,
  52. "attack" : attack,
  53. "defense" : defense,
  54. "current_health" : current_health,
  55. "max_health" : max_health,
  56. "damage" : damage,
  57. "regen" : regen,
  58. "experience" : experience,
  59. "tnl" : tnl,
  60. "level" : level,
  61. "attack_growth" : attack_growth,
  62. "defense_growth" : defense_growth,
  63. "health_growth" : health_growth,
  64. "is_alive" : is_alive,
  65. "last_attack" : last_attack
  66. }
  67. return save_dict
  68. .. code-tab:: csharp
  69. public Dictionary<object, object> Save()
  70. {
  71. return new Dictionary<object, object>()
  72. {
  73. { "Filename", GetFilename() },
  74. { "Parent", GetParent().GetPath() },
  75. { "PosX", Position.x }, // Vector2 is not supported by JSON
  76. { "PosY", Position.y },
  77. { "Attack", Attack },
  78. { "Defense", Defense },
  79. { "CurrentHealth", CurrentHealth },
  80. { "MaxHealth", MaxHealth },
  81. { "Damage", Damage },
  82. { "Regen", Regen },
  83. { "Experience", Experience },
  84. { "Tnl", Tnl },
  85. { "Level", Level },
  86. { "AttackGrowth", AttackGrowth },
  87. { "DefenseGrowth", DefenseGrowth },
  88. { "HealthGrowth", HealthGrowth },
  89. { "IsAlive", IsAlive },
  90. { "LastAttack", LastAttack }
  91. };
  92. }
  93. This gives us a dictionary with the style
  94. ``{ "variable_name":value_of_variable }``, which will be useful when
  95. loading.
  96. Saving and reading data
  97. -----------------------
  98. As covered in the :ref:`doc_filesystem` tutorial, we'll need to open a file
  99. and write to it and then later, read from it. Now that we have a way to
  100. call our groups and get their relevant data, let's use :ref:`to_json()
  101. <class_@GDScript_method_to_json>` to
  102. convert it into an easily stored string and store them in a file. Doing
  103. it this way ensures that each line is its own object, so we have an easy
  104. way to pull the data out of the file as well.
  105. .. tabs::
  106. .. code-tab:: gdscript GDScript
  107. # Note: This can be called from anywhere inside the tree. This function is
  108. # path independent.
  109. # Go through everything in the persist category and ask them to return a
  110. # dict of relevant variables
  111. func save_game():
  112. var save_game = File.new()
  113. save_game.open("user://savegame.save", File.WRITE)
  114. var save_nodes = get_tree().get_nodes_in_group("Persist")
  115. for i in save_nodes:
  116. var node_data = i.call("save");
  117. save_game.store_line(to_json(node_data))
  118. save_game.close()
  119. .. code-tab:: csharp
  120. // Note: This can be called from anywhere inside the tree. This function is
  121. // path independent.
  122. // Go through everything in the persist category and ask them to return a
  123. // dict of relevant variables
  124. public void SaveGame()
  125. {
  126. var saveGame = new File();
  127. saveGame.Open("user://savegame.save", (int)File.ModeFlags.Write);
  128. var saveNodes = GetTree().GetNodesInGroup("Persist");
  129. foreach (Node saveNode in saveNodes)
  130. {
  131. var nodeData = saveNode.Call("Save");
  132. saveGame.StoreLine(JSON.Print(nodeData));
  133. }
  134. saveGame.Close();
  135. }
  136. Game saved! Loading is fairly simple as well. For that, we'll read each
  137. line, use parse_json() to read it back to a dict, and then iterate over
  138. the dict to read our values. But we'll need to first create the object
  139. and we can use the filename and parent values to achieve that. Here is our
  140. load function:
  141. .. tabs::
  142. .. code-tab:: gdscript GDScript
  143. # Note: This can be called from anywhere inside the tree. This function
  144. # is path independent.
  145. func load_game():
  146. var save_game = File.new()
  147. if not save_game.file_exists("user://savegame.save"):
  148. return # Error! We don't have a save to load.
  149. # We need to revert the game state so we're not cloning objects
  150. # during loading. This will vary wildly depending on the needs of a
  151. # project, so take care with this step.
  152. # For our example, we will accomplish this by deleting saveable objects.
  153. var save_nodes = get_tree().get_nodes_in_group("Persist")
  154. for i in save_nodes:
  155. i.queue_free()
  156. # Load the file line by line and process that dictionary to restore
  157. # the object it represents.
  158. save_game.open("user://savegame.save", File.READ)
  159. while not save_game.eof_reached():
  160. var current_line = parse_json(save_game.get_line())
  161. # Firstly, we need to create the object and add it to the tree and set its position.
  162. var new_object = load(current_line["filename"]).instance()
  163. get_node(current_line["parent"]).add_child(new_object)
  164. new_object.position = Vector2(current_line["pos_x"], current_line["pos_y"])
  165. # Now we set the remaining variables.
  166. for i in current_line.keys():
  167. if i == "filename" or i == "parent" or i == "pos_x" or i == "pos_y":
  168. continue
  169. new_object.set(i, current_line[i])
  170. save_game.close()
  171. .. code-tab:: csharp
  172. // Note: This can be called from anywhere inside the tree. This function is
  173. // path independent.
  174. public void LoadGame()
  175. {
  176. var saveGame = new File();
  177. if (!saveGame.FileExists("user://savegame.save"))
  178. return; // Error! We don't have a save to load.
  179. // We need to revert the game state so we're not cloning objects during loading.
  180. // This will vary wildly depending on the needs of a project, so take care with
  181. // this step.
  182. // For our example, we will accomplish this by deleting saveable objects.
  183. var saveNodes = GetTree().GetNodesInGroup("Persist");
  184. foreach (Node saveNode in saveNodes)
  185. saveNode.QueueFree();
  186. // Load the file line by line and process that dictionary to restore the object
  187. // it represents.
  188. saveGame.Open("user://savegame.save", (int)File.ModeFlags.Read);
  189. while (!saveGame.EofReached())
  190. {
  191. var currentLine = (Dictionary<object, object>)JSON.Parse(saveGame.GetLine()).Result;
  192. if (currentLine == null)
  193. continue;
  194. // Firstly, we need to create the object and add it to the tree and set its position.
  195. var newObjectScene = (PackedScene)ResourceLoader.Load(currentLine["Filename"].ToString());
  196. var newObject = (Node)newObjectScene.Instance();
  197. GetNode(currentLine["Parent"].ToString()).AddChild(newObject);
  198. newObject.Set("Position", new Vector2((float)currentLine["PosX"], (float)currentLine["PosY"]));
  199. // Now we set the remaining variables.
  200. foreach (KeyValuePair<object, object> entry in currentLine)
  201. {
  202. string key = entry.Key.ToString();
  203. if (key == "Filename" || key == "Parent" || key == "PosX" || key == "PosY")
  204. continue;
  205. newObject.Set(key, entry.Value);
  206. }
  207. }
  208. saveGame.Close();
  209. }
  210. And now, we can save and load an arbitrary number of objects laid out
  211. almost anywhere across the scene tree! Each object can store different
  212. data depending on what it needs to save.
  213. Some notes
  214. ----------
  215. We may have glossed over a step, but setting the game state to one fit
  216. to start loading data can be complicated. This step will need to be
  217. heavily customized based on the needs of an individual project.
  218. This implementation assumes no Persist objects are children of other
  219. Persist objects. Otherwise, invalid paths would be created. To
  220. accommodate nested Persist objects, consider saving objects in stages.
  221. Load parent objects first so they are available for the :ref:`add_child()
  222. <class_node_method_add_child>`
  223. call when child objects are loaded. You will also need a way to link
  224. children to parents as the :ref:`NodePath
  225. <class_nodepath>` will likely be invalid.