Unity下的简单多账号角色游戏存档功能介绍

    我们知道 在RPG里单机游戏里 游戏存档功能必不可少 在这里 网上有个教程 非常的好 是基于xml下的保存 保存成dat文件(数据类型) 在这里 主要是针对网上那个教程做一个简单的介绍 让大家可以随心所欲的用那个封装好的存档 可以自我增加功能


一、此套存档脚本分为6个脚本  先介绍哪些脚本 然后再具体脚本的作用(tag的取值是可以由用户更改的 只是你要在相应的脚本改成你自己设置的名字)

        1.Sence脚本 2.GameDataManager脚本 3.Xmlsacer脚本 4.SenceData脚本 5.SaveCube脚本(这个脚本的名字是根据情况取的 在Demo中我是用来存档cube位置信息) 6.UI脚本

二、这个脚本分别挂在哪些游戏对象上

    1.Sence脚本不需要挂在游戏对象上 存在就行

    2.GameDataManager脚本放在一个空物体上(取名GameDataManager)空物体Tag也设置取名GameDataManage

    3.Xmlsacer脚本是xml保存数据的支撑脚本里面全是固定代码不需要用户编写  直接粘贴到工程就行 也放在名为GameDataManager空物体上

  4.SenceData脚本 放在空物体上 空物体名字叫SenceData 空物体Tag也设置取名SenceData

  5.SavePlayer 脚本也放在SenceData 空物体下玩家要设置tag值 随意取名字 只是要在相应脚本里把名字改成自己设的

6.U脚本 挂在空物体上 取名UI


三、各个脚本的作用 (在这里 我保存的是一个cube的位置信息 以及缩放比例)

    1.sence脚本

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

[SerializeField]public class Sence

{

// 在这里声明你要保存的属性 其他的东西都不需要动

public Vector3 cubePoision ; // cube位置信息

public Vector3 cubeScale ; // cube的放大比例

}

public class sence : MonoBehaviour {

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

}

2.GameDataManage脚本 注意保存存档名称和保存路径即可 其他不需要动

using System.Collections;

using System.Collections.Generic;

using UnityEngine; 

using System.IO ; // 文件输入与输出 操作文件读取和写入时用到

using System.Collections.Generic ;

using System.Text ;

using System.Xml ;

using System.Security.Cryptography ;

using System ;

//GameData,储存数据的类,把需要储存的数据定义在GameData之内就行//

public class GameData

{

// 密钥 用于防止拷贝存档

public string key ;

// 需要存储的内容 现在只存储一个bool值 点击读档为true 加载场景后为false

public bool isDu ;

// 场景存储内容

public Sence s ;

public GameData()

{

s = new Sence (); // 存储内容初始化 只能放构造函数

isDu = false;

}

}

public class GameDataManager : MonoBehaviour {

//  存档文件的名称 特别注意一下这条代码 "csData1.dat" 如果你想实现多账号存储 这里 你要穿过来账号的名称 每次账号不同 保存文件名不同 文件名不同 保存的存档就不同 这就是为什么可以多账号存储的原因

private string dataFileNmae = "csData1.dat" ;

public GameData gameData ;

private  XmlSaver xs = new XmlSaver();

void Awake()

{

gameData = new GameData ();

// 设置密钥 根据具体平台设定

gameData.key = SystemInfo.deviceUniqueIdentifier ;

Load ();

}

// 读档时调用的方法

public void Load()

{

string gameDataFile = GetDataPath () + "/" + dataFileNmae;

if (xs.hasFile (gameDataFile)) {

string dataString = xs.LoadXML (gameDataFile);

GameData gameDataFromXML = xs.DeserializeObject (dataString, typeof(GameData)) as GameData;

// 如果是合法存档

if (gameDataFromXML.key == gameData.key) {

gameData = gameDataFromXML;

} else {

// 游戏启动后数据清零 存档后作弊用

}

} else {

if (gameData != null) {

Save ();

}

}

}

public void Save()

{

string gameDataFile = GetDataPath() + "/"+dataFileNmae;

string dataString= xs.SerializeObject(gameData,typeof(GameData));

xs.CreateXML(gameDataFile,dataString);

}

// 获取路径

private static string GetDataPath()

{

// 判断当前使用设备为苹果手机 本例子针对pc端 移动端返回路径根据苹果系统与安卓系统的不同自己设置

if (Application.platform == RuntimePlatform.IPhonePlayer) {

return null;

} else {

// 返回系统文件路径 这里返回的是Application.dataPath 如果你想返回Application.dataPath下的文件夹 自己在后面加文件夹名字

return Application.dataPath  ;

}

}

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}


3.Xmlsacer脚本位固定代码 直接拷贝就行 不用管

using UnityEngine;

using System.Collections;

using System.Xml;

using System.Xml.Serialization;

using System.IO;

using System.Text;

using System.Security.Cryptography;

using System;

public class XmlSaver :MonoBehaviour{

//内容加密

public string Encrypt(string toE)

{

//加密和解密采用相同的key,具体自己填,但是必须为32位//

byte[] keyArray = UTF8Encoding.UTF8.GetBytes("12348578902223367877723456789012");

RijndaelManaged rDel = new RijndaelManaged();

rDel.Key = keyArray;

rDel.Mode = CipherMode.ECB;

rDel.Padding = PaddingMode.PKCS7;

ICryptoTransform cTransform = rDel.CreateEncryptor();

byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toE);

byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray,0,toEncryptArray.Length);

return Convert.ToBase64String(resultArray,0,resultArray.Length);

}

//内容解密

public string Decrypt(string toD)

{

//加密和解密采用相同的key,具体值自己填,但是必须为32位//

byte[] keyArray = UTF8Encoding.UTF8.GetBytes("12348578902223367877723456789012");

RijndaelManaged rDel = new RijndaelManaged();

rDel.Key = keyArray;

rDel.Mode = CipherMode.ECB;

rDel.Padding = PaddingMode.PKCS7;

ICryptoTransform cTransform = rDel.CreateDecryptor();

byte[] toEncryptArray = Convert.FromBase64String(toD);

byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray,0,toEncryptArray.Length);

return UTF8Encoding.UTF8.GetString(resultArray);

}

public string SerializeObject(object pObject,System.Type ty)

{

string XmlizedString  = null;

MemoryStream memoryStream  = new MemoryStream();

XmlSerializer xs  = new XmlSerializer(ty);

XmlTextWriter xmlTextWriter  = new XmlTextWriter(memoryStream, Encoding.UTF8);

xs.Serialize(xmlTextWriter, pObject);

memoryStream = (MemoryStream)xmlTextWriter.BaseStream;

XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray());

return XmlizedString;

}

public object DeserializeObject(string pXmlizedString , System.Type ty)

{

XmlSerializer xs  = new XmlSerializer(ty);

MemoryStream memoryStream  = new MemoryStream(StringToUTF8ByteArray(pXmlizedString));

XmlTextWriter xmlTextWriter  = new XmlTextWriter(memoryStream, Encoding.UTF8);

return xs.Deserialize(memoryStream);

}

//创建XML文件

public void CreateXML(string fileName,string thisData)

{

string xxx = Encrypt(thisData);

StreamWriter writer;

writer = File.CreateText(fileName);

writer.Write(xxx);

writer.Close();

}

//读取XML文件

public string LoadXML(string fileName)

{

StreamReader sReader = File.OpenText(fileName);

string dataString = sReader.ReadToEnd();

sReader.Close();

string xxx = Decrypt(dataString);

return xxx;

}

//判断是否存在文件

public bool hasFile(String fileName)

{

return File.Exists(fileName);

}

public string UTF8ByteArrayToString(byte[] characters  )

{

UTF8Encoding encoding  = new UTF8Encoding();

string constructedString  = encoding.GetString(characters);

return (constructedString);

}

public byte[] StringToUTF8ByteArray(String pXmlString )

{

UTF8Encoding encoding  = new UTF8Encoding();

byte[] byteArray  = encoding.GetBytes(pXmlString);

return byteArray;

}

}


4.SenceData脚本. 这里你要把你所保存的东西的脚本名字tag值修改成你自己的

using System.Collections;

using System.Collections.Generic;

using UnityEngine;public class SenceData : MonoBehaviour

{// Use this for initializationvoid Start () {SaveCube sc = gameObject.GetComponent();

sc.Load ();

//末尾设置默认不读挡

GameObject gameDataManager = GameObject.Find ("GameDataManager");

GameDataManager g = gameDataManager.GetComponent();

g.gameData.isDu = false;

g.Save ();

}

public void Save (){

/存档数据//Cube信息的保存SaveCube sc = gameObject.GetComponent();

sc.Save();

}

// Update is called once per frame

void Update () {

}

}


5.SaveCube脚本 这里就是存档的本质了 在游戏结束后 把最后cube的属性值赋予给sence脚本里你要保存的信息 同时 在读档时 把保存下来的给控制cube信息实际的属性(切记:要理解两个点 1.你把cube最后的位置给保存的属性 最后的位置一定是可以决定cube位置的 比如说。cube.position系统方法 修改 cube.position cube的位置一定会被改变 2.读档时 你把保留下来的值给能决定当前对象的属性 这样一运行 就是你结束游戏时那个样子 )

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class SaveCube : MonoBehaviour {

// Use this for initialization

void Start () {

}

// Update is called once per frame

void Update () {

}

// 保存cube的部分数据 这里只保存cube的位置信息

public void Save(){GameObject cube = GameObject.Find ("Cube");

GameObject gameDataManager = GameObject.Find ("GameDataManager"); 

这里是保存cube在游戏结束时最后的位置 把它赋予给你在sence脚本创的属性

// 拿到游戏对象管理的脚本GameDataManager g = gameDataManager.GetComponent() ;

g.gameData.s.cubePoision = cube.transform.position;

g.gameData.s.cubeScale = cube.transform.localScale;

}

////// 场景开始时加载游戏对象

///public void Load(){GameObject cube = GameObject.Find("Cube");

这里是赋值 cube在游戏结束时保存的位置 把它赋予给能决定cube位置的属性

//获得Cube对象GameObject gameDataManager =GameObject.Find("GameDataManager");

//获得GameDataManager(游戏控制器)对象

GameDataManager g = gameDataManager.GetComponent();

//获得GameDataManager(游戏控制器)脚本

if(g.gameData.isDu == true){//如果是读档状态

cube.transform.position = g.gameData.s.cubePoision;//读取Cube的位置,并赋值给Cube

cube.transform.localScale = g.gameData.s.cubeScale ;

}

}

}


6. UI脚本 就是按钮的点击存档 以及读档 注意:游戏第一次运行 从来没有存档过 此时点击读档会报错  解决方案已经在代码体现 读者自己理解下

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

private GameDataManager g;//定义GameDataManager(游戏控制器)脚本

private SenceData s;//定义SenceData(场景加载器)脚本

public int senceNum;

void Awake()

{

Instant = this;

// 程序一运行 就获取isEnable的布尔值 如果此时还是假 也没有关系 当游戏第二次运行 就会变成真 那么 就读档 存档正常了

if (PlayerPrefs.GetString(LoginManager.instance.userName)=="true")

{

isEnable = true;

}

}

void Start ()

{GameObject gameDataManager = GameObject.Find ("GameDataManager");

//获得GameDataManager(游戏控制器)对象

g = gameDataManager.GetComponent();

//获得GameDataManager(游戏控制器)脚本

GameObject senceData = GameObject.Find ("SenceData");

//获得SenceData(场景加载器)对象

s = senceData.GetComponent();//获得SenceData(场景加载器)脚本

}

// 你的存档按钮方法回调

public void SaveBtn()

{

s.Save ();//场景提交数据

g.Save ();//日志保存

isEnable = true;

PlayerPrefs.SetString(LoginManager.instance.userName,"true");

}

// 读档按钮

public void ReadBtn()

{

// isEnable布尔值 为真才会去读取 这就是能解决游戏一运行去点击读档报错的方案 一出来isEnable为false 不会执行 方法 必须点击过保存 才会变成true 然后用系统单例PlayerPrefs把isEnable为真保存起来 下次运行 就是 读档就可以被点击了

if (isEnable)

{

g.gameData.isDu = true;//设置为可读档状态

g.Save();//日志保存为可读档状态

UnityEngine.SceneManagement.SceneManager.LoadScene(senceNum);//加载场景

senceNum++;

}

else

{

Debug.Log("还不能保存");

}

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容