最近公司需要去做存档功能,因为需要存档物品位置信息所以我在网上开始找资料,其中遇到几个坑在这里记录一下,防止以后碰到同样的错误以及给阅读过的读者少绕点路。
本来是想用json直接储存的,但是因为要和同事统一,所以选择使用序列化储存,对于声音以及设定上简单信息的存档我们可以使用PlayerPrefs就可以做到。
首先将变量序列化
[SerializeField]
public Slider SoundSlider;
[SerializeField]
public Toggle SoundToggle;
然后在我们使用自带的方法检索键值获取存档以及,传值来改变状态或者数值。具体代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SoundContrl : MonoBehaviour {
[SerializeField]
public Slider SoundSlider;
[SerializeField]
public Toggle SoundToggle;
//工程名
public string ProName = "";
private AudioSource audioSource;
/// <summary>
/// 声音初始化
/// </summary>
private void Awake()
{
//获取AudioSource
audioSource = transform.GetComponent<AudioSource>();
//检索音乐键值
if (!PlayerPrefs.HasKey(ProName + "MusicOn"))
{
//初始化音乐设置
PlayerPrefs.SetFloat(ProName + "MusicOn", 0.5f);
SoundToggle.isOn = true;
SoundSlider.value = 0.5f;
PlayerPrefs.Save();
}
else
{
//这里我设置声音为0的时候音乐关闭
if (PlayerPrefs.GetFloat(ProName + "MusicOn") == 0)
{
SoundToggle.isOn = false;
}
else
{
SoundToggle.isOn = true;
//储存音乐大小
SoundSlider.value = PlayerPrefs.GetFloat(ProName + "MusicOn");
}
}
}
// Update is called once per frame
void Update ()
{
//声音随着按钮事件更新
audioSource.volume = SoundSlider.value;
audioSource.enabled = SoundToggle.isOn;
SoundSave();
}
/// <summary>
/// 声音存档更新
/// </summary>
private void SoundSave()
{
if (!SoundToggle.isOn)
{
PlayerPrefs.SetFloat(ProName+"MusicOn", 0);
}
else
{
PlayerPrefs.SetFloat(ProName+"MusicOn", SoundSlider.value);
}
}
}
参考链接:https://blog.youkuaiyun.com/kmyhy/article/details/78643293
这个是unity自带的存档没有什么难度,困扰我的是序列化。根据上面参考链接我开始学习使用序列化。但是有个问题,这个案例的序列化使用的int类型,当我使用vector3类型或者Gameobject类型都会报错提示不支持序列化,但是我主要储存的坐标不是固定的一个点。于是我开始翻阅资料,发现序列化只支持简单类型比如int,float,string类型的序列化,那我该怎么办呢,找了半天总算让我找到能够让我这个大脑转不过弯的菜鸟的资料。
https://www.cnblogs.com/madinglin/p/8490721.html
上面的资料是作者使用json来对游戏对象存储读取位置信息数据,既然不能储存vector3类型那我就直接储存坐标x,y,z的数值不行就行了吗?加载Gameobject类型我也可以用名字代替,然后resource方法加载出来。这里就扯远了,但是我们的确可以通过这种方式达到我们的要求。
我们先创建一个save类,来储存位置信息。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Save
{
//建筑坐标
public string PosX;
public string PosY;
public string PosZ;
//
public Save() { }
public Save(string x, string y, string z)
{
PosX = x; PosY = y; PosZ = z;
}
public override string ToString()
{
return string.Format("x ={0} y ={1} z = {2}", PosX, PosY, PosZ);
}
}
然后我们开始写保存的方法
public void SaveGame()
{
// 创建 save对象
BuildList.Clear();
foreach (Transform item in Targets.transform)
{
Vector3 Pos = item.transform.position;
Save save = new Save(Pos.x.ToString(), Pos.y.ToString(), Pos.z.ToString());
BuildList.Add(save);
}
// 创建文件保存save对象
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.dataPath + "/GameSave.Scene");
bf.Serialize(file, BuildList);
file.Close();
BuildList.Clear();
// TODO 数据重置
Debug.Log("Game Saved");
}
因为我需要保存的物体坐标不是只有一个,所以我需要遍历我需要保存的物体的所有坐标,然后将这些信息都保存在集合里,然后创建文件和流数据保存集合信息。
接下来开始是载入,中间出了一个小插曲,因为我是直接复制黏贴代码,结果中间还是报错了,因为我传入的是集合并不是save类型所以抄代码理所当然就报错了,然后我该用什么类型呢?想到这里我蒙蔽了我也不知道啊,就在我以为需要使用封包拆包进行分析的时候看到了这个资料https://blog.youkuaiyun.com/e295166319/article/details/52790131
序列化什么类型,反序列化什么类型!序列化的是集合类型,那么能反序列化的也就只能是集合类型(JOJO立)。
public void LoadGame()
{
BuildList.Clear();
if (File.Exists(Application.dataPath + "/GameSave.Scene"))
{
//1 重置场景
ResetBuild();
//2 加载保存数据
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.dataPath + "/GameSave.Scene", FileMode.Open);
BuildList = bf.Deserialize(file)as List<Save>;
file.Close();
//3 载入建筑
for (int i = 0; i < BuildList.Count; i++)
{
Vector3 Pos = new Vector3(float.Parse(BuildList[i].PosX), float.Parse(BuildList[i].PosY), float.Parse(BuildList[i].PosZ));
CreatBuild(PrefabObj, Pos);
}
}
else
{
Debug.Log("No game saved!");
}
}
完整代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class Game : MonoBehaviour {
//建筑生成点
[SerializeField]
public Transform Targets;
//生成建筑
public GameObject PrefabObj;
//保存列表
private List<Save> BuildList;
public void SaveGame()
{
// 创建 save对象
BuildList.Clear();
foreach (Transform item in Targets.transform)
{
Vector3 Pos = item.transform.position;
Save save = new Save(Pos.x.ToString(), Pos.y.ToString(), Pos.z.ToString());
BuildList.Add(save);
}
// 创建文件保存save对象
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.dataPath + "/GameSave.Scene");
bf.Serialize(file, BuildList);
file.Close();
BuildList.Clear();
// TODO 数据重置
Debug.Log("Game Saved");
}
public void LoadGame()
{
BuildList.Clear();
if (File.Exists(Application.dataPath + "/GameSave.Scene"))
{
//1 重置场景
ResetBuild();
//2 加载保存数据
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.dataPath + "/GameSave.Scene", FileMode.Open);
BuildList = bf.Deserialize(file)as List<Save>;
file.Close();
//3 载入建筑
for (int i = 0; i < BuildList.Count; i++)
{
Vector3 Pos = new Vector3(float.Parse(BuildList[i].PosX), float.Parse(BuildList[i].PosY), float.Parse(BuildList[i].PosZ));
CreatBuild(PrefabObj, Pos);
}
}
else
{
Debug.Log("No game saved!");
}
}
/// <summary>
/// 实例化建筑
/// </summary>
/// <param name="build">要实例化的物体</param>
/// <param name="Pos">实例化坐标</param>
public void CreatBuild(GameObject build,Vector3 Pos)
{
Vector3 TargetPostion = new Vector3(Pos.x, Pos.y, Pos.z);
GameObject Prefab = Instantiate(build) as GameObject;
Prefab.transform.position = TargetPostion;
Prefab.transform.parent = Targets.transform;
Prefab.transform.GetComponent<SelectBuild>().SelectShow(false);
}
/// <summary>
/// 重置建筑
/// </summary>
private void ResetBuild()
{
BuildList.Clear();
foreach (Transform item in Targets.transform)
{
Destroy(item.gameObject);
}
}
// Use this for initialization
void Awake ()
{
BuildList = new List<Save>();
}
}
这样一来就没有问题了,程序员的思维和基础真的非常重要,身为码农以后的路还长着继续加油。