講道理這是本宅第一次寫原創技術文章,且文中全部內容均爲本人拙劣的技術方法實現,若有各種疑問和錯誤,歡迎及時指正。
絕大部分有點規模的遊戲都需要讀取XML,且XML數量並不少,如果每次需要數據時就讀一次那就太浪費資源了,而且性能也不好,所以我自己動手簡單寫了一個管理讀取XML簡單架構。
大致思路:首先建立一個讀取XML的父類,繼承的子類即所要讀XML的實體類。使用WWW類獲取XML路徑,使用www.text得到XML內容,經過處理後把所得的東西存入一個Dictionary<Type,Object>中,以後需要用到XML中數據的時候,直接從Dictionary中調數據即可,而不需要重複的讀XML了。
思路大致如此,下面貼點代碼詳細說明一下吧。
<span style="font-size:14px;"><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PlayerConfig>
<Player name="伊沢ライオン" id="1001" HP="100" MP="50" ATK="10" DFN="10" >
<skill>Q</skill>
<skill>W</skill>
<skill>E</skill>
<skill>R</skill>
</Player>
</PlayerConfig></span>
↑↑↑↑↑這個是示例XML,我們就以讀取這個爲例。
<span style="font-size:14px;">using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Player {
public string name;
public int id;
public int hp;
public int mp;
public int atk;
public int dfn;
public List<Player> players=new List<Player>();
public List<Skill> skill=new List<Skill>();
}
public class Skill {
public string skillName;
}</span>
↑↑↑↑↑↑這個是Player類,存數據的
<span style="font-size:14px;">using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class DatabaseConfig {
//判斷是否爲空
public virtual bool Read(string text) {
if (string.IsNullOrEmpty(text)) {
return false;
}
return true;
}
}
public class DefaultConfig {
//單例模式
private static DefaultConfig instance;
public static DefaultConfig getInstance() {
if (instance == null) {
instance = new DefaultConfig();
return instance;
}
else {
return instance;
}
}
public Dictionary<System.Type, DatabaseConfig> configDic = new Dictionary<System.Type, DatabaseConfig>();
public T GetConfigByType<T>() where T : DatabaseConfig {
//查找字典是否已有T類型的數據源
if (configDic.ContainsKey(typeof(T))) {
return configDic[typeof(T)] as T;
}
return null;
}
}
</span><span style="font-size:18px;">
</span>
↑↑↑↑↑↑這段是讀取XML的父類和聲明Dictionary的部分。其中父類只有一個Read()方法判斷是否爲空,因爲在子類中還要override。
這裏特別說明一下,我使用的Dictionary是<type,Object>方式存儲的,key就是當前要存入字典中value對象的Type,使用typeof(ClassName)或者Object.getType()都可以得到。而value就是DatabaseConfig的對象。
下面還有一個判斷是否已有T類型的key,如果有,就把它對應的value(即T類型的對象)返回去,實體類通過操作這個對象就可以獲取Player的各種字段值了。
<span style="font-size:14px;">using UnityEngine;
using System.Collections;
using System.Xml;
using System.Collections.Generic;
public class PlayerConfig : DatabaseConfig{
Player player1 = new Player();
public override bool Read(string text){
if (base.Read(text) == false){
return false;
}
XmlDocument xmldoc = new XmlDocument();
xmldoc.LoadXml(text);
XmlElement rootElem = xmldoc.DocumentElement;
//讀取根節點下面子節點的元素
XmlNodeList xmllist1 = rootElem.ChildNodes;
//遍歷所有根節點下面子節點的屬性
foreach (XmlElement tempel1 in xmllist1){
Player tmpplayer1 = new Player();
tmpplayer1.name = tempel1.GetAttribute("name");
tmpplayer1.id = int.Parse(tempel1.GetAttribute("id"));
//將當前Player記錄到players鏈表下
player1.players.Add(tmpplayer1);
}
return true;
}</span>
<span style="font-size:14px;">
#region
public Player GetPlayerByID(int id) {
if ( id<= 0) {
return null;
}
foreach (var player in player1.players) {
if (player.id == id) {
return player;
}
}
return null;
}
#endregion
}</span><span style="font-size:18px;">
</span>
↑↑↑↑↑↑這個就是繼承於父類的一個實際讀取XML的子類。中間一大部分都是C#中讀取XML的代碼,我就不多介紹了,可以搜索C#讀取XML瞭解。我這裏只獲取了一個id、一個name做測試,其他字段都可以通過相同方法獲取。
最下面寫了一個功能函數,是在實際使用時調用的,就是通過判斷id來獲取player對象。
由於我個人水平極低,在我寫代碼的時候遇到如下一些低級問題,僅獻給和我一樣的初學者們,希望你們少走彎路。
1、GetPlayerByID函數找不到players
這是我第一個遇到的問題,而實際出問題的地方卻並非多高端。players是Player類中的一個List,我最開始並沒有把Player player1寫在Read()函數外,而出了方法體player1找不到了,當然也就不可能在其他位置調用了。
2、存入players的數據爲null
3、存入players的數據出錯
上面兩個問題也是困擾我半宿,都是圍繞這個List展開的。我最開始的寫法是這樣的:tmpplayer1.players.Add(tmpplayer1);Debug時看不出什麼問題,但是實際存在players裏的東西就不一樣了,因爲foreach循環體裏每次都要new tmpplayer(),代表着每次循環所使用的tmpplayers都不是同一個,可以理解爲tmpplayer1、tmpplayer2,對象不是同一個,其players的自然也不是同一個,所以List總是隻保存了最後一次存入的數據(因爲foreach結束了不再newtmpplayer了),之前寫入的東西都被後一個覆蓋掉了。
其實上面說這麼多,原因就是在Read()函數體中操作的數據是沒有被保存的,出了這個函數,如果沒有保存或return就再也找不到了,包括players。所以我在函數外聲明瞭一個player對象專門用於保存函數體內操作後的數據,而後還要把這個對象存入Dictionary中,這樣數據就不會丟了。
<span style="font-size:14px;">using UnityEngine;
using System.Collections;
public class LoadManager : MonoBehaviour {
//遊戲一開始就加載所有的xml
void Start () {
//需要添加新的配置表,只需添加addconfig方法,加上讀取的xml名即可
addConfig<PlayerConfig>();
// addConfig<XXX>();
}
void addConfig<T>() where T : DatabaseConfig, new() {
T config = new T();
StartCoroutine(loadXML(config));
// Debug.Log(config);
}
public string getPlatForm(DatabaseConfig configPath) {
string path = Application.streamingAssetsPath+ configPath.ToString() + ".xml;
return path;
}
IEnumerator loadXML(DatabaseConfig tempConfig) {
string path = getPlatForm(tempConfig);
WWW www = new WWW(path);
yield return www;
Debug.Log(www.text);
tempConfig.Read(www.text);
DefaultConfig.getInstance().configDic.Add(tempConfig.GetType(), tempConfig);
}
}</span>
這裏使用了streamingAssets文件夾作爲存放XML的路徑,大家可以搜索streamingAssets有好多介紹。最下面就是用協程加載找到XML並調用Read()方法讀取XML內容,上面已經講過了,最後把這個對象存到Dictionary裏。
所以需要加載其他XML的時候,只要寫一個繼承於DatabaseConfig的子類就可以了,然後把AddConfig<ClassName>放進Start裏,在Unity一運行的時候,就把這個XML讀好放進Dictionary裏了。需要增加或減少XML只需要管理Start裏的條目就可以了。雖然整體邏輯我自己想起來也很費腦子,但是以後再增刪XML可就省事多了。
最後附一段測試代碼
void testDemo() {
if(<pre name="code" class="csharp" style="font-size:18px;">PlayerConfig playercon= DefaultConfig.getInstance().GetConfigByType<PlayerConfig>()<span style="font-family: Arial, Helvetica, sans-serif;">){</span>
<pre name="code" class="csharp"> Player currentPlayer = playercon.GetPlayerByID(1001);
Debug.Log(currentPlayer.name);
}
如果Dictionary裏有PlayerConfig的key,那麼下面就是獲取Player的對象,查找ID爲1001角色,如果沒有錯誤,最後Debug裏輸出的值就是“伊沢ライオン”啦!