首页 > 解决方案 > 如何保存/加载游戏时间?(统一引擎)

问题描述

我的游戏中有两个场景 - 主菜单和游戏。我已经使用二进制格式化程序和序列化使玩家的位置和旋转在 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#unity3dserializationtime

解决方案


First of all why so complicated with that float[]. In my eyes it just makes it hard to read/understand and is error prone.

I would simply use a structure like the following (if you don't like it - sure stick to float[7] why not ^^)

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);
    }
}

Then for tracking the total played time there are multiple ways.

  • Either store the last save DateTime.Now so you can simply add the time currently played when saving:

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

    Pro: The system handles it and it's a one time calculation

    Con: The user can simply change the system time -> possibly "hack" your app stats

  • Or you can simply use Time.unscaledTime which anyway is the time since the last app start.

    Pro: User can not simply alter this value

    Con: Depending on your use case you might not be able to use the Time.unscaledTime if e.g. you allow to reset to the last save within your app (because in this case the Time.time simply continues counting up since app start)

  • Or you can go fully "manual" and track the played time fully on your own using Time.unscaledDeltaTime like e.g.

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

    Pro: Full control + can easily reset or load a value into that field.

    Con: Overhead for the Update method being called every frame ... but this is so small that it won't matter at all! ;)

Fazit I would go with the last option and do e.g.

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);
    }
}

推荐阅读