Godot:SavingGames
저장 게임은 복잡할 수 있습니다. 예를 들어 여러 수준에 걸쳐 여러 개체의 정보를 저장하는 것이 바람직할 수 있습니다. 고급 저장 게임 시스템은 임의의 개체 수에 대한 추가 정보를 허용해야 합니다. 이렇게 하면 게임이 더 복잡해짐에 따라 저장 기능을 확장할 수 있습니다.
INFORMATION |
사용자 구성을 저장하려는 경우 이 용도로 ConfigFile 클래스를 사용할 수 있습니다. |
영구 개체 식별
먼저, 게임 세션 사이에 유지하려는 개체와 해당 개체에서 유지하려는 정보를 식별해야 합니다.
이 자습서에서는 그룹을 사용하여 저장할 개체를 표시하고 처리하지만 다른 방법도 가능합니다.
Persist
그룹에 저장하려는 개체를 추가하는 것으로 시작합니다. GUI 또는 스크립트를 통해 이 작업을 수행할 수 있습니다. GUI를 사용하여 관련 노드를 추가해 보겠습니다.
Godot_node_groups.png
이 작업이 완료되면 게임을 저장해야 할 때 모든 개체를 가져와 저장한 다음 이 스크립트를 사용하여 모두 저장하도록 지시할 수 있습니다.
var saveNodes = GetTree().GetNodesInGroup("Persist");
foreach (Node saveNode in saveNodes)
{
// Now, we can call our save function on each node.
}
직렬화
다음 단계는 데이터를 직렬화하는 것입니다. 이렇게 하면 디스크에서 읽고 디스크에 저장하기가 훨씬 쉬워집니다. 이 경우 Persist
그룹의 각 구성원이 인스턴스화된 노드이므로 경로가 있다고 가정합니다. GDScript에는 to_json()
및 parse_json()
과 같은 도우미 함수가 있으므로 사전을 사용합니다. 노드에는 이 데이터를 반환하는 저장 기능이 포함되어야 합니다. 저장 기능은 다음과 같습니다.
public Godot.Collections.Dictionary<string, object> Save()
{
return new Godot.Collections.Dictionary<string, object>()
{
{ "Filename", GetFilename() },
{ "Parent", GetParent().GetPath() },
{ "PosX", Position.x }, // Vector2 is not supported by JSON
{ "PosY", Position.y },
{ "Attack", Attack },
{ "Defense", Defense },
{ "CurrentHealth", CurrentHealth },
{ "MaxHealth", MaxHealth },
{ "Damage", Damage },
{ "Regen", Regen },
{ "Experience", Experience },
{ "Tnl", Tnl },
{ "Level", Level },
{ "AttackGrowth", AttackGrowth },
{ "DefenseGrowth", DefenseGrowth },
{ "HealthGrowth", HealthGrowth },
{ "IsAlive", IsAlive },
{ "LastAttack", LastAttack }
};
}
데이터 저장 및 읽기
파일 시스템(File system) 튜토리얼 에서 다룬 것처럼 파일 을 열어야 파일에 쓰거나 읽을 수 있습니다. 이제 그룹을 호출하고 관련 데이터를 가져오는 방법이 있으므로 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.
public void SaveGame()
{
var saveGame = new File();
saveGame.Open("user://savegame.save", (int)File.ModeFlags.Write);
var saveNodes = GetTree().GetNodesInGroup("Persist");
foreach (Node saveNode in saveNodes)
{
// Check the node is an instanced scene so it can be instanced again during load.
if (saveNode.Filename.Empty())
{
GD.Print(String.Format("persistent node '{0}' is not an instanced scene, skipped", saveNode.Name));
continue;
}
// Check the node has a save function.
if (!saveNode.HasMethod("Save"))
{
GD.Print(String.Format("persistent node '{0}' is missing a Save() function, skipped", saveNode.Name));
continue;
}
// Call the node's save function.
var nodeData = saveNode.Call("Save");
// Store the save dictionary as a new line in the save file.
saveGame.StoreLine(JSON.Print(nodeData));
}
saveGame.Close();
}
게임이 저장되었습니다! 로딩도 상당히 간단합니다. 이를 위해 각 줄을 읽고 parse_json()
을 사용하여 다시 사전으로 읽은 다음 사전을 반복하여 값을 읽습니다. 그러나 먼저 개체를 만들어야 하며 파일 이름과 부모 값을 사용하여 이를 달성할 수 있습니다. 로드 함수는 다음과 같습니다.
// Note: This can be called from anywhere inside the tree. This function is
// path independent.
public void LoadGame()
{
var saveGame = new File();
if (!saveGame.FileExists("user://savegame.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 saveable objects.
var saveNodes = GetTree().GetNodesInGroup("Persist");
foreach (Node saveNode in saveNodes)
saveNode.QueueFree();
// Load the file line by line and process that dictionary to restore the object
// it represents.
saveGame.Open("user://savegame.save", (int)File.ModeFlags.Read);
while (saveGame.GetPosition() < saveGame.GetLen())
{
// Get the saved dictionary from the next line in the save file
var nodeData = new Godot.Collections.Dictionary<string, object>((Godot.Collections.Dictionary)JSON.Parse(saveGame.GetLine()).Result);
// Firstly, we need to create the object and add it to the tree and set its position.
var newObjectScene = (PackedScene)ResourceLoader.Load(nodeData["Filename"].ToString());
var newObject = (Node)newObjectScene.Instance();
GetNode(nodeData["Parent"].ToString()).AddChild(newObject);
newObject.Set("Position", new Vector2((float)nodeData["PosX"], (float)nodeData["PosY"]));
// Now we set the remaining variables.
foreach (KeyValuePair<string, object> entry in nodeData)
{
string key = entry.Key.ToString();
if (key == "Filename" || key == "Parent" || key == "PosX" || key == "PosY")
continue;
newObject.Set(key, entry.Value);
}
}
saveGame.Close();
}
이제 우리는 장면 트리 전체에 걸쳐 거의 모든 곳에 배치된 임의의 수의 개체를 저장하고 로드할 수 있습니다! 각 개체는 저장해야 하는 항목에 따라 다른 데이터를 저장할 수 있습니다.
일부 메모
로딩을 위한 게임 상태 설정에 대해서는 얼버무렸습니다. 궁극적으로 이 논리의 상당 부분이 사용되는 프로젝트 생성자에게 달려 있습니다. 이는 종종 복잡하며 개별 프로젝트의 요구 사항에 따라 크게 사용자 지정해야 합니다.
또한 우리의 구현에서는 Persist
개체가 다른 Persist
개체의 자식이 아니라고 가정합니다. 그렇지 않으면 유효하지 않은 경로가 생성됩니다. 중첩된 Persist
개체를 수용하려면 단계에서 개체를 저장하는 것이 좋습니다. 자식 개체가 로드될 때 add_child()
호출 에 사용할 수 있도록 먼저 부모 개체 를 로드합니다. 또한 NodePath 가 유효하지 않을 수 있으므로 자식을 부모에 연결하는 방법이 필요합니다.
See also
- Godot
- Godot:SavingGames
- Godot:ConfigFile - 설정파일 저장시 사용.
- Godot:PacketPeer - 패킷 바이너리 직렬화
- Godot:ResourceSaver - 리소스(
.tres
) 파일 저장.