game/godot

Godot3 - I/O Saving games

C/H 2018. 11. 19. 08:30

Saving games

Introduction

Save games can be complicated. It can be desired to store more information than the current level or number of stars earned on a level. More advanced save games may need to store additional information about an arbitrary number of objects. This will allow the save function to scale as the game grows more complex.
게임 저장은 복잡 할 수 있습니다. 현재 레벨이나 레벨에서 보상받은 별(Star) 수량보다 더 많은 정보를 저장하는 것이 바람직 할 수 있습니다. 고급 게임 저장은 임의의 수의 개체에 대한 추가 정보를 저장해야 할 수 있습니다. 이렇게하면 게임이 더 복잡 해짐에 따라 저장 기능을 확장 할 수 있습니다.

Identify persistent objects

First we should identify what objects we want to keep between game sessions and what information we want to keep from those objects. For this tutorial, we will use groups to mark and handle objects to be saved but other methods are certainly possible.
먼저 우리는 게임 세션간에 유지하고자하는 객체와 객체에서 유지하고자하는 정보를 식별해야합니다. 이 자습서에서는 그룹을 사용하여 저장할 개체를 표시하고 처리하지만 다른 방법도 가능합니다.

We will start by adding objects we wish to save to the “Persist” group. As in the Scripting (continued) tutorial, we can do this through the GUI or through script. Let’s add the relevant nodes using the GUI:
"Persist"그룹에 저장하려는 개체를 추가하여 시작합니다. 스크립팅(계속)에서와 같이 튜토리얼에서는 GUI 또는 스크립트를 통해 이 작업을 수행 할 수 있습니다. GUI를 사용하여 관련 노드를 추가합시다.

Once this is done when we need to save the game we can get all objects to save them and then tell them all to save with this script:
게임을 저장할 필요가있을 때 이 작업이 완료되면 모든 객체를 저장하고 모든 객체에 이 스크립트를 저장하도록 지시 할 수 있습니다.

var save_nodes = get_tree().get_nodes_in_group("Persist")
for i in save_nodes:
	# Now we can call our save function on each node.
	# 이제 각 노드에서 save 함수를 호출 할 수 있습니다.

Serializing

The next step is to serialize the data. This makes it much easier to read and store to disk. In this case, we’re assuming each member of group Persist is an instanced node and thus has a path. GDScript has helper functions for this, such as to_json() and parse_json(), so we will use a dictionary. Our node needs to contain a save function that returns this data. The save function will look like this:
다음 단계는 데이터를 직렬화하는 것입니다. 이렇게하면 디스크를 읽고 디스크에 저장하는 것이 훨씬 쉬워집니다. 이 경우 우리는 그룹 Persist의 각 멤버가 인스턴스화 된 노드이므로 경로가 있다고 가정합니다. 다음 단계는 데이터를 직렬화하는 것입니다. 이렇게하면 디스크를 읽고 디스크에 저장하는 것이 훨씬 쉬워집니다. 이 경우 우리는 그룹 Persist의 각 멤버가 인스턴스화 된 노드이므로 경로가 있다고 가정합니다. GDScript에는 to_json() 및 parse_json()과 같은 헬퍼 함수가 있으므로 dictionary를 사용합니다. 노드에는이 데이터를 반환하는 저장 함수가 있어야합니다. save 함수는 다음과 같습니다.

func save():
	var save_dict = {
		"filename" : get_filename(),
		"parent" : get_parent().get_path(),
		"pos_x" : position.x, # Vector2 is not supported by JSON
		"pos_y" : position.y,
		"attack" : attack,
		"defense" : defense,
		"current_health" : current_health,
		"max_health" : max_health,
		"damage" : damage,
		"regen" : regen,
		"experience" : experience,
		"tnl" : tnl,
		"level" : level,
		"attack_growth" : attack_growth,
		"defense_growth" : defense_growth,
		"health_growth" : health_growth,
		"is_alive" : is_alive,
		"last_attack" : last_attack
	}
	return save_dict

This gives us a dictionary with the style { "variable_name":that_variables_value } which will be useful when loading.
이렇게하면로드 할 때 유용하게 사용할 수있는 { "variable_name": that_variables_value} 스타일의 dictionary를 얻을 수 있습니다.

Saving and reading data

As covered in the File system tutorial, we’ll need to open a file and write to it and then later read from it. Now that we have a way to call our groups and get their relevant data, let’s use to_json() to convert it into an easily stored string and store them in a file. Doing it this way ensures that each line is its own object so we have an easy way to pull the data out of the file as well.
파일 시스템 튜토리얼에서 다루었 듯이 파일을 열고 써야하고 나중에 읽어야합니다. 그룹을 호출하고 관련 데이터를 가져 오는 방법이 있으므로 to_json()을 사용하여 쉽게 저장되는 문자열로 변환하고 파일에 저장해 봅시다. 이 방법을 사용하면 각 행이 자체 객체이기 때문에 파일에서 데이터를 쉽게 가져올 수 있습니다.

# Note: This can be called from anywhere inside the tree.  This function is path independent.
# Go through everything in the persist category and ask them to return a dict of relevant variables
func save_game():
	var save_game = File.new()
	save_game.open("user://savegame.save", File.WRITE)
	var save_nodes = get_tree().get_nodes_in_group("Persist")
	for i in save_nodes:
		var node_data = i.call("save");
		save_game.store_line(to_json(node_data))
	save_game.close()

Game saved! Loading is fairly simple as well. For that we’ll read each line, use parse_json() to read it back to a dict, and then iterate over the dict to read our values. But we’ll need to first create the object and we can use the filename and parent values to achieve that. Here is our load function:
게임이 저장되었습니다! 로딩은 상당히 간단합니다. 이를 위해 각 행을 읽고, parse_json()을 사용하여 dict를 읽은 다음, dict에서 값을 읽습니다. 그러나 우리는 먼저 객체를 생성해야하며, 이를 달성하기 위해 파일 이름과 부모 값을 사용할 수 있습니다. 다음은로드 기능입니다.

# Note: This can be called from anywhere inside the tree. This function is path independent.
# 참고 : 이것은 트리 안의 어느 곳에서나 호출 할 수 있습니다. 이 함수는 경로 독립적입니다.
func load_game():
	var save_game = File.new()
	if not save_game.file_exists("user://save_game.save"):
		return # Error! We don't have a save to load. 오류! 로드할 저장파일이 없습니다.

	# 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.
	# 로드하는 동안 객체를 복제하지 않도록 게임 상태를 되돌려 야합니다. 이것은 프로젝트의 필요에 따라 격렬하게 달라 지므로이 단계에 주의하십시오.
	# For our example, we will accomplish this by deleting savable objects.
	# 예를 들어, 저장 가능한 객체를 삭제하면됩니다.
	var save_nodes = get_tree().get_nodes_in_group("Persist")
	for i in save_nodes:
		i.queue_free()

	# Load the file line by line and process that dictionary to restore the object it represents
	# 파일을 한 줄씩로드하고 해당 dictionary 를 처리하여 나타내는 개체를 복원합니다.
	save_game.open("user://savegame.save", File.READ)
	while not save_game.eof_reached():
		var current_line = parse_json(save_game.get_line())
		# First we need to create the object and add it to the tree and set its position.
		# 먼저 객체를 생성하여 트리에 추가하고 위치를 설정해야합니다.
		var new_object = load(current_line["filename"]).instance()
		get_node(current_line["parent"]).add_child(new_object)
		new_object.position = Vector2(current_line["pos_x"], current_line["pos_y"]))
		# Now we set the remaining variables.
		# 나머지 변수를 설정했습니다.
		for i in current_line.keys():
			if i == "filename" or i == "parent" or i == "pos_x" or i == "pos_y":
				continue
			new_object.set(i, current_line[i])
	save_game.close()

And now we can save and load an arbitrary number of objects laid out almost anywhere across the scene tree! Each object can store different data depending on what it needs to save.
이제 장면 트리의 거의 모든 위치에 배치 된 임의의 수의 객체를 저장하고로드 할 수 있습니다! 각 객체는 저장할 내용에 따라 다른 데이터를 저장할 수 있습니다.

Some notes

We may have glossed over a step, but setting the game state to one fit to start loading data can be complicated. This step will need to be heavily customized based on the needs of an individual project.
우리는 한 걸음 더 나아졌지만 게임 상태를 데이터 로드를 시작하는데 적합하게 설정하는 것은 복잡 할 수 있습니다. 이 단계는 개별 프로젝트의 필요에 따라 크게는 사용자가 정의해야합니다.

This implementation assumes no Persist objects are children of other Persist objects. Doing so would create invalid paths. If this is one of the needs of a project this needs to be considered. Saving objects in stages (parent objects first) so they are available when child objects are loaded will make sure they’re available for the add_child() call. There will also need to be some way to link children to parents as the NodePath will likely be invalid.
이 구현은 Persist 객체가 다른 Persist 객체의 자식이라고 가정하지 않습니다. 그렇게하면 잘못된 경로가 만들어집니다. 이것이 프로젝트의 요구사항 중 하나라면 이를 고려할 필요가 있습니다. 자식 객체가 로드될 때 사용할 수 있도록 스테이지에 객체를 저장하면 부모 객체를 add_child() 호출에 사용할 수 있습니다. NodePath가 무효가되기 때문에 자녀를 부모에게 연결하는 방법이 필요합니다.

반응형

'game > godot' 카테고리의 다른 글

Godot3 - I/O  (0) 2018.11.20
Godot3 - I/O Encrypting save games  (0) 2018.11.20
Godot3 - I/O Data paths  (0) 2018.11.17
Godot3 - I/O Background loading  (0) 2018.11.16
Godot3 API - Singletons (AutoLoad)  (0) 2018.11.15