一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

【新提醒】【Unity3D游戲開發(fā)之游戲讀/存檔功能在Unity3D中的實(shí)現(xiàn)】

 Ko03 2015-11-09


我是秦元培,我的博客是http:///,期待交流。

今天想和大家分享的內(nèi)容是RPG游戲中游戲存檔的實(shí)現(xiàn),因?yàn)樽罱谧鲆粋€(gè)RPG游戲的項(xiàng)目,所以遇到這個(gè)問題就隨時(shí)記錄下來,在對(duì)知識(shí)進(jìn)行總結(jié)的同時(shí)可以將這種思路或者想法分享給大家,這是一件快樂而幸運(yùn)的事情。我討厭寫按部就班的技術(shù)教程,因?yàn)槲矣X得學(xué)習(xí)是一種自我的探索行為,如果一切都告訴你了,探索的過程便會(huì)變得沒有意義了。

游戲存檔是一種在單機(jī)游戲中特別常見的機(jī)制,這種機(jī)制是你在玩網(wǎng)絡(luò)游戲的時(shí)候無法體驗(yàn)到的,你知道每次玩完一款單機(jī)游戲都會(huì)把游戲存檔保存起來是一種怎樣的感覺嗎?它就像是一個(gè)征戰(zhàn)沙場(chǎng)的將軍將陪伴自己一生金戈鐵馬的寶劍靜靜地收入劍匣,然而每一次打開它的時(shí)候都會(huì)不由自主的熱淚盈眶。人的本性其實(shí)就是游戲,我們每一天發(fā)生的故事何嘗不是一個(gè)游戲?有時(shí)候讓我們懷念的可能并不是游戲本身,而只是擱淺在時(shí)光里的那時(shí)的我們。好了,游戲存檔是我們?cè)谟螒蚴澜缋镅┠帏欁?,它代表了我們?cè)?jīng)來到過這個(gè)世界。以RPG游戲?yàn)槔?,一個(gè)一般化的游戲存檔應(yīng)該囊括以下內(nèi)容:

角色信息:指一切表征虛擬角色成長(zhǎng)路線的信息,如生命值、魔法值、經(jīng)驗(yàn)值等等。
道具信息:指一切表征虛擬道具數(shù)量或者作用的信息,如藥品、道具、裝備等等。
場(chǎng)景信息:指一切和游戲場(chǎng)景相關(guān)的信息,如場(chǎng)景名稱、角色在當(dāng)前場(chǎng)景中的位置坐標(biāo)等等。
事件信息:指一切和游戲事件相關(guān)的信息,如主線任務(wù)、支線任務(wù)、觸發(fā)性事件等等。

從以上信息劃分的層次來看,我們可以發(fā)現(xiàn)在游戲存檔中要儲(chǔ)存的信息相對(duì)是比較復(fù)雜的,那么我們這里不得不說說unity3D中的數(shù)據(jù)持久化方案PlayerPrefs。該方案采用的是一種鍵值型的數(shù)據(jù)存儲(chǔ)方案,支持int、string、float三種基本數(shù)據(jù)類型,通過鍵名來獲取相對(duì)應(yīng)的數(shù)值,當(dāng)值不存在時(shí)將返回一個(gè)默認(rèn)值。這種數(shù)據(jù)存儲(chǔ)方案本質(zhì)上是將數(shù)據(jù)寫入到一個(gè)Xml文件。這種方案如果用來存儲(chǔ)簡(jiǎn)單的信息是沒有問題的,可是如果用它來存儲(chǔ)游戲存檔這樣負(fù)責(zé)的數(shù)據(jù)結(jié)構(gòu)就顯得力不從心了。一個(gè)更為重要的問題是在數(shù)據(jù)持久化的過程中我們希望得到是一個(gè)結(jié)構(gòu)化的【游戲存檔】實(shí)例,顯然此時(shí)松散的PlayerPrefs是不能滿足我們的要求的。因此我們想到了將游戲數(shù)據(jù)序列化的思路,常見的數(shù)據(jù)序列化思路主要有Xml和JSON兩種形式,在使用Xml的數(shù)據(jù)序列化方案的時(shí)候通常有兩種思路,即手動(dòng)建立數(shù)據(jù)實(shí)體和數(shù)據(jù)字符間的對(duì)應(yīng)關(guān)系和基于XmlSerializer的數(shù)據(jù)序列化。其中基于XmlSerializer的數(shù)據(jù)序列化是利用了[Serializable]這樣的語法特性來幫助.NET完成數(shù)據(jù)實(shí)體和數(shù)據(jù)字符間的對(duì)應(yīng)關(guān)系,兩種思路本質(zhì)上一樣的??墒俏覀冎繶ml的優(yōu)點(diǎn)是可讀性強(qiáng),缺點(diǎn)是冗余信息多,因此在權(quán)衡了兩種方案的利弊后,我決定采用JSON來作為數(shù)據(jù)序列化的方案,而且JSON在數(shù)據(jù)實(shí)體和數(shù)據(jù)字符間的對(duì)應(yīng)關(guān)系上有著天然的優(yōu)勢(shì),JSON所做的事情不就是將數(shù)據(jù)實(shí)體轉(zhuǎn)化為字符串和從一個(gè)字符串中解析出數(shù)據(jù)實(shí)體嗎?所以整個(gè)方案基本一氣呵成。好了,下面我們來看具體的代碼實(shí)現(xiàn)過程吧!

一、JSON的序列化和反序列化

這里我使用的是Newtonsoft.Json這個(gè)類庫(kù),相信大家都是知道的了!因此,序列化和反序列化特別簡(jiǎn)單。

[C#] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// <summary>
    /// 將一個(gè)對(duì)象序列化為字符串
    /// </summary>
    /// <returns>The object.</returns>
    /// <param name="pObject">對(duì)象</param>
    /// <param name="pType">對(duì)象類型</param>
    private static string SerializeObject(object pObject)
    {
        //序列化后的字符串
        string serializedString = string.Empty;
        //使用Json.Net進(jìn)行序列化
        serializedString = JsonConvert.SerializeObject(pObject);
        return serializedString;
    }
    /// <summary>
    /// 將一個(gè)字符串反序列化為對(duì)象
    /// </summary>
    /// <returns>The object.</returns>
    /// <param name="pString">字符串</param>
    /// <param name="pType">對(duì)象類型</param>
    private static object DeserializeObject(string pString,Type pType)
    {
        //反序列化后的對(duì)象
        object deserializedObject = null;
        //使用Json.Net進(jìn)行反序列化
        deserializedObject=JsonConvert.DeserializeObject(pString,pType);
        return deserializedObject;
    }


二、Rijandel加密/解密算法

因?yàn)槲覀冞@里要做的是一個(gè)游戲存檔的方案設(shè)計(jì),因?yàn)榭紤]到存檔數(shù)據(jù)的安全性,我們可以考慮采用相關(guān)的加密/解密算法來實(shí)現(xiàn)對(duì)序列化后的明文數(shù)據(jù)進(jìn)行加密,這樣可以從一定程度上保證游戲存檔數(shù)據(jù)的安全性。因?yàn)椴┲鞑]有深入地研究過加密/解密方面的內(nèi)容,所以這里僅僅提供一個(gè)從MSDN上獲取的Rijandel算法,大家感興趣的話可以自行去研究。

[C#] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
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
/// <summary>
    /// Rijndael加密算法
    /// </summary>
    /// <param name="pString">待加密的明文</param>
    /// <param name="pKey">密鑰,長(zhǎng)度可以為:64位(byte[8]),128位(byte[16]),192位(byte[24]),256位(byte[32])</param>
    /// <param name="iv">iv向量,長(zhǎng)度為128(byte[16])</param>
    /// <returns></returns>
    private static string RijndaelEncrypt(string pString, string pKey)
    {
        //密鑰
        byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey);
        //待加密明文數(shù)組
        byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(pString);
        //Rijndael解密算法
        RijndaelManaged rDel = new RijndaelManaged();
        rDel.Key = keyArray;
        rDel.Mode = CipherMode.ECB;
        rDel.Padding = PaddingMode.PKCS7;
        ICryptoTransform cTransform = rDel.CreateEncryptor();
        //返回加密后的密文
        byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
        return Convert.ToBase64String(resultArray, 0, resultArray.Length);
    }
    /// <summary>
    /// ijndael解密算法
    /// </summary>
    /// <param name="pString">待解密的密文</param>
    /// <param name="pKey">密鑰,長(zhǎng)度可以為:64位(byte[8]),128位(byte[16]),192位(byte[24]),256位(byte[32])</param>
    /// <param name="iv">iv向量,長(zhǎng)度為128(byte[16])</param>
    /// <returns></returns>
    private static String RijndaelDecrypt(string pString, string pKey)
    {
        //解密密鑰
        byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey);
        //待解密密文數(shù)組
        byte[] toEncryptArray = Convert.FromBase64String(pString);
        //Rijndael解密算法
        RijndaelManaged rDel = new RijndaelManaged();
        rDel.Key = keyArray;
        rDel.Mode = CipherMode.ECB;
        rDel.Padding = PaddingMode.PKCS7;
        ICryptoTransform cTransform = rDel.CreateDecryptor();
        //返回解密后的明文
        byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
        return UTF8Encoding.UTF8.GetString(resultArray);
    }


三、完整代碼

好了,下面給出完整代碼,我們這里提供了兩個(gè)公開的方法GetData()和SetData()以及IO相關(guān)的輔助方法,我們?cè)趯?shí)際使用的時(shí)候只需要關(guān)注這些方法就可以了!

[C#] 純文本查看 復(fù)制代碼
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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
/**
 * Unity3D數(shù)據(jù)持久化輔助類
 * 作者:秦元培
 * 時(shí)間:2015年8月14日
 **/
using UnityEngine;
using System.Collections;
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using Newtonsoft.Json;
public static class IOHelper
{
    /// <summary>
    /// 判斷文件是否存在
    /// </summary>
    public static bool IsFileExists(string fileName)
    {
        return File.Exists(fileName);
    }
    /// <summary>
    /// 判斷文件夾是否存在
    /// </summary>
    public static bool IsDirectoryExists(string fileName)
    {
        return Directory.Exists(fileName);
    }
    /// <summary>
    /// 創(chuàng)建一個(gè)文本文件   
    /// </summary>
    /// <param name="fileName">文件路徑</param>
    /// <param name="content">文件內(nèi)容</param>
    public static void CreateFile(string fileName,string content)
    {
        StreamWriter streamWriter = File.CreateText(fileName);
        streamWriter.Write(content);
        streamWriter.Close();
    }
    /// <summary>
    /// 創(chuàng)建一個(gè)文件夾
    /// </summary>
    public static void CreateDirectory(string fileName)
    {
        //文件夾存在則返回
        if(IsDirectoryExists (fileName))
            return;
        Directory.CreateDirectory(fileName);
    }
    public static void SetData(string fileName,object pObject)
    {
        //將對(duì)象序列化為字符串
        string toSave = SerializeObject(pObject);
        //對(duì)字符串進(jìn)行加密,32位加密密鑰
        toSave = RijndaelEncrypt(toSave, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        StreamWriter streamWriter = File.CreateText(fileName);
        streamWriter.Write(toSave);
        streamWriter.Close();
    }
    public static object GetData(string fileName,Type pType)
    {
        StreamReader streamReader = File.OpenText(fileName);
        string data = streamReader.ReadToEnd();
        //對(duì)數(shù)據(jù)進(jìn)行解密,32位解密密鑰
        data = RijndaelDecrypt(data, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        streamReader.Close();
        return DeserializeObject(data,pType);
    }
    /// <summary>
    /// Rijndael加密算法
    /// </summary>
    /// <param name="pString">待加密的明文</param>
    /// <param name="pKey">密鑰,長(zhǎng)度可以為:64位(byte[8]),128位(byte[16]),192位(byte[24]),256位(byte[32])</param>
    /// <param name="iv">iv向量,長(zhǎng)度為128(byte[16])</param>
    /// <returns></returns>
    private static string RijndaelEncrypt(string pString, string pKey)
    {
        //密鑰
        byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey);
        //待加密明文數(shù)組
        byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(pString);
        //Rijndael解密算法
        RijndaelManaged rDel = new RijndaelManaged();
        rDel.Key = keyArray;
        rDel.Mode = CipherMode.ECB;
        rDel.Padding = PaddingMode.PKCS7;
        ICryptoTransform cTransform = rDel.CreateEncryptor();
        //返回加密后的密文
        byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
        return Convert.ToBase64String(resultArray, 0, resultArray.Length);
    }
    /// <summary>
    /// ijndael解密算法
    /// </summary>
    /// <param name="pString">待解密的密文</param>
    /// <param name="pKey">密鑰,長(zhǎng)度可以為:64位(byte[8]),128位(byte[16]),192位(byte[24]),256位(byte[32])</param>
    /// <param name="iv">iv向量,長(zhǎng)度為128(byte[16])</param>
    /// <returns></returns>
    private static String RijndaelDecrypt(string pString, string pKey)
    {
        //解密密鑰
        byte[] keyArray = UTF8Encoding.UTF8.GetBytes(pKey);
        //待解密密文數(shù)組
        byte[] toEncryptArray = Convert.FromBase64String(pString);
        //Rijndael解密算法
        RijndaelManaged rDel = new RijndaelManaged();
        rDel.Key = keyArray;
        rDel.Mode = CipherMode.ECB;
        rDel.Padding = PaddingMode.PKCS7;
        ICryptoTransform cTransform = rDel.CreateDecryptor();
        //返回解密后的明文
        byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
        return UTF8Encoding.UTF8.GetString(resultArray);
    }
    /// <summary>
    /// 將一個(gè)對(duì)象序列化為字符串
    /// </summary>
    /// <returns>The object.</returns>
    /// <param name="pObject">對(duì)象</param>
    /// <param name="pType">對(duì)象類型</param>
    private static string SerializeObject(object pObject)
    {
        //序列化后的字符串
        string serializedString = string.Empty;
        //使用Json.Net進(jìn)行序列化
        serializedString = JsonConvert.SerializeObject(pObject);
        return serializedString;
    }
    /// <summary>
    /// 將一個(gè)字符串反序列化為對(duì)象
    /// </summary>
    /// <returns>The object.</returns>
    /// <param name="pString">字符串</param>
    /// <param name="pType">對(duì)象類型</param>
    private static object DeserializeObject(string pString,Type pType)
    {
        //反序列化后的對(duì)象
        object deserializedObject = null;
        //使用Json.Net進(jìn)行反序列化
        deserializedObject=JsonConvert.DeserializeObject(pString,pType);
        return deserializedObject;
    }
}


這里我們的密鑰是直接寫在代碼中的,這樣做其實(shí)是有風(fēng)險(xiǎn)的,因?yàn)橐坏┪覀兊捻?xiàng)目被反編譯,我們這里的密鑰就變得很不安全了。這里有兩種方法,一種是把密鑰暴露給外部方法,即在讀取數(shù)據(jù)和寫入數(shù)據(jù)的時(shí)候使用同一個(gè)密鑰即可,而密鑰可以采取由機(jī)器MAC值生成的方法,這樣每臺(tái)機(jī)器上的密鑰都是不同的可以防止數(shù)據(jù)被破解;其次可以采用DLL混淆的方法讓反編譯者無法看到代碼中的內(nèi)容,這樣就無法獲得正確的密鑰從而無法獲得存檔里的內(nèi)容了。

四、最終效果

好了,最后我們來寫一個(gè)簡(jiǎn)單的測(cè)試腳本:

[C#] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
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
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TestSave : MonoBehaviour {
    /// <summary>
    /// 定義一個(gè)測(cè)試類
    /// </summary>
    public class TestClass
    {
        public string Name = "張三";
        public float Age = 23.0f;
        public int Sex = 1;
        public List<int> Ints = new List<int> ()
        {
            1,
            2,
            3
        };
    }
    void Start ()
    {
        //定義存檔路徑
        string dirpath = Application.persistentDataPath + "/Save";
        //創(chuàng)建存檔文件夾
        IOHelper.CreateDirectory (dirpath);
        //定義存檔文件路徑
        string filename = dirpath + "/GameData.sav";
        TestClass t = new TestClass ();
        //保存數(shù)據(jù)
        IOHelper.SetData (filename,t);
        //讀取數(shù)據(jù)
        TestClass t1 = (TestClass)IOHelper.GetData(filename,typeof(TestClass));
        Debug.Log(t1.Name);
        Debug.Log(t1.Age);
        Debug.Log(t1.Ints);
    }
}


腳本執(zhí)行結(jié)果:



加密后游戲存檔:



好了,這就是今天的內(nèi)容了,希望大家能夠喜歡,有什么問題可以給我留言,謝謝!

感謝風(fēng)宇沖unity3d教程寶典之兩步實(shí)現(xiàn)超實(shí)用的XML存檔一文提供相關(guān)思路!

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    欧美激情床戏一区二区三| 我想看亚洲一级黄色录像| 国产精品久久精品毛片| 欧美三级精品在线观看| 日韩欧美三级中文字幕| 国产精品蜜桃久久一区二区| 成人国产激情在线视频| 中日韩免费一区二区三区| 国产黑人一区二区三区| 我要看日本黄色小视频| 中文字幕一区二区熟女| 日韩黄片大全免费在线看| 久久精品中文字幕人妻中文| 国产精品人妻熟女毛片av久久| 偷拍美女洗澡免费视频| 国产高清在线不卡一区| 久久精品蜜桃一区二区av| 亚洲国产欧美久久精品| 内用黄老外示儒术出处| 国产又黄又爽又粗视频在线| 国产av熟女一区二区三区四区| 国产成人在线一区二区三区| 大香蕉网国产在线观看av| av国产熟妇露脸在线观看| 欧美成人黄色一区二区三区| 一区二区三区四区亚洲另类| 日本三区不卡高清更新二区| 日本精品中文字幕人妻| 婷婷色网视频在线播放| 亚洲综合精品天堂夜夜| 国产一区二区精品高清免费| 日木乱偷人妻中文字幕在线| 日本成人中文字幕一区| 国产毛片对白精品看片| 亚洲av一区二区三区精品| 日本不卡一区视频欧美| 免费高清欧美一区二区视频| 伊人久久青草地婷婷综合| 东北老熟妇全程露脸被内射 | 中文字幕乱子论一区二区三区| 91亚洲精品国产一区|