简体   繁体   English

如何保存/加载游戏时间? (统一引擎)

[英]How to Save / Load game-time? (Unity Engine)

I have two scenes in my game - Main Menu and Game.我的游戏中有两个场景 - 主菜单和游戏。 I already made player's position and rotation persist between 2 scenes using binary formatter and serialization.我已经使用二进制格式化程序和序列化制作了播放器的 position 和旋转在 2 个场景之间持续存在。 But, I struggle to make time in the Game scene persist between 2 scenes (and game sessions).但是,我很难让游戏场景中的时间在两个场景(和游戏会话)之间持续存在。 To put it simply:简而言之:

  1. Game starts - the time is 'x'.游戏开始 - 时间是'x'。
  2. I save the Game after some time and leave / go to Menu - the time is 'x + time passed'.一段时间后我保存游戏并离开 / go 到菜单 - 时间是“x + 时间过去”。
  3. I load the Game - the time is 'x + time passed' instead of 'x'.我加载游戏 - 时间是“x + 时间过去”而不是“x”。

I paste some code (saving and loading pos & rot) so you have an idea of what's going on.我粘贴了一些代码(保存和加载 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);
    }
}

I would appreciate it if you would help me with saving & loading game-time in the same way.如果您能以同样的方式帮助我保存和加载游戏时间,我将不胜感激。

Thank you!谢谢!

First of all why so complicated with that float[] .首先,为什么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 ^^)我会简单地使用如下结构(如果你不喜欢它 - 一定要坚持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);
    }
}

Then for tracking the total played time there are multiple ways.那么跟踪总播放时间有多种方法。

  • Either store the last saveDateTime.Now so you can simply add the time currently played when saving:要么存储上次保存的DateTime.Now ,这样您就可以在保存时简单地添加当前播放的时间:

     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 useTime.unscaledTime which anyway is the time since the last app start.或者您可以简单地使用Time.unscaledTime ,它是自上次应用程序启动以来的时间。

    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 eg 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 )缺点:根据您的用例,您可能无法使用Time.unscaledTime如果您允许重置到应用程序中的最后一次保存(因为在这种情况下Time.time只是从应用程序启动开始继续计数)

  • Or you can go fully "manual" and track the played time fully on your own using Time.unscaledDeltaTime like eg或者你可以 go 完全“手动”并使用Time.unscaledDeltaTime完全自己跟踪播放时间,例如

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

    Pro: Full control + can easily reset or load a value into that field. Pro:完全控制 + 可以轻松地重置或将值加载到该字段中。

    Con: Overhead for the Update method being called every frame... but this is so small that it won't matter at all;缺点:每帧调用Update方法的开销……但这太小了,根本不重要; ;) ;)

Fazit I would go with the last option and do eg Fazit 我会用最后一个选项 go 做例如

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM