saving_games.rst 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. First 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. the GUI or through 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. ::
  24. var savenodes = get_tree().get_nodes_in_group("Persist")
  25. for i in savenodes:
  26. # Now we can call our save function on each node.
  27. Serializing
  28. -----------
  29. The next step is to serialize the data. This makes it much easier to
  30. read and store to disk. In this case, we're assuming each member of
  31. group Persist is an instanced node and thus has a file path. GDScript
  32. has helper functions for this such as dictionary.to_json() and
  33. dictionary.parse_json() so we will use a dictionary. Our node needs to
  34. contain a save function that returns this data. The save function will
  35. look like this:
  36. ::
  37. func save():
  38. var savedict = {
  39. filename=get_filename(),
  40. parent=get_parent().get_path(),
  41. posx=get_pos().x, #Vector2 is not supported by json
  42. posy=get_pos().y,
  43. attack=attack,
  44. defense=defense,
  45. currenthealth=currenthealth,
  46. maxhealth=maxhealth,
  47. damage=damage,
  48. regen=regen,
  49. experience=experience,
  50. TNL=TNL,
  51. level=level,
  52. AttackGrowth=AttackGrowth,
  53. DefenseGrowth=DefenseGrowth,
  54. HealthGrowth=HealthGrowth,
  55. isalive=isalive,
  56. last_attack=last_attack
  57. }
  58. return savedict
  59. This gives us a dictionary with the style
  60. ``{ "variable_name":that_variables_value }`` which will be useful when
  61. loading.
  62. Saving and reading data
  63. -----------------------
  64. As covered in the :ref:`doc_filesystem` tutorial, we'll need to open a file
  65. and write to it and then later read from it. Now that we have a way to
  66. call our groups and get their relevant data, let's use to_json() to
  67. convert it into an easily stored string and store them in a file. Doing
  68. it this way ensures that each line is its own object so we have an easy
  69. way to pull the data out of the file as well.
  70. ::
  71. # Note: This can be called from anywhere inside the tree. This function is path independent.
  72. # Go through everything in the persist category and ask them to return a dict of relevant variables
  73. func save_game():
  74. var savegame = File.new()
  75. savegame.open("user://savegame.save", File.WRITE)
  76. var savenodes = get_tree().get_nodes_in_group("Persist")
  77. for i in savenodes:
  78. var nodedata = i.save()
  79. savegame.store_line(nodedata.to_json())
  80. savegame.close()
  81. Game saved! Loading is fairly simple as well. For that we'll read each
  82. line, use parse_json() to read it back to a dict, and then iterate over
  83. the dict to read our values. But we'll need to first create the object
  84. and we can use filename and parent to achieve that. Here is our load
  85. function:
  86. ::
  87. # Note: This can be called from anywhere inside the tree. This function is path independent.
  88. func load_game():
  89. var savegame = File.new()
  90. if !savegame.file_exists("user://savegame.save"):
  91. return #Error! We don't have a save to load
  92. # We need to revert the game state so we're not cloning objects during loading. This will vary wildly depending on the needs of a project, so take care with this step.
  93. #For our example, we will accomplish this by deleting savable objects.
  94. var savenodes = get_tree().get_nodes_in_group("Persist")
  95. for i in savenodes:
  96. i.queue_free()
  97. # Load the file line by line and process that dictionary to restore the object it represents
  98. var currentline = {} # dict.parse_json() requires a declared dict.
  99. savegame.open("user://Invasionsave.save", File.READ)
  100. while (!savegame.eof_reached()):
  101. currentline.parse_json(savegame.get_line())
  102. #First we need to create the object and add it to the tree and set its position.
  103. var newobject = load(currentline["filename"]).instance()
  104. get_node(currentline["parent"]).add_child(newobject)
  105. newobject.set_pos(Vector2(currentline["posx"],currentline["posy"]))
  106. # Now we set the remaining variables.
  107. for i in currentline.keys():
  108. if (i == "filename" or i == "parent" or i == "posx" or i == "posy"):
  109. continue
  110. newobject.set(i, currentline[i])
  111. savegame.close()
  112. And now we can save and load an arbitrary number of objects laid out
  113. almost anywhere across the scene tree! Each object can store different
  114. data depending on what it needs to save.
  115. Some notes
  116. ----------
  117. We may have glossed over a step, but setting the game state to one fit
  118. to start loading data can be very complicated. This step will need to be
  119. heavily customized based on the needs of an individual project.
  120. This implementation assumes no Persist objects are children of other
  121. Persist objects. Doing so would create invalid paths. If this is one of
  122. the needs of a project this needs to be considered. Saving objects in
  123. stages (parent objects first) so they are available when child objects
  124. are loaded will make sure they're available for the add_child() call.
  125. There will also need to be some way to link children to parents as the
  126. nodepath will likely be invalid.