Unity中游戏的存档与读档

在许多游戏中当我们因为一些问题无法接着进行游玩,我们都会选择保存,以便后面有空时,接着游玩。接下来,我们会学习一些Unity有关的存储方法。

一:Unity-PlayerPrefs(数据持久化)

这是Unity自带的用于本地持久化保存与读取的类,采用的是键值对的方式来进行存储,一般通过键名来进行获取。PlayerPrefs有Int,float,string类型。

保存数据
PlayerPrefs.SetString(“Color”, color);
PlayerPrefs.SetInt(“Number”, number);
PlayerPrefs.SetFloat(“Class”, class);

读取数据
color = PlayerPrefs.GetString(“Color”, “red”);
number = PlayerPrefs.GetInt(“Number”, 20);
class = PlayerPrefs.GetFloat(“Class”, 1);

其他一些方法
PlayerPrefs.HasKey();//是否包含该键值对
PlayerPrefs.DeleteAll();//删除所有键值对
PlayerPrefs.DeleteKey();//删除某一个键值对

二:Unity-Serialization(序列化)和Deserialization(反序列化)

在Unity中我们也可以通过使用序列化来实现存储,我们将要存储的对象用序列化转化为字节流来进行存储,当我们需要用时我们通过反序列化将字节流转化为对象来进行读取。

常见的序列化方法:二进制,XML,Json.

这三种方法各有千秋,以下是对这三种方法对比

二进制方法:简单,但可读性差

XML方法:可读性强,但是文件庞大,冗余信息多

JSON方法:数据格式比较简单,易于读写,但是不直观,可读性比XML差

接下来我会讲解一些关于二进制的方法,关于XML以及Json方法我会放在下次来讲。

二进制序列方法(Binary Formatter)

序列化:新建或打开一个二进制文件,通过二进制格式器将对象写入该二进制文件。
反序列化:打开待反序列化的二进制文件,通过二进制格式器将文件解析成对象。

在进行存储之前我们要定义一个类用来存储我们需要保存的东西

实例:比如飞行射击游戏《飞机大战》中需要保存玩家数据包括生命值,得分,技能数以及当前关卡敌机位置和类型,现在这些数据都要保存在本地, 就需要用到序列化存储这些数据,下面分别介绍二进制,XML和JSON三种方法实现游戏的存档和读档首先写好一个存储游戏数据类,定义好需要保存的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization;
using System;
/// <summary>
/// 存储游戏数据类
/// </summary>
[Serializable] //可序列化标志
public class SaveGameData{undefined
 
public List<SerializableVector3> livingEnemyPostions = new List<SerializableVector3>();
public List<int> livingEnemyTypes = new List<int>();
public int score = 0;
public int hp = 0;
public int skillCount = 0;
 
}
/*
* 序列化在unity中的注意点
* 不可以直接序列化Unity特有的数据类型(例如Vector3, Quaternion),必须要转换一下
*/
//在Vector3 和 SerializableVector3之间自动转换
[Serializable]
public struct SerializableVector3
{undefined
public float x;
public float y;
public float z;
//构造函数
public SerializableVector3(float rX, float rY, float rZ)
{undefined
x = rX;
y = rY;
z = rZ;
}
 
// 以字符串形式返回,方便调试查看
public override string ToString()
{undefined
return String.Format("[{0}, {1}, {2}]", x, y, z);
}
 
// 隐式转换:将SerializableVector3 转换成 Vector3
//implicit关键字属于转换运算符,表示隐式的类型转换,可以让我们自定义的类型支持相互交换。
public static implicit operator Vector3(SerializableVector3 rValue)
{undefined
return new Vector3(rValue.x, rValue.y, rValue.z);
}
 
// 隐式转换:将Vector3 转成 SerializableVector3
public static implicit operator SerializableVector3(Vector3 rValue)
{undefined
return new SerializableVector3(rValue.x, rValue.y, rValue.z);
}
}

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
using System.Collections;
 
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
using System.IO; //实现文件流所必需的库
using System.Runtime.Serialization.Formatters.Binary; //实现二进制串行化必需的库
using LitJson;
using System.Xml;
 
public enum GameState
{undefined
playing,
gameOver,
pause
}
public class GameManager : MonoBehaviour {undefined
public static GameManager Instance;
public GameObject gameoverPanel;
public GameObject saveMenu;
public Transform spawn;
 
//左上角显示分数
public Text txt_Score;
//游戏结束面板当前分数
public Text txt_CurScore;
//历史分数
public Text txt_HistoryScore;
//玩家当前生命值
public Text txt_CurHP;
public Text messageText;
public Transform pause;
private int score;
private GameObject player;
private string path;
private GameState gs;
public GameState GS
{undefined
get { return gs; }
}
private void Awake()
{undefined
Instance = this;
//清空对象池
BulletPool.ClearPool();
path = Application.dataPath + "/StreamingFile"; //路径为根目录自定义StreamingFile文件夹下
//path = Application.streamingAssetsPath; 路径为根目录StreamingAssets文件夹下
}
void Start () {undefined
UpdateScore(0);
player = GameObject.FindGameObjectWithTag("Player");
gs = GameState.playing;
}
private void Update()
{undefined
if (Input.GetKeyDown(KeyCode.Escape))
{//按下ESC键调出Menu菜单,并将游戏状态改为暂停
Pause();
}
}
/// <summary>
/// 切换游戏状态
/// </summary>
/// <param name="_gs"></param>
public void SetGameState(GameState _gs)
{undefined
gs = _gs;
if (gs==GameState.gameOver)
{undefined
Debug.Log("game over!");
}
}
private void Pause()
{undefined
gs = GameState.pause;
Time.timeScale = 0;
saveMenu.SetActive(true);
pause.GetComponent<Image>().sprite = Resources.Load<Sprite>("ButtonIcon/game_resume");
}
private void UnPause()
{undefined
gs = GameState.playing;
Time.timeScale = 1;
saveMenu.SetActive(false);
pause.GetComponent<Image>().sprite = Resources.Load<Sprite>("ButtonIcon/game_pause");
}
public void ContinueGame()
{undefined
UnPause();
ShowMessage("");
}
/// <summary>
/// 点击按钮控制是否暂停游戏
/// </summary>
public void GamePause()
{undefined
if (gs == GameState.playing)
{undefined
Pause();
}
else
{undefined
UnPause();
}
}
public void UpdateScore(int _score)
{undefined
score += _score;
//更新当前得分
txt_Score.text = score.ToString();
}
public void UpdateHP(int hp)
{undefined
txt_CurHP.text = hp.ToString();
}
 
//显示提示信息
public void ShowMessage(string str)
{undefined
messageText.text = str;
}
void SaveDate(int historyScore)
{undefined
//先取出历史数据
if (score > historyScore)
{//如果当前得分大于历史分数则覆盖历史最高分
PlayerPrefs.SetInt("score", score);
}
}
public void GameOver()
{undefined
gs = GameState.gameOver;
//显示当前分数
txt_CurScore.text = score.ToString();
//取出历史最高分
int historyScore = PlayerPrefs.GetInt("score", 0);
//显示最高分
txt_HistoryScore.text = historyScore.ToString();
//每次游戏结束需要把最高分保存下来
SaveDate(historyScore);
gameoverPanel.SetActive(true);
}
/// <summary>
/// 重新开始游戏
/// </summary>
public void Restart()
{undefined
UnityEngine.SceneManagement.SceneManager.LoadScene("01_play");
UnPause();
}
/// <summary>
/// 退出游戏
/// </summary>
public void Quit()
{undefined
Application.Quit();
}
/// <summary>
/// 保存游戏
/// </summary>
public void SaveGame()
{undefined
//SaveByBinary();
//SaveByJson();
SaveByXml();
}
/// <summary>
/// 加载游戏
/// </summary>
public void LoadGame()
{undefined
//LoadByBinary();
//LoadByJson();
LoadByXml();
 
}
/// <summary>
/// 创建SaveGameData对象并存储当前游戏状态信息数据
/// </summary>
/// <returns></returns>
private SaveGameData CreateSaveObject()
{
SaveGameData save = new SaveGameData();
if (spawn.childCount > 0)
{
//如果有敌机就把敌机的位置信息和类型添加到List中
GameObject[] enemys = new GameObject[spawn.childCount];
//List<GameObject> enemys = new List<GameObject>();
for (int i = 0; i < spawn.childCount; i++)
{
//enemys.Add(spawn.GetChild(i).gameObject);
enemys[i] = spawn.GetChild(i).gameObject;
}
foreach (GameObject enemyGO in enemys)
{
EnemyControl enemyControl = enemyGO.GetComponent<EnemyControl>();
save.livingEnemyPostions.Add(enemyGO.transform.localPosition);
int type = enemyControl.enemyType;
save.livingEnemyTypes.Add(type);
}
}
//把score,hp和skillCount保存在save对象中
save.score = score;
save.hp = player.GetComponent<PlayerHealth>().curHP;
save.skillCount = player.GetComponent<PlayerControl>().skillCount;
return save;
}
/// <summary>
/// 通过读档信息将游戏重置为保存游戏时的状态
/// </summary>
/// <param name="save"></param>
private void SetGame(SaveGameData save)
{undefined
//GameObject[] enemys = new GameObject[spawn.childCount];
//通过反序列化的得到的对象中存储的信息在指定位置生成对应类型的敌人
for (int i = 0; i < save.livingEnemyPostions.Count; i++)
{undefined
Vector3 pos = save.livingEnemyPostions[i];
int type = save.livingEnemyTypes[i];
GameObject enemy = Instantiate(Resources.Load<GameObject>("prefabs/enemy" + type.ToString()), spawn);
enemy.transform.localPosition = pos;
}
//更新UI显示
score = save.score;
txt_Score.text = score.ToString();
player.GetComponent<PlayerHealth>().curHP = save.hp;
UpdateHP(player.GetComponent<PlayerHealth>().curHP);
player.GetComponent<PlayerControl>().skillCount = save.skillCount;
player.GetComponent<PlayerControl>().UpdateSkillCount();
UnPause();
}
//二进制方法:存档和读档
private void SaveByBinary()
{undefined
//序列化过程——(将SaveGameData对象转化为字节流)
//创建SaveGameData对象并保存当前游戏状态信息
SaveGameData save = CreateSaveObject();
//创建一个二进制格式化程序
BinaryFormatter bf = new BinaryFormatter();
//创建一个文件流
FileStream fileStream = File.Create(path + "/savebyBin.txt");
//调用二进制格式化程序的序列化方法来序列化save对象 参数:创建的文件流和需要序列化的对象
bf.Serialize(fileStream, save);
//关闭流
fileStream.Close();
//如果文件存在,则显示保存成功
if(File.Exists(path+ "/savebyBin.txt"))
{undefined
ShowMessage("保存成功");
}
}
private void LoadByBinary()
{undefined
if(File.Exists(path + "/savebyBin.txt"))
{undefined
//反序列化过程——(将字节流转化为对象)
//创建一个二进制格式化程序
BinaryFormatter bf = new BinaryFormatter();
//打开一个文件流
FileStream fileStream = File.Open(path + "/savebyBin.txt", FileMode.Open);
//调用二进制格式化程序的反序列化方法,将文件流转化为对象
SaveGameData save = (SaveGameData)bf.Deserialize(fileStream);
//关闭文件流
fileStream.Close();
 
SetGame(save);
ShowMessage("");
}
else
{undefined
ShowMessage("存档文件不存在");
}
}
 
//Xml:存档和读档
private void SaveByXml()
{undefined
SaveGameData save = CreateSaveObject();
//创建Xml文件的存储路径
string filePath = Application.dataPath + "/StreamingFile" + "/savebyXML.txt";
//创建XML文档实例
XmlDocument xmlDoc = new XmlDocument();
//创建root根节点,也就是最上一层节点
XmlElement root = xmlDoc.CreateElement("save");
//设置根节点中的值
root.SetAttribute("name", "saveFile");
//创建下一层XmlElement节点元素
//XmlElement enemy;
//XmlElement enemyPosition;
//XmlElement enemyType;
//遍历save中存储的数据,并将数据转换为XML格式
for (int i = 0; i < save.livingEnemyPostions.Count; i++)
{undefined
//创建下一层XmlElement节点元素
XmlElement enemy = xmlDoc.CreateElement("enemy");
XmlElement enemyPosition = xmlDoc.CreateElement("enemyPosition");
//设置节点中的值
enemyPosition.InnerText = save.livingEnemyPostions[i].ToString();
XmlElement enemyType = xmlDoc.CreateElement("enemyType");
enemyType.InnerText = save.livingEnemyTypes[i].ToString();
//设置节点的层级关系(把节点一层一层的添加至XMLDoc中 ,注意先后顺序,这将是生成XML文件的顺序)
enemy.AppendChild(enemyPosition);
enemy.AppendChild(enemyType);
root.AppendChild(enemy);
}
XmlElement score = xmlDoc.CreateElement("score");
score.InnerText = save.score.ToString();
root.AppendChild(score);
 
XmlElement hp = xmlDoc.CreateElement("hp");
hp.InnerText = save.hp.ToString();
root.AppendChild(hp);
 
XmlElement skillCount = xmlDoc.CreateElement("skillCount");
skillCount.InnerText = save.skillCount.ToString();
root.AppendChild(skillCount);
 
xmlDoc.AppendChild(root);
//把XML文件保存至本地
xmlDoc.Save(filePath);
 
if(File.Exists(filePath))
{undefined
ShowMessage("保存成功");
}
}
private void LoadByXml()
{undefined
string filePath = Application.dataPath + "/StreamingFile" + "/savebyXML.txt";
if(File.Exists(filePath))
{undefined
//加载XML文档
XmlDocument xmlDoc = new XmlDocument();
//根据路径将XML读取出来
xmlDoc.Load(filePath);
 
SaveGameData save = new SaveGameData();
//通过节点名称来获取元素,结果为XmlNodeList类型
//XmlNodeList enemylist = xmlDoc.GetElementsByTagName("enemy");
 
XmlNodeList enemylist = xmlDoc.SelectNodes("enemy");
//遍历所有子节点并获得子节点的InnerText值
if(enemylist.Count>0)
{undefined
 
foreach (XmlNode enemy in enemylist)
{undefined
XmlNode enemyPosition = enemy.ChildNodes[0];
string posStr = enemyPosition.InnerText;
//删除首尾的字符
posStr = posStr.TrimStart('[');
posStr = posStr.TrimEnd(']');
string[] strArray = posStr.Split(',');
Vector3 enemyPos;
enemyPos.x = float.Parse(strArray[0]);
enemyPos.y = float.Parse(strArray[1]);
enemyPos.z = float.Parse(strArray[2]);
//将的得到的位置信息存储到save中
save.livingEnemyPostions.Add(enemyPos);
 
XmlNode enemyType = enemy.ChildNodes[1];
int enemyTypeIndex = int.Parse(enemyType.InnerText);
save.livingEnemyTypes.Add(enemyTypeIndex);
}
}
 
//XmlNodeList scoreList = xmlDoc.GetElementsByTagName("score");
//int score= int.Parse(scoreList[0].InnerText);
//SelectSingleNode只能寻找单个子节点
XmlNode scoreNode = xmlDoc.SelectSingleNode("save").SelectSingleNode("score");
int score = int.Parse(scoreNode.InnerText);
save.score =score;
 
XmlNodeList hpList = xmlDoc.GetElementsByTagName("hp");
int hp = int.Parse(hpList[0].InnerText);
save.hp = hp;
 
XmlNodeList skillCountList = xmlDoc.GetElementsByTagName("skillCount");
int skillCount = int.Parse(skillCountList[0].InnerText);
save.skillCount = skillCount;
 
SetGame(save);
ShowMessage("");
}
else
{undefined
ShowMessage("存档文件不存在");
}
}
 
//Json:存档和读档
private void SaveByJson()
{undefined
//将save对象转换为Json格式的字符串
//创建SaveGameData对象并保存当前游戏状态信息
SaveGameData save = CreateSaveObject();
string filePath = Application.dataPath + "/StreamingFile" + "/savebyJson.json";
//方法1:利用第三方LitJson库中的JsonMapper将save对象转换为Json格式的字符串
//string saveJsonStr = JsonMapper.ToJson(save);
/*备注
* 注意::用LitJson调用JsonMapper在进行类转json字符串时,如果类的属性中有json不能识别的数据类型,例如float类型会报错JsonException:
*原因:用于序列化或者反序列化的数据,其类型必须是下面几种
LiteJosn的数据类型支持下面几种,否则会报错
public JsonData(bool boolean);
public JsonData(double number);
public JsonData(int number);
public JsonData(long number);
public JsonData(object obj);
public JsonData(string str);
*/
//方法2:利用系统自带JsonUtility将save对象转换为Json格式的字符串
string saveJsonStr = JsonUtility.ToJson(save);
//将字符串写入到文件中进行存储
//创建StreamWriter并将字符串写入
StreamWriter sw = new StreamWriter(filePath);
sw.Write(saveJsonStr);
//关闭StreamWriter
sw.Close();
ShowMessage("保存成功");
}
private void LoadByJson()
{undefined
string filePath = Application.dataPath + "/StreamingFile" + "/savebyJson.json";
if (File.Exists(filePath))
{undefined
//创建StreamReader用来读取流
StreamReader sr = new StreamReader(filePath);
//将读取到的流赋值给Json格式的字符串
string jsonStr = sr.ReadToEnd();
//关闭
sr.Close();
 
//将字符串jsonStr转换为SaveGameData对象(解析)
//方法1:利用第三方LitJson库中的JsonMapper解析
//SaveGameData save = JsonMapper.ToObject<SaveGameData>(jsonStr);
//方法2:利用系统自带JsonUtility解析
SaveGameData save =JsonUtility.FromJson<SaveGameData>(jsonStr);
SetGame(save);
 
ShowMessage("");
}
else
{undefined
ShowMessage("存档文件不存在");
}
}
}
posted @   多见多闻  阅读(1502)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示