Unity3D仿星露谷物语开发55之保存游戏到文件

发布于:2025-05-30 ⋅ 阅读:(18) ⋅ 点赞:(0)

1、目标

将游戏保存到文件,并从文件中加载游戏。

Player在游戏中种植的Crop,我们希望保存到文件中,当游戏重新加载时Crop的GridProperty数据仍然存在。这次主要实现保存地面属性(GridProperties)信息。

我们要做的是实现一个我们可以点击的方式,将游戏保存到一个文件中,然后点击加载按钮,它将获取该文件,然后将数据带回游戏中。

2、创建GameSave.cs脚本

在Assets/Scripts/SaveSystem下创建新的脚本命名为:GameSave.cs。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class GameSave
{
    // string key - GUID gameobject ID
    public Dictionary<string, GameObjectSave> gameObjectData;

    public GameSave()
    {
        gameObjectData = new Dictionary<string, GameObjectSave>();
    }
}

3、修改ISaveable.cs脚本

添加2行代码:

    GameObjectSave ISaveableSave();

    void ISaveableLoad(GameSave gameSave);

ISaveableSave: 保存变量和游戏对象,这一步是获取数据,后续会写入文件

ISaveableLoad:将游戏保存数据对象(从文件中读取的数据)作为它的参数

4、修改GridPropertiesManager.cs脚本

添加新的引用:

using UnityEngine.SceneManagement;

添加ISaveableLoad函数实现:

public void ISaveableLoad(GameSave gameSave)
{
    if(gameSave.gameObjectData.TryGetValue(ISaveableUniqueID, out GameObjectSave gameObjectSave))
    {
        GameObjectSave = gameObjectSave;

        // Restore data for current scene
        ISaveableRestoreScene(SceneManager.GetActiveScene().name);
    }
}

其中,ISaveableUniqueID是通过GetComponent<GenerateGUID>().GUID生成的。

在GenerateGUID的实现中,它的属性是[ExecuteAlways],即运行/编译时都会执行,实际上是需要编译时生成_gUID信息。_gUID是[SerializeField]属性,而[SerializeField] 标记的字段会被 Unity 自动序列化到场景文件或预制体中。

所以一个游戏中,GenerateGUID的值是不会变化的。

另外,GameObjectSave是属性变量,其定义为:

    public GameObjectSave GameObjectSave { get { return _gameObjectSave; } set { _gameObjectSave = value; } }
 

添加ISaveableSave方法的实现:

 public GameObjectSave ISaveableSave()
 {
     // Store current scene data
     ISaveableStoreScene(SceneManager.GetActiveScene().name);

     return GameObjectSave;
 }

5、修改SceneItemsManager.cs脚本 

 添加新的引用:

using UnityEngine.SceneManagement;

添加如下2个方法的实现:

 public void ISaveableLoad(GameSave gameSave)
 {
     if(gameSave.gameObjectData.TryGetValue(ISaveableUniqueID, out GameObjectSave gameObjectSave))
     {
         GameObjectSave = gameObjectSave;

         // Restore data for current scene
         ISaveableRestoreScene(SceneManager.GetActiveScene().name);
     }
 }

 public GameObjectSave ISaveableSave()
 {
     // Store current scene data
     ISaveableStoreScene(SceneManager.GetActiveScene().name);

     return GameObjectSave;
 }

6、修改SaveLoadManager.cs脚本

SaveLoadManager有2个方法:StoreCurrentSceneData / RestoreCurrentSceneData。

在SceneControllerManager中,当场景切换和Start中会使用RestoreCurrentSceneData方法。

接下来就是改造SaveLoadManager,从文件中读写信息。

添加2个引用:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

接着添加2个函数:

public void LoadDataFromFile()
{
    BinaryFormatter bf = new BinaryFormatter();

    if(File.Exists(Application.persistentDataPath + "/WildHopeCreek.data"))
    {
        gameSave = new GameSave();

        FileStream file = File.Open(Application.persistentDataPath + "/WildHopeCreek.data", FileMode.Open);

        gameSave = (GameSave)bf.Deserialize(file);  

        // loop through all ISaveable obvjects and apply save data
        for(int i = iSaveableObjectList.Count - 1; i > -1; i--)
        {
            if (gameSave.gameObjectData.ContainsKey(iSaveableObjectList[i].ISaveableUniqueID))
            {
                iSaveableObjectList[i].ISaveableLoad(gameSave);
            }
            else
            {
                // else if iSaveableObject unique ID is not in the game object data then destroy object
                Component component = (Component)iSaveableObjectList[i];
                Destroy(component.gameObject);
            }
        }

        file.Close();
    }

    UIManager.Instance.DisablePauseMenu();
}


public void SaveDataToFile()
{
    gameSave = new GameSave();

    // loop through all ISaveable objects and generate save data
    foreach(ISaveable iSaveableObject in iSaveableObjectList)
    {
        gameSave.gameObjectData.Add(iSaveableObject.ISaveableUniqueID, iSaveableObject.ISaveableSave());
    }

    BinaryFormatter bf = new BinaryFormatter();

    FileStream file = File.Open(Application.persistentDataPath + "/WildHopeCreek.data" , FileMode.Create);

    bf.Serialize(file, gameSave);

    file.Close();

    UIManager.Instance.DisablePauseMenu();
}

可以通过如下代码查看存储的路径:

Debug.Log("dataPath:" + Application.persistentDataPath);

我的地址是:C:\Users\benbe\AppData\LocalLow\DefaultCompany\XingluValley

7、编辑UI界面

(1)设置界面元素

将PauseMenuPanel下的Tab2修改为Tab2SaveGame,

将SelectionTabButtonsPanel -> SelectionButton(2) -> Text的输入改为"Save/Load"。

给Tab2SaveGame对象添加Vertical Layout Group组件,相关配置如下:

在Tab2SaveGame下右击 -> UI -> Button(TextMeshPro),命名为SavevGameButton。再创建一个同样的Button命名为LoadGameButton。

分别点击SaveGameButton及LoadGameButton两个对象,修改Button属性的Normal Color为87775D  ,Highlighed Color为D8DB84   。

最后界面呈现的样式为:

(2)添加点击事件

SaveGameButton对应的点击事件:

LoadGameButton对应的点击事件:

8、运行游戏

运行游戏,收集地面上的item,并且种植了Crop,点击Esc -> Save Game。

退出游戏后重新进入,点击Esc -> Load Game。之前地面上已经被收集掉的item没有再次出现,Crop仍然保持之前的状态。