c# - 如何保存/加载游戏时间?(统一引擎)
问题描述
我的游戏中有两个场景 - 主菜单和游戏。我已经使用二进制格式化程序和序列化使玩家的位置和旋转在 2 个场景之间保持不变。但是,我很难让游戏场景中的时间在两个场景(和游戏会话)之间持续存在。简而言之:
- 游戏开始 - 时间是'x'。
- 一段时间后我保存游戏并离开/进入菜单 - 时间是“x + 时间过去”。
- 我加载游戏 - 时间是“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);
}
}
如果您能以同样的方式帮助我保存和加载游戏时间,我将不胜感激。
谢谢!
解决方案
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 theTime.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);
}
}
推荐阅读
- telegram - 解锁 Telegram Grous 上的隐藏消息
- java - 本机查询弹簧数据的问题 JPA 返回带有空对象的空 JSON 数组
- python - 如何修复此代码 - 函数中未定义变量
- regex - 向 Htaccess 添加点运算符时出现一些错误
- android - react-native-picker 不适用于 react-native 版本 0.63.2
- reactjs - 在反应原生Android中上传文件
- c - 在 C 中使用 opendir 和 readdir 打开目录中的文件
- r - 重命名和创建指标变量?
- java - “可以在函数(isFull())中减去 var 大小还是在构造函数中这样做更好?”
- c++ - 优化这种“统计巧合”查找算法