【问题标题】:How to Save / Load game-time? (Unity Engine)如何保存/加载游戏时间? (统一引擎)
【发布时间】:2021-02-02 06:57:01
【问题描述】:

我的游戏中有两个场景 - 主菜单和游戏。我已经使用二进制格式化程序和序列化使玩家的位置和旋转在 2 个场景之间保持不变。但是,我很难让游戏场景中的时间在两个场景(和游戏会话)之间持续存在。简单来说:

  1. 游戏开始 - 时间是“x”。
  2. 我在一段时间后保存游戏并离开/转到菜单 - 时间是“x + 已过时间”。
  3. 我加载了游戏 - 时间是“x + 时间过去”而不是“x”。

我粘贴了一些代码(保存和加载 pos & rot),以便您了解发生了什么。

public static class DataSerializer
{
    public static void SerializeData(Player player)
    {
        BinaryFormatter bf = new BinaryFormatter();
        string dataPath = Application.persistentDataPath + "/data.txt";
        FileStream stream = new FileStream(dataPath, FileMode.Create);
        PlayerData data = new PlayerData(player);

        bf.Serialize(stream, data);
        stream.Close();
    }

    public static float[] DeserializeData()
    {
        string dataPath = Application.persistentDataPath + "/data.txt";
        if (File.Exists(dataPath))
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream stream = new FileStream(dataPath, FileMode.Open);
            PlayerData data = bf.Deserialize(stream) as PlayerData;

            stream.Close();
            return data.stats;
        }
        else
        {
            Debug.LogError("File does not exist.");
            return new float[6];
        }
    }
}

[Serializable]
public class PlayerData
{
    public float[] stats;

    public PlayerData(Player player)
    {
        stats = new float[6];

        stats[0] = player.transform.position.x;
        stats[1] = player.transform.position.y;
        stats[2] = player.transform.position.z;

        stats[3] = player.transform.eulerAngles.x;
        stats[4] = player.transform.eulerAngles.y;
        stats[5] = player.transform.eulerAngles.z;
    }
}
public class Player : MonoBehaviour
{
    public GameObject player;

    void Start()
    {
        float[] loadedStats = DataSerializer.DeserializeData();

        Vector3 position = new Vector3(loadedStats[0], loadedStats[1], loadedStats[2]);
        Vector3 rotation = new Vector3(loadedStats[3], loadedStats[4], loadedStats[5]);

        player.transform.position = position;
        player.transform.rotation = Quaternion.Euler(rotation);
    }

    public void Save()
    {
        DataSerializer.SerializeData(this);
    }
}

如果您能以同样的方式帮助我保存和加载游戏时间,我将不胜感激。

谢谢!

【问题讨论】:

    标签: c# unity3d serialization time


    【解决方案1】:

    首先,为什么float[] 如此复杂。在我看来,它只会让人难以阅读/理解,而且容易出错。

    我会简单地使用类似下面的结构(如果你不喜欢它 - 一定要坚持float[7]为什么不^^)

    public static class DataSerializer
    {
        private static readonly dataPath = Path.Combine(Application.persistentDataPath, "data.txt");
    
        public static void SerializeData(Player player)
        {
            var data = new PlayerData(player);
    
            using(var stream = File.Open(dataPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
            {
                var bf = new BinaryFormatter();
                bf.Serialize(stream, data);
            }
        }
    
        public static bool DeserializeData(out PlayerData data)
        {
            data = null;
    
            if (!File.Exists(dataPath)) 
            {
                Debug.LogError("File does not exist.");
                return false;
            }
            
            using(var stream = File.Open(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var bf = new BinaryFormatter();                
                data = bf.Deserialize(stream) as PlayerData;    
            } 
    
            return data != null; 
        }            
    }
    
    [Serializable]
    public class PlayerData
    {
        public SerializableVector3 Position;
        public SerializableVector3 EulerAngles;
        public float TotalTimePlayed;
    
        public PlayerData(Player player)
        {
            Position = player.transform.position;
    
            EulerAngles = player.transform.eulerAngles;
    
            TotalPlayTime = player.TotalTimePlayed;
        }
    }
    
    [Serializable]
    public class SerializableVector3
    {
        public float x;
        public float y;
        public float z;
    
        public SerializableVector3(Vector3 vec)
        {
            x = vec.x;
            y = vec.y;
            z = vec.z;
        }
    
        public Vector3 ToVector3()
        {
            return new Vector3(x,y,z);
        }
    
        public static implicit operator Vector3(SerializableVector3 vec)
        {
            return vec.ToVector3();
        }
    
        public static implicit operator SerializableVector3(Vector3 vec)
        {
            return new SerializableVector3(vec);
        }
    }
    

    那么跟踪总播放时间有多种方法。

    • 要么存储最后一次存档DateTime.Now,所以您可以在保存时简单地添加当前播放的时间:

       float timePlayedThisSession = (DateTime.Now - dateTimeLastSaved).TotalSeconds;
      

      专业人士:系统会处理它,这是一次计算

      缺点:用户可以简单地更改系统时间 -> 可能会“破解”您的应用统计信息

    • 或者您可以简单地使用Time.unscaledTime,这无论如何是自上次应用启动以来的时间。

      专业人士:用户不能简单地更改此值

      Con:根据您的用例,您可能无法使用Time.unscaledTime,例如您允许重置到您的应用程序中的最后一次保存(因为在这种情况下,Time.time 只是继续计数自应用程序启动

    • 或者您可以完全“手动”并使用Time.unscaledDeltaTime 自行跟踪播放时间,例如

       private float timePlayedThisSession;
      
       private void Update()
       {
           timePlayedThisSession += Time.unscaledDeltaTime;
       }
      

      专业版:完全控制 + 可以轻松地将值重置或加载到该字段中。

      Con:每帧调用 Update 方法的开销......但这太小了,根本不重要! ;)

    Fazit 我会选择最后一个选项,例如

    public class Player : MonoBehaviour
    {
        public GameObject player;
        private float totalTimePlayed;
    
        // Read-only access
        public float TotalTimePlayed => totalTimePlayed;
    
        void Start()
        {
            ResetToLastSave ();
        }
    
        private void Update ()
        {
            totalTimePlayed += Time.unscaledDeltaTime;
        }
    
        // Just adding this to show you could even simply add a method to allow your user revert to the last save
        public void ResetToLastSave()
        {
            if(!DataSerializer.DeserializeData(out var loadedStats))
            {
                Debug.LogWarning("Could not load!", this);
                return;
            }
    
            player.transform.position = loadedStats.Position;
            player.transform.eulerAngles = loadedStats.Rotation;
            totalTimePlayed = loadedStats.TotalTimePlayed;
        }
    
        public void Save()
        {
            DataSerializer.SerializeData(this);
        }
    }
    

    【讨论】:

    • 非常感谢!我还是 C# 和序列化的新手,你的详细回答给了我很多关于一切如何工作的线索。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-06
    • 1970-01-01
    • 2015-05-20
    • 1970-01-01
    • 2011-12-02
    • 2012-05-18
    • 1970-01-01
    相关资源
    最近更新 更多