UnityXML存檔與加密

前言

不管開發(fā)什么游戲,游戲存檔是個(gè)必不可少的功能,你可能需要保存玩家的一些信息,比如身上穿戴的裝備,玩家角色所處的場(chǎng)景等各種信息,對(duì)于存檔功能(數(shù)據(jù)持久化),Unity提供了原生技術(shù)Playerprefs,它的優(yōu)點(diǎn)是理解和使用起來十分簡(jiǎn)單,缺點(diǎn)是對(duì)于大型數(shù)據(jù)存儲(chǔ)時(shí)會(huì)力不從心,所以本文會(huì)介紹如何使用XML來實(shí)現(xiàn)游戲存檔和存檔加密的功能。

編程環(huán)境

  • Unity 5.2.2
  • OS X EI Capitan 10.11.6

你將學(xué)到什么?

  • 如何使用XML對(duì)數(shù)據(jù)進(jìn)行序列化和反序列化操作。
  • 如何對(duì)數(shù)據(jù)進(jìn)行加密和解密操作。
  • 不同平臺(tái)下文件存放的具體路徑和規(guī)則。

整體思路解析

數(shù)據(jù)存儲(chǔ)和加密的主要邏輯思路:

  1. 使用XmlSerializer類對(duì)需要保存的數(shù)據(jù)類進(jìn)行序列化操作,得到一串字符串。
  2. 將得到的字符串使用RijndaeManaged類和ICrytoTransform類進(jìn)行加密操作,獲得加密之后的字符串。
  3. 根據(jù)平臺(tái)類型,確定文件保存的路徑。
  4. 使用StreamWriter類將字符串保存到文件中。

數(shù)據(jù)加載和解密的主要邏輯思路:

  1. 根據(jù)存檔文件的路徑,使用StreamReader類讀取文件中的內(nèi)容(一串加密過的字符串)。
  2. 使用RijndaeManaged類和ICrytoTransform類對(duì)讀取的文件內(nèi)容進(jìn)行解密操作,獲得一串解密后的字符串。
  3. 使用XmlSerializer類對(duì)解密后的字符串進(jìn)行反序列化操作,獲得具體的游戲數(shù)據(jù),并使用數(shù)據(jù)對(duì)游戲中的數(shù)據(jù)進(jìn)行轉(zhuǎn)換操作。

一、準(zhǔn)備工作

在實(shí)現(xiàn)具體保存操作前,我們需要先實(shí)現(xiàn)我們要保存的游戲數(shù)據(jù),本文使用一個(gè)簡(jiǎn)化的數(shù)據(jù),假設(shè)我們保存的數(shù)據(jù)是MyPlayer類,里面記錄著以下信息:

  • 玩家的名字
  • 玩家的等級(jí)
  • 玩家的武器(包括物品ID,物品名字信息)
  • 玩家的衣服(包括物品ID,物品名字信息)

下面我們就來具體實(shí)現(xiàn)這些數(shù)據(jù),首先我們新建一個(gè)Unity工程,工程名字大家可以自定義,然后我們新建一個(gè)C#文件,命名為Item,打開item文件,并進(jìn)行如下編輯:

image
  • 在Item類中,我們定義了兩個(gè)公共成員,_itemID和_name,分別代表物品的ID和名字。
  • 然后我們?cè)跇?gòu)造函數(shù)里面初始化了物品ID和名稱。
  • 最后我們又創(chuàng)建了一個(gè)構(gòu)造函數(shù),可以通過參數(shù)來指定Item的id和名字。

接下來我們?cè)傩陆ㄒ粋€(gè)C#文件,命名為MyPlayer,打開Myplayer文件,并進(jìn)行如下編輯:

image
  • 在MyPlayer類中,我們定義了4個(gè)公共成員,他們是_id,_name,_weapon和_clothes,他們分別代表玩家的ID,名字,所擁有的武器和衣服。
  • 然后我們實(shí)現(xiàn)了MyPlayer的構(gòu)造函數(shù),在構(gòu)造函數(shù)里面我們實(shí)例化了他的4個(gè)公共成員。

二、實(shí)現(xiàn)XmlManager的序列化與寫入操作

有了需要存儲(chǔ)的數(shù)據(jù),下面我們可以來實(shí)現(xiàn)如何將數(shù)據(jù)用XML來序列化,并且將序列化的數(shù)據(jù)寫入到文件中。

首先我們新建一個(gè)C#腳本,命名為XmlManager,然后打開腳本進(jìn)行如下編輯:

image
  • 通過上圖我們可以看到,在XmlManager腳本中,我們實(shí)現(xiàn)了3個(gè)方法
  • 在serializeObject方法中我們首先創(chuàng)建MemoryStream對(duì)象,這是為了后面我們創(chuàng)建XmlTextWriter類時(shí)的準(zhǔn)備,因?yàn)槲覀儠?huì)通過指定流和編碼方式來創(chuàng)建XmlTextWriter的實(shí)例對(duì)象。
  • 接著我們通過指定數(shù)據(jù)類型創(chuàng)建了一個(gè)Xmlserializer的實(shí)例對(duì)象xs,然后通過調(diào)用xs的Serialize方法對(duì)傳入的pObject進(jìn)行序列化。
  • 然后我們把xmlTextWriter.BaseStream強(qiáng)制轉(zhuǎn)換成MemoryStream類型,并賦值給mStream。
  • 最后通過調(diào)用UTF8ByteArrayToString方法將mStream數(shù)據(jù)轉(zhuǎn)換成string類型,并返回?cái)?shù)據(jù)。
  • 在CreateXMl方法中,我們通過傳入的參數(shù),指定了文件保存的位置,以及需要保存的具體數(shù)據(jù),然后通過StreamWriter類將數(shù)據(jù)寫入到文件中。
  • 在UTF8ByteArrayToString方法中,我們通過UTF8Encoding將byte[]類型數(shù)據(jù)轉(zhuǎn)換成了String類型。

三、實(shí)現(xiàn)GameDataManager保存操作

首先在場(chǎng)景中新增一個(gè)空的對(duì)象,然后將其命名為DataController,然后在上面掛載一個(gè)我們新建的C#腳本GameDataManager

image

然后我們打開GameDataManager腳本進(jìn)行如下的編輯:

image

編寫上圖中的代碼后,我們回到Unity編輯器,然后運(yùn)行,之后我們便會(huì)發(fā)現(xiàn)Project視窗中多了一個(gè)名為ZuiData的文件,如下圖:

image

然后我們打開ZuiData文件,就會(huì)發(fā)現(xiàn)里面保存著_myPlayer對(duì)象的數(shù)據(jù),如下圖:

image

到此,我們學(xué)會(huì)了將游戲數(shù)據(jù)序列化并寫入到文件中的操作了。

四、實(shí)現(xiàn)XmlManager的反序列化和讀取操作

學(xué)會(huì)了保存數(shù)據(jù)后,下一步我們就要來實(shí)現(xiàn)讀取文件數(shù)據(jù),并且將其反序列化,成為我們可以使用的對(duì)象。

我們?cè)俅未蜷_XmlManager腳本,新增以下代碼:

image
  • 首先我們看deserializeObject方法,我們通過傳入的參數(shù)ty,確定XmlSerializer需要反序列化的類型,然后需要反序列化的內(nèi)容從string轉(zhuǎn)換成byte[]類型,最后調(diào)用xs.Deserialize方法進(jìn)行反序列化操作,并返回其結(jié)果。
  • 接著我們看loadXML方法,有一個(gè)參數(shù),是需要讀取的文件名稱,然后創(chuàng)建一個(gè)StreamReader類型的對(duì)象,然后調(diào)用其方法ReadToEnd進(jìn)行讀取操作,最后返回讀取的內(nèi)容
  • stringToUTF8ByteArrayhasFile就很好理解了,一個(gè)是將string類型裝換成byte[]類型,一個(gè)是通過文件名判斷該文件是否存在。

五、實(shí)現(xiàn)GameDataManager的讀取數(shù)據(jù)操作

實(shí)現(xiàn)了XmlManager的反序列化和讀取操作后,我們就可以在GameDataManager中實(shí)現(xiàn)將xml的數(shù)據(jù)讀取,并且把這些數(shù)據(jù)轉(zhuǎn)換成我們需要使用的類型,比如轉(zhuǎn)換成我們_myPlayer的信息。

接下來,我們打開GameDataManager腳本,并新增以下代碼:

image
  • 首先在load方法中,先獲取文件存儲(chǔ)的路徑,然后判斷文件是否存在,如果不存在,則在后臺(tái)打印提示信息,接下來,調(diào)用xm.loadXML方法讀取文件中的數(shù)據(jù),讀取出來的數(shù)據(jù)是一段字符串,然后我們?cè)谡{(diào)用xm.deserializeObject方法把數(shù)據(jù)轉(zhuǎn)換成MyPlayer類型的數(shù)據(jù),最后如果數(shù)據(jù)不為空,我們就把這些數(shù)據(jù)賦值給_myPlayer對(duì)象。
  • pressLoadButton方法是后面我們制作讀取按鈕時(shí)會(huì)用的方法,里面主要做了兩件事情,一是調(diào)用load方法,讀取數(shù)據(jù),二是調(diào)用printData方法打印_myPlayer的部分屬性。
  • printData方法中,我們調(diào)用Debug.log方法打印出我們想要看的_myPlayer的屬性,而這里我們打印的屬性,是后面我們修改過具體內(nèi)容的幾個(gè)數(shù)據(jù),打印出來就是為了查看是否修改成功。

制作讀取按鈕

有了上面這些方法后,為了在實(shí)際演示中,讓我們可以看到數(shù)據(jù)的讀取后的改變,我們?cè)陧?xiàng)目中新建一個(gè)按鈕,來觸發(fā)數(shù)據(jù)讀取的操作。

image

我們?cè)陧?xiàng)目中新建一個(gè)名為“LoadButton”的按鈕,然后將其內(nèi)容改為“讀取數(shù)據(jù)”,然后我們?cè)诎粹o的點(diǎn)擊邏輯上掛載GameDataManager中的pressLoadButton方法,如下圖:

image

實(shí)現(xiàn)按鈕后,我們打開ZuiData文件,將玩家的ID改為99,玩家名字改為“ZuiPlayer”,武器的名稱改為“Eagles”,如下圖:

image

修改之后,我們?cè)俅未蜷_GameDataManager腳本,把Start方法中的內(nèi)容全部注釋掉,如下圖:

image

最后我們回到Unity編輯器,然后運(yùn)行程序,點(diǎn)擊讀取數(shù)據(jù)按鈕,然后查看后臺(tái)打印出來的數(shù)據(jù)是否與我們修改過后的數(shù)據(jù)一致,如無意外,效果如下圖:

image

到此,我們學(xué)會(huì)了XML數(shù)據(jù)的讀取和反序列化。

六、對(duì)文件數(shù)據(jù)進(jìn)行加密和解密

雖然我們現(xiàn)在學(xué)會(huì)了使用xml進(jìn)行數(shù)據(jù)的存儲(chǔ)和讀取,但是就想我們上面讀取操作時(shí)一樣,我們可以直接通過改寫ZuiData文件里面的內(nèi)容,從而改變游戲的數(shù)據(jù),這樣對(duì)于游戲數(shù)據(jù)來說是很不安全的,所以我們最好對(duì)游戲最終保存的數(shù)據(jù)進(jìn)行一些加密操作,這樣就無法通過文件直接修改游戲的數(shù)據(jù)了。

我們?cè)俅未蜷_XmlManager腳本,然后新增以下代碼:

image
  • 首先我們?cè)谇懊嬉肓诵碌念^文件System.Security.Cryptography,我們下面需要用的RijndaelManagedICryptTransform類,都是屬于其中。
  • 接下來我們定義了我們加密和解密所需要用的密鑰,具體的數(shù)字可以自定義,但是必須一共是32位。
  • 然后我們先跳到上圖最后的getRijndaelManaged方法,這里面我們主要是創(chuàng)建并定義我們加密和解密的方式,我們定義了一個(gè)RijndaelManaged對(duì)象,然后設(shè)置密鑰為_keyArray(也就是我們之前定義的密鑰),然后設(shè)置對(duì)稱解密算法的運(yùn)算模式和填充模式(關(guān)于運(yùn)算模式和填充模式的詳細(xì)說明,大家可以參見最后面的參考鏈接),最后我們返回RijndaelManaged的實(shí)例化對(duì)象。
  • 接下來我們看到encrypt方法,這里面我們主要是把傳入的數(shù)據(jù),進(jìn)行加密操作,然后返回加密后的數(shù)據(jù),首先我們定義了一個(gè)ICryptTransform類型的對(duì)象,并且他是加密模式的,然后我們把需要加密的數(shù)據(jù)類型轉(zhuǎn)換成byte[]類型,調(diào)用TransformFinalBlock方法得到加密后的數(shù)據(jù),最后將這個(gè)數(shù)據(jù)轉(zhuǎn)換成string類型并返回。
  • 最后我們來看decrypt方法,這里我們主要把傳入的數(shù)據(jù),進(jìn)行解密操作,然后返回解密后的數(shù)據(jù),首先我們定義了一個(gè)ICryptTransform類型的對(duì)象,并且他是解密模式的,然后我們把需要解密的數(shù)據(jù)轉(zhuǎn)換成byte[]類型,調(diào)用TransformFinalBlock方法獲得解密后的數(shù)據(jù),最后將這個(gè)數(shù)據(jù)轉(zhuǎn)換成string類型并返回。

完成了這些主要的方法后,我們還需要對(duì)XmlManager腳本進(jìn)行一些小修改,具體見下圖:

image

做完這些改動(dòng)后,我們?cè)侔?strong>GameDataManager腳本中的Start方法中的代碼注釋取消掉,讓其恢復(fù)作用,最后我們回到Unity編輯器,運(yùn)行程序,可以看到我們save和load操作都是正常運(yùn)行的,而這個(gè)時(shí)候我們?cè)俅未蜷_ZuiData文件,就會(huì)發(fā)現(xiàn)文件中的內(nèi)容變成了一些亂碼,這樣就無法通過修改存檔來改變游戲的數(shù)據(jù)了。

image

參考閱讀

MSDN 關(guān)于RijndaelManaged類的說明
MSDN 關(guān)于ICryptoTransform類的說明

補(bǔ)充內(nèi)容

  • 需要序列化的成員必須是public的,私有的是不會(huì)被序列化的,如果某個(gè)成員變量不想序列化,有兩種方法,一是設(shè)置為私有,二是使用[XmlIgnore]修飾,如下圖:
image
  • 如果一個(gè)成員變量的值為null,序列化時(shí)不會(huì)記錄任何信息。
  • 自定義序列化類,必須要有默認(rèn)的構(gòu)造函數(shù)(即不帶任何參數(shù)的構(gòu)造函數(shù)),否則會(huì)報(bào)錯(cuò)。

作者:Zui
鏈接:http://www.itdecent.cn/p/29e3b50deac1
來源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 12,338評(píng)論 6 13
  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí),會(huì)觸發(fā)此異常。 O...
    我想起個(gè)好名字閱讀 5,946評(píng)論 0 9
  • 前言 不管開發(fā)什么游戲,游戲存檔是個(gè)必不可少的功能,你可能需要保存玩家的一些信息,比如身上穿戴的裝備,玩家角色所處...
    Zui閱讀 7,276評(píng)論 6 33
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,632評(píng)論 1 32
  • JAVA序列化機(jī)制的深入研究 對(duì)象序列化的最主要的用處就是在傳遞,和保存對(duì)象(object)的時(shí)候,保證對(duì)象的完整...
    時(shí)待吾閱讀 11,179評(píng)論 0 24

友情鏈接更多精彩內(nèi)容