前言
不管開發(fā)什么游戲苛吱,游戲存檔是個必不可少的功能,你可能需要保存玩家的一些信息阶剑,比如身上穿戴的裝備,玩家角色所處的場景等各種信息危号,對于存檔功能(數(shù)據(jù)持久化)牧愁,Unity提供了原生技術(shù)Playerprefs,它的優(yōu)點(diǎn)是理解和使用起來十分簡單外莲,缺點(diǎn)是對于大型數(shù)據(jù)存儲時會力不從心猪半,所以本文會介紹如何使用XML來實現(xiàn)游戲存檔和存檔加密的功能。
編程環(huán)境
- Unity 5.2.2
- OS X EI Capitan 10.11.6
你將學(xué)到什么偷线?
- 如何使用XML對數(shù)據(jù)進(jìn)行序列化和反序列化操作磨确。
- 如何對數(shù)據(jù)進(jìn)行加密和解密操作。
- 不同平臺下文件存放的具體路徑和規(guī)則淋昭。
整體思路解析
數(shù)據(jù)存儲和加密的主要邏輯思路:
- 使用XmlSerializer類對需要保存的數(shù)據(jù)類進(jìn)行序列化操作俐填,得到一串字符串。
- 將得到的字符串使用RijndaeManaged類和ICrytoTransform類進(jìn)行加密操作翔忽,獲得加密之后的字符串英融。
- 根據(jù)平臺類型盏檐,確定文件保存的路徑。
- 使用StreamWriter類將字符串保存到文件中驶悟。
數(shù)據(jù)加載和解密的主要邏輯思路:
- 根據(jù)存檔文件的路徑胡野,使用StreamReader類讀取文件中的內(nèi)容(一串加密過的字符串)。
- 使用RijndaeManaged類和ICrytoTransform類對讀取的文件內(nèi)容進(jìn)行解密操作痕鳍,獲得一串解密后的字符串硫豆。
- 使用XmlSerializer類對解密后的字符串進(jìn)行反序列化操作,獲得具體的游戲數(shù)據(jù)笼呆,并使用數(shù)據(jù)對游戲中的數(shù)據(jù)進(jìn)行轉(zhuǎn)換操作熊响。
一、準(zhǔn)備工作
在實現(xiàn)具體保存操作前诗赌,我們需要先實現(xiàn)我們要保存的游戲數(shù)據(jù)汗茄,本文使用一個簡化的數(shù)據(jù),假設(shè)我們保存的數(shù)據(jù)是MyPlayer類铭若,里面記錄著以下信息:
- 玩家的名字
- 玩家的等級
- 玩家的武器(包括物品ID洪碳,物品名字信息)
- 玩家的衣服(包括物品ID,物品名字信息)
下面我們就來具體實現(xiàn)這些數(shù)據(jù)叼屠,首先我們新建一個Unity工程瞳腌,工程名字大家可以自定義,然后我們新建一個C#文件镜雨,命名為Item嫂侍,打開item文件,并進(jìn)行如下編輯:
- 在Item類中荚坞,我們定義了兩個公共成員吵冒,_itemID和_name,分別代表物品的ID和名字西剥。
- 然后我們在構(gòu)造函數(shù)里面初始化了物品ID和名稱。
- 最后我們又創(chuàng)建了一個構(gòu)造函數(shù)亿汞,可以通過參數(shù)來指定Item的id和名字瞭空。
接下來我們再新建一個C#文件,命名為MyPlayer疗我,打開Myplayer文件咆畏,并進(jìn)行如下編輯:
- 在MyPlayer類中,我們定義了4個公共成員吴裤,他們是_id旧找,_name,_weapon和_clothes麦牺,他們分別代表玩家的ID钮蛛,名字鞭缭,所擁有的武器和衣服。
- 然后我們實現(xiàn)了MyPlayer的構(gòu)造函數(shù)魏颓,在構(gòu)造函數(shù)里面我們實例化了他的4個公共成員岭辣。
二、實現(xiàn)XmlManager的序列化與寫入操作
有了需要存儲的數(shù)據(jù)甸饱,下面我們可以來實現(xiàn)如何將數(shù)據(jù)用XML來序列化沦童,并且將序列化的數(shù)據(jù)寫入到文件中。
首先我們新建一個C#腳本叹话,命名為XmlManager偷遗,然后打開腳本進(jìn)行如下編輯:
- 通過上圖我們可以看到,在XmlManager腳本中驼壶,我們實現(xiàn)了3個方法
- 在serializeObject方法中我們首先創(chuàng)建MemoryStream對象氏豌,這是為了后面我們創(chuàng)建XmlTextWriter類時的準(zhǔn)備,因為我們會通過指定流和編碼方式來創(chuàng)建XmlTextWriter的實例對象辅柴。
- 接著我們通過指定數(shù)據(jù)類型創(chuàng)建了一個Xmlserializer的實例對象xs箩溃,然后通過調(diào)用xs的Serialize方法對傳入的pObject進(jìn)行序列化。
- 然后我們把xmlTextWriter.BaseStream強(qiáng)制轉(zhuǎn)換成MemoryStream類型碌嘀,并賦值給mStream涣旨。
- 最后通過調(diào)用UTF8ByteArrayToString方法將mStream數(shù)據(jù)轉(zhuǎn)換成string類型,并返回數(shù)據(jù)股冗。
- 在CreateXMl方法中霹陡,我們通過傳入的參數(shù),指定了文件保存的位置止状,以及需要保存的具體數(shù)據(jù)烹棉,然后通過StreamWriter類將數(shù)據(jù)寫入到文件中。
- 在UTF8ByteArrayToString方法中怯疤,我們通過UTF8Encoding將byte[]類型數(shù)據(jù)轉(zhuǎn)換成了String類型浆洗。
三、實現(xiàn)GameDataManager保存操作
首先在場景中新增一個空的對象集峦,然后將其命名為DataController伏社,然后在上面掛載一個我們新建的C#腳本GameDataManager。
然后我們打開GameDataManager腳本進(jìn)行如下的編輯:
編寫上圖中的代碼后塔淤,我們回到Unity編輯器摘昌,然后運(yùn)行,之后我們便會發(fā)現(xiàn)Project視窗中多了一個名為ZuiData的文件高蜂,如下圖:
然后我們打開ZuiData文件仰泻,就會發(fā)現(xiàn)里面保存著_myPlayer對象的數(shù)據(jù)论矾,如下圖:
到此,我們學(xué)會了將游戲數(shù)據(jù)序列化并寫入到文件中的操作了艾蓝。
四、實現(xiàn)XmlManager的反序列化和讀取操作
學(xué)會了保存數(shù)據(jù)后,下一步我們就要來實現(xiàn)讀取文件數(shù)據(jù),并且將其反序列化,成為我們可以使用的對象脂崔。
我們再次打開XmlManager腳本,新增以下代碼:
- 首先我們看deserializeObject方法梧喷,我們通過傳入的參數(shù)ty砌左,確定XmlSerializer需要反序列化的類型,然后需要反序列化的內(nèi)容從string轉(zhuǎn)換成byte[]類型铺敌,最后調(diào)用xs.Deserialize方法進(jìn)行反序列化操作汇歹,并返回其結(jié)果。
- 接著我們看loadXML方法偿凭,有一個參數(shù)产弹,是需要讀取的文件名稱,然后創(chuàng)建一個StreamReader類型的對象弯囊,然后調(diào)用其方法ReadToEnd進(jìn)行讀取操作痰哨,最后返回讀取的內(nèi)容
- stringToUTF8ByteArray和hasFile就很好理解了,一個是將string類型裝換成byte[]類型匾嘱,一個是通過文件名判斷該文件是否存在斤斧。
五、實現(xiàn)GameDataManager的讀取數(shù)據(jù)操作
實現(xiàn)了XmlManager的反序列化和讀取操作后霎烙,我們就可以在GameDataManager中實現(xiàn)將xml的數(shù)據(jù)讀取撬讽,并且把這些數(shù)據(jù)轉(zhuǎn)換成我們需要使用的類型,比如轉(zhuǎn)換成我們_myPlayer的信息悬垃。
接下來游昼,我們打開GameDataManager腳本,并新增以下代碼:
- 首先在load方法中尝蠕,先獲取文件存儲的路徑烘豌,然后判斷文件是否存在,如果不存在看彼,則在后臺打印提示信息扇谣,接下來,調(diào)用xm.loadXML方法讀取文件中的數(shù)據(jù)闲昭,讀取出來的數(shù)據(jù)是一段字符串,然后我們在調(diào)用xm.deserializeObject方法把數(shù)據(jù)轉(zhuǎn)換成MyPlayer類型的數(shù)據(jù)靡挥,最后如果數(shù)據(jù)不為空序矩,我們就把這些數(shù)據(jù)賦值給_myPlayer對象。
- pressLoadButton方法是后面我們制作讀取按鈕時會用的方法跋破,里面主要做了兩件事情簸淀,一是調(diào)用load方法瓶蝴,讀取數(shù)據(jù),二是調(diào)用printData方法打印_myPlayer的部分屬性租幕。
- printData方法中舷手,我們調(diào)用Debug.log方法打印出我們想要看的_myPlayer的屬性,而這里我們打印的屬性劲绪,是后面我們修改過具體內(nèi)容的幾個數(shù)據(jù)男窟,打印出來就是為了查看是否修改成功。
制作讀取按鈕
有了上面這些方法后贾富,為了在實際演示中歉眷,讓我們可以看到數(shù)據(jù)的讀取后的改變,我們在項目中新建一個按鈕颤枪,來觸發(fā)數(shù)據(jù)讀取的操作汗捡。
我們在項目中新建一個名為“LoadButton”的按鈕,然后將其內(nèi)容改為“讀取數(shù)據(jù)”畏纲,然后我們在按鈕的點(diǎn)擊邏輯上掛載GameDataManager中的pressLoadButton方法扇住,如下圖:
實現(xiàn)按鈕后,我們打開ZuiData文件盗胀,將玩家的ID改為99艘蹋,玩家名字改為“ZuiPlayer”,武器的名稱改為“Eagles”读整,如下圖:
修改之后簿训,我們再次打開GameDataManager腳本,把Start方法中的內(nèi)容全部注釋掉米间,如下圖:
最后我們回到Unity編輯器强品,然后運(yùn)行程序,點(diǎn)擊讀取數(shù)據(jù)按鈕屈糊,然后查看后臺打印出來的數(shù)據(jù)是否與我們修改過后的數(shù)據(jù)一致的榛,如無意外,效果如下圖:
到此逻锐,我們學(xué)會了XML數(shù)據(jù)的讀取和反序列化夫晌。
六、對文件數(shù)據(jù)進(jìn)行加密和解密
雖然我們現(xiàn)在學(xué)會了使用xml進(jìn)行數(shù)據(jù)的存儲和讀取昧诱,但是就想我們上面讀取操作時一樣晓淀,我們可以直接通過改寫ZuiData文件里面的內(nèi)容,從而改變游戲的數(shù)據(jù)盏档,這樣對于游戲數(shù)據(jù)來說是很不安全的凶掰,所以我們最好對游戲最終保存的數(shù)據(jù)進(jìn)行一些加密操作,這樣就無法通過文件直接修改游戲的數(shù)據(jù)了。
我們再次打開XmlManager腳本懦窘,然后新增以下代碼:
- 首先我們在前面引入了新的頭文件System.Security.Cryptography前翎,我們下面需要用的RijndaelManaged和ICryptTransform類,都是屬于其中畅涂。
- 接下來我們定義了我們加密和解密所需要用的密鑰港华,具體的數(shù)字可以自定義,但是必須一共是32位午衰。
- 然后我們先跳到上圖最后的getRijndaelManaged方法立宜,這里面我們主要是創(chuàng)建并定義我們加密和解密的方式,我們定義了一個RijndaelManaged對象苇经,然后設(shè)置密鑰為_keyArray(也就是我們之前定義的密鑰)赘理,然后設(shè)置對稱解密算法的運(yùn)算模式和填充模式(關(guān)于運(yùn)算模式和填充模式的詳細(xì)說明,大家可以參見最后面的參考鏈接)扇单,最后我們返回RijndaelManaged的實例化對象商模。
- 接下來我們看到encrypt方法,這里面我們主要是把傳入的數(shù)據(jù)蜘澜,進(jìn)行加密操作施流,然后返回加密后的數(shù)據(jù),首先我們定義了一個ICryptTransform類型的對象鄙信,并且他是加密模式的瞪醋,然后我們把需要加密的數(shù)據(jù)類型轉(zhuǎn)換成byte[]類型,調(diào)用TransformFinalBlock方法得到加密后的數(shù)據(jù)装诡,最后將這個數(shù)據(jù)轉(zhuǎn)換成string類型并返回银受。
- 最后我們來看decrypt方法,這里我們主要把傳入的數(shù)據(jù)鸦采,進(jìn)行解密操作宾巍,然后返回解密后的數(shù)據(jù),首先我們定義了一個ICryptTransform類型的對象渔伯,并且他是解密模式的顶霞,然后我們把需要解密的數(shù)據(jù)轉(zhuǎn)換成byte[]類型,調(diào)用TransformFinalBlock方法獲得解密后的數(shù)據(jù)锣吼,最后將這個數(shù)據(jù)轉(zhuǎn)換成string類型并返回选浑。
完成了這些主要的方法后,我們還需要對XmlManager腳本進(jìn)行一些小修改玄叠,具體見下圖:
做完這些改動后古徒,我們再把GameDataManager腳本中的Start方法中的代碼注釋取消掉,讓其恢復(fù)作用读恃,最后我們回到Unity編輯器描函,運(yùn)行程序崎苗,可以看到我們save和load操作都是正常運(yùn)行的,而這個時候我們再次打開ZuiData文件舀寓,就會發(fā)現(xiàn)文件中的內(nèi)容變成了一些亂碼,這樣就無法通過修改存檔來改變游戲的數(shù)據(jù)了肌蜻。
參考閱讀
MSDN 關(guān)于RijndaelManaged類的說明
MSDN 關(guān)于ICryptoTransform類的說明
補(bǔ)充內(nèi)容
- 需要序列化的成員必須是public的互墓,私有的是不會被序列化的,如果某個成員變量不想序列化蒋搜,有兩種方法篡撵,一是設(shè)置為私有,二是使用[XmlIgnore]修飾豆挽,如下圖:
- 如果一個成員變量的值為null育谬,序列化時不會記錄任何信息。
- 自定義序列化類帮哈,必須要有默認(rèn)的構(gòu)造函數(shù)(即不帶任何參數(shù)的構(gòu)造函數(shù))膛檀,否則會報錯。