1. 程式人生 > >SRPG遊戲開發(三十四)第八章 遊戲中的資料 - 四 資料編輯器(Data Editor)

SRPG遊戲開發(三十四)第八章 遊戲中的資料 - 四 資料編輯器(Data Editor)

返回總目錄

第八章 遊戲中的資料(Data in Game)

在之前的章節中,我們進行地圖物件的生成,移動等操作。

這一章本來可以進行戰鬥的編寫,不過資料缺失是一個問題。

所以這一章我們先來建立一些資料,以及如何編輯它們,是否需要生成配置檔案等。


文章目錄


四 資料編輯器(Data Editor)

我們將進行一個EditorWindow的編寫,並將資料顯示在上面。

要解決的主要問題是顯示的數量(上一節中提過的,如果數量過多,會造成渲染卡頓)。

除了上一節內容,我沒有進行更詳細的編輯,你可以對每個資料檔案都進行PropertyDrawer的編寫。


1 建立編輯器視窗(Create Editor Window)

我們先來建立一個最基本的視窗(EditorSrpgDataEditorWindow.cs):

namespace
DR.Book.SRPG_Dev.Models { public class EditorSrpgDataEditorWindow : EditorWindow { private static EditorSrpgDataEditorWindow s_Window; public static EditorSrpgDataEditorWindow OpenEditorSrpgDataEditorWindow() { if (s_Window != null) { s_Window.Focus(); return s_Window; } s_Window = EditorWindow.GetWindow<EditorSrpgDataEditorWindow>(false, "SRPG Data"); s_Window.minSize = new Vector2(480, 480); s_Window.Show(); return s_Window; } private EditorSrpgData m_SrpgData; private SerializedObject m_SerializedObject; public EditorSrpgData srpgData { get { return m_SrpgData; } set { if (m_SrpgData == value) { return; } m_SrpgData = value; // 刪除以前的 if (m_SerializedObject != null) { m_SerializedObject.Dispose(); m_SerializedObject = null; } // 重新建立 if (m_SrpgData != null) { m_SerializedObject = new SerializedObject(m_SrpgData); } } } private void OnDestroy() { this.srpgData = null; s_Window = null; } // TODO 其它需要新增的 } }

你會看到我沒有使用[MenuItem("Window/SRPG/SRPG Data Editor")],因為我不打算在選單中開啟它。

1.1 開啟視窗(Open Editor Window)

將開啟視窗和資源放在一起,建立檔案EditorSrpgDataEditor.cs

using UnityEngine;
using UnityEditor;

namespace DR.Book.SRPG_Dev.Models
{
    [CustomEditor(typeof(EditorSrpgData))]
    public class EditorSrpgDataEditor : Editor
    {
        #region Property
        public EditorSrpgData srpgData
        {
            get { return target as EditorSrpgData; }
        }
        #endregion

        #region Unity Callback
        public override void OnInspectorGUI()
        {
            EditorGUI.BeginDisabledGroup(true);
            base.OnInspectorGUI();
            EditorGUI.EndDisabledGroup();

            if (GUILayout.Button("Edit Datas"))
            {
                EditorSrpgDataEditorWindow window = EditorSrpgDataEditorWindow.OpenEditorSrpgDataEditorWindow();
                window.srpgData = srpgData;
            }
        }
        #endregion
    }
}

1.2 選擇資料(Select Data)

在視窗中不要全部渲染,最好可按檔案選擇我們的資料。

EditorSrpgData中新增屬性:

        public enum ConfigType
        {
            MoveConsumption,
            Class,
            Character,
            Item,
            Text
        }

        [SerializeField]
        public ConfigType currentConfig = ConfigType.MoveConsumption;

最終效果:

EditorSrpgData Inspector

  • 圖 8.9 EditorSrpgData Inspector

2 功能介面(IEditorConfigSerializer.cs)

我們的編輯器至少具有的功能:

  • 編輯資料;

  • 儲存配置檔案;

  • 讀取配置檔案。

除了這些功能外,最好還能排序(根據key),還要能檢測是否有重複的key

建立檔案IEditorConfigSerializer.cs

using System;

namespace DR.Book.SRPG_Dev.Models
{
    public interface IEditorConfigSerializer
    {
        Array EditorGetKeys();
        void EditorSortDatas();
        byte[] EditorSerializeToBytes();
        void EditorDeserializeToObject(byte[] bytes);
    }
}

有了介面,我們在EditorSrpgData.cs中加入方法:

        public IEditorConfigSerializer GetCurConfig()
        {
            switch (currentConfig)
            {
                case ConfigType.MoveConsumption:
                    return moveConsumptionConfig;
                case ConfigType.Class:
                    return classConfig;
                case ConfigType.Character:
                    return characterInfoConfig;
                case ConfigType.Item:
                    return itemInfoConfig;
                case ConfigType.Text:
                    return textInfoConfig;
                default:
                    return null;
            }
        }

同時我們讓所有配置檔案都繼承這個介面:

    [Serializable]
    public abstract class BaseXmlConfig<TKey, TData> : XmlConfigFile, IEditorConfigSerializer
        where TData : class, IConfigData<TKey>

    [Serializable]
    public class BaseTxtConfig<TKey, TData> : TxtConfigFile, IEditorConfigSerializer
        where TData : class, ITxtConfigData<TKey>, new()

獲取key與排序都是對datas進行,所以應該是一樣的:

        /// <summary>
        /// 獲取所有Key
        /// </summary>
        /// <returns></returns>
        Array IEditorConfigSerializer.EditorGetKeys()
        {
            if (datas == null)
            {
                return default(Array);
            }
            else
            {
                return datas.Select(data => data.GetKey()).ToArray();
            }
        }

        void IEditorConfigSerializer.EditorSortDatas()
        {
            if (datas != null)
            {
                Array.Sort(datas, (data1, data2) =>
                {
                    return data1.GetKey().GetHashCode().CompareTo(data2.GetKey().GetHashCode());
                });
            }
        }

2.1 儲存與讀取Xml檔案(Save or Load Xml File)

這個和我們的序列化是差不多,曾經我們已經寫過了,這裡不再闡述。

序列化:

        public virtual byte[] EditorSerializeToBytes()
        {
            byte[] bytes;
            using (MemoryStream ms = new MemoryStream())
            {
                using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
                {
                    XmlSerializer xs = new XmlSerializer(GetType());
                    XmlSerializerNamespaces xsn = new XmlSerializerNamespaces();
                    xsn.Add("", "");
                    xs.Serialize(sw, this, xsn);
                    bytes = ms.ToArray();
                }
            }
            return bytes;
        }

反序列化:

        public virtual void EditorDeserializeToObject(byte[] bytes)
        {
            XmlConfigFile config;
            using (MemoryStream ms = new MemoryStream(bytes))
            {
                using (StreamReader sr = new StreamReader(ms, Encoding.UTF8))
                {
                    XmlSerializer xs = new XmlSerializer(GetType());
                    config = xs.Deserialize(sr) as XmlConfigFile;
                }
            }
            datas = (config as BaseXmlConfig<TKey, TData>).datas;
        }

2.2 儲存與讀取Txt檔案(Save or Load Txt File)

我們之前寫過txt檔案的反序列化,但那是對字典的;這裡我們要對保留的datas進行填充。

也和之前說的一樣, txt檔案可沒有現成的序列化方法,每個檔案都是不同的。 這樣每個檔案都重寫一次方法顯然是很麻煩, 所以我們採用反射方法,這樣除了特殊的txt外,都可以用這個通用的方法。

使用反射儲存檔案:

        public virtual byte[] EditorSerializeToBytes()
        {
            if (datas == null)
            {
                datas = new TData[0];
            }

            StringBuilder builder = new StringBuilder();

            // 反射獲取所有public欄位
            Type dataType = typeof(TData);
            FieldInfo[] fields = dataType.GetFields(BindingFlags.Instance 
                | BindingFlags.Public 
                | BindingFlags.GetField 
                | BindingFlags.SetField);

            if (fields.Length != 0)
            {
                // 每一列的名字
                string[] line = fields.Select(field => field.Name).ToArray();
                builder.AppendLine(k_CommentingPrefix + string.Join("\t", line));

                // 每一行資料
                for (int i = 0; i < datas.Length; i++)
                {
                    line = fields.Select(field => field.GetValue(datas[i]).ToString()).ToArray();
                    builder.AppendLine(string.Join("\t", line));
                }
            }
            return Encoding.UTF8.GetBytes(builder.ToString().Trim());
        }

讀取檔案:

        public virtual void EditorDeserializeToObject(byte[] bytes)
        {
            string buffer = Encoding.UTF8.GetString(bytes).Trim();
            // 分割行
            string[] lines = buffer.Split(
                new string[] { Environment.NewLine }, 
                StringSplitOptions.RemoveEmptyEntries);

            List<TData> loadedDatas = new List<TData>();

            for (int i = 0; i < lines.Length; i++)
            {
                string line = lines[i].Trim();
                // 如果是註釋,直接下一條
                if (line.StartsWith(k_CommentingPrefix))
                {
                    continue;
                }

                TData data = new TData();
                if (data.FormatText(line))
                {
                    loadedDatas.Add(data);
                }
            }

            datas = loadedDatas.ToArray();
        }

3 繪製主函式(OnGUI)

回來EditorSrpgDataEditorWindow中,我們來填充我們的OnGUI方法:

        private Vector2 m_Scroll;

        private GUILayoutOption m_BtnWidth = GUILayout.MaxWidth(120);

        private void OnGUI()
        {
            // TODO
        }

首先,我們的物件不能為空,且能夠選擇繪製的型別:

            EditorGUI.BeginDisabledGroup(true);
            srpgData = (EditorSrpgData)EditorGUILayout.ObjectField("SRPG Data Editor", srpgData, typeof(EditorSrpgData), false);
            EditorGUI.EndDisabledGroup();
            if (srpgData == null || m_SerializedObject == null)
            {
                EditorGUILayout.HelpBox("Please re-open a SRPG Data Editor Window.", MessageType.Info);
                return;
            }

            m_SerializedObject.Update();

            // 繪製選擇型別
            SerializedProperty curConfigTypeProperty = m_SerializedObject.FindProperty("currentConfig");
            EditorGUILayout.PropertyField(curConfigTypeProperty, true);
            EditorGUILayout.Space();

            // TODO

其次,是我們的功能按鈕(方法之後再填充):

            // 繪製按鈕
            if (!DoDrawButtons())
            {
                return;
            }

最後,繪製我們當前的資料(方法之後再填充):

            // 繪製資料
            if (!DoDrawDatas())
            {
                return;
            }

4 繪製按鈕(Draw Buttons)

我們按鈕的功能主要分為:

  • 儲存資料成配置檔案:SaveToFile

  • 讀取配置檔案:LoadFromFile

  • 檢測重複的keyCheckDumplicateKeys

  • 根據key排序資料:SortWithKeys

即,建立DoDrawButtons()

        private GUILayoutOption m_BtnWidth = GUILayout.MaxWidth(120);

        /// <summary>
        /// 繪製按鈕
        /// </summary>
        private bool DoDrawButtons()
        {
            IEditorConfigSerializer config = srpgData.GetCurConfig();
            if (config == null)
            {
                EditorGUILayout.HelpBox(
                    string.Format("{0} Config is not found.", srpgData.currentConfig.ToString()), 
                    MessageType.Error);
                return false;
            }

            EditorGUILayout.BeginHorizontal();
            {
                if (GUILayout.Button("Save To File", m_BtnWidth))
                {
                    SaveToFile(config);
                }

                if (GUILayout.Button("Load From File", m_BtnWidth))
                {
                    LoadFromFile(config);
                }

                if (GUILayout.Button("Check Keys", m_BtnWidth))
                {
                    CheckDuplicateKeys(config);
                }

                if (GUILayout.Button("Sort Datas", m_BtnWidth))
                {
                    SortWithKeys(config);
                }
            }
            EditorGUILayout.EndHorizontal();

            return true;
        }

我們讓它們水平方向排列:

EditorSrpgDataWindow Buttons

  • 圖 8.10 EditorSrpgDataWindow Buttons

4.1 儲存檔案(Save To File)

將配置檔案轉換成byte[]的方法我們已經寫過了,只需要獲取相應的路徑然後儲存就可以了。 在UnityEditor中,獲取儲存檔案路徑的方法是 EditorUtility.SaveFilePanel 。而在儲存之前,我們需要檢測一下檔案是否合法(是否有重複的key)。

        private void SaveToFile(IEditorConfigSerializer config)
        {
            string ext = (config is XmlConfigFile) ? "xml" : "txt";
            string path = EditorUtility.SaveFilePanel(
                "Save", Application.streamingAssetsPath, config.GetType().Name, ext);

            if (!string.IsNullOrEmpty(path))
            {
                if (!CheckDuplicateKeys(config))
                {
                    Debug.LogError("Config to save has some `Duplicate Keys`. Save Failure.");
                    return;
                }

                try
                {
                    byte[] bytes = config.EditorSerializeToBytes();
                    File.WriteAllBytes(path, bytes);
                    AssetDatabase.Refresh();
                }
                catch (Exception e)
                {
                    Debug.LogError("Save ERROR: " + e.ToString())
            
           

相關推薦

SRPG遊戲開發 遊戲資料 - 資料編輯Data Editor

返回總目錄 第八章 遊戲中的資料(Data in Game) 在之前的章節中,我們進行地圖物件的生成,移動等操作。 這一章本來可以進行戰鬥的編寫,不過資料缺失是一個問題。 所以這一章我們先來建立一些資料,以及如何編輯它們,是否需要生成配置檔案等。 文章

SRPG遊戲開發 遊戲資料 - 五 測試,匯出,匯入與編輯說明Test, Export, Import and Description

返回總目錄 第八章 遊戲中的資料(Data in Game) 在之前的章節中,我們進行地圖物件的生成,移動等操作。 這一章本來可以進行戰鬥的編寫,不過資料缺失是一個問題。 所以這一章我們先來建立一些資料,以及如何編輯它們,是否需要生成配置檔案等。 文章

SRPG遊戲開發十三 遊戲資料 - 編輯做準備Editors Preparation

返回總目錄 第八章 遊戲中的資料(Data in Game) 在之前的章節中,我們進行地圖物件的生成,移動等操作。 這一章本來可以進行戰鬥的編寫,不過資料缺失是一個問題。 所以這一章我們先來建立一些資料,以及如何編輯它們,是否需要生成配置檔案等。 文章

圍圈報數遊戲C程式設計5題

題目描述:有n個人圍成一圈,順序排號,從第一個人開始報數,從1到3報數,凡報到3的人退出圈子,問最後留下來的幸運者是原來的幾號。 #include<stdio.h> #include<stdlib.h> #include<assert.h>

SLAM從入門到放棄:SLAM習題1-3

以下均為簡單筆記,如有錯誤,請多多指教。 除了LK光流之外,還有哪些光流方法?它們各有什麼特點? 答:此答案轉載於:https://blog.csdn.net/iloveayu/article/det

SLAM從入門到放棄:SLAM習題4

以下均為簡單筆記,如有錯誤,請多多指教。 使用Ceres實現RGB-D上稀疏直接法和半稠密直接法。 答:由於稀疏直接法和半稠密直接法並沒有本質區別,所以此處只提供了稀疏直接法的計算結果。我的實驗結果發現,在同樣的資料集上g2o和Ceres的結果似乎不太一樣,目

雲吶運維手冊:服務商版註冊指南

1.管理員WEB官網註冊 如您的客戶端為PC,可使用WEB官網註冊方式。 通過WEB瀏覽器訪問www.easyitom.com,在產品官網的右上角點選“註冊”按鈕。     輸入註冊所需資訊,填入手機號碼,接收並輸入驗證碼,完成註冊。 註冊時需選擇貴企業的“企業型別”

一站式學習Redis 從入門到高可用分散式實踐慕課 Redis Sentinel

主從複製高可用?           主從複製,主掛掉後需要手工來操作麻煩           寫能力和儲存能力受限 (主從複製只是備份,單節點儲存能力)  #其實分散式後

《C++程式設計思想》第二版3 C++的C筆記、習題及答案(一)

一.總結本章的內容: 1.如果宣告指標是void* ,它意味著任何型別的地址都可以間接引用那個指標(而如果宣告int*,則只能對int型變數的地址間接引用那個指標)。一旦我們間接引用一個void*,就

Python語言程式設計MOOC崇天程式設計方法學學習筆記體育競技分析+第三方庫安裝腳步+os庫

複習: 數字型別及操作: 字串型別及操作: 程式的分支結構: 程式的迴圈結構: 函式的定義與使用: 程式碼複用與函式遞迴 集合型別及操作: 序列型別及操作: 字典型別及操作: 檔案的使用: 一維資料的格式化和處理:

資料結構 高階搜尋樹

多路查詢樹(muitl-way search tree),其每一個節點的孩子數可以多於兩個,且每一個節點處可以儲存多個元素。主要有4中特殊形式。 一、2-3樹 定義:其中的每一個節點都具有兩個孩子(稱為2節點)或者三個孩子(稱為3節點)。  並且2-3樹中所有的葉子都在同一層上。

Java開發筆記字串的賦值及型別轉換

不管是基本的char字元型,還是包裝字元型別Character,它們的每個變數只能存放一個字元,無法滿足對一串字元的加工。為了能夠直接操作一連串的字元,Java設計了專門的字串型別String,該型別允許儲存一整串字元,並對字串進行各種處理。字串型別不屬於基本型別,它的用法與包裝型別更為接近。例如給字串變數賦

【Visual C++】遊戲開發筆記之一 基礎動畫顯示 排序貼圖

                ------------------------------------------------------------------------------------------------------------------------------淺墨歷時一年為遊戲程式設計

【Visual C++】遊戲開發筆記之 基礎動畫顯示 透明動畫的實現

                作者:毛星雲    郵箱: [email protected]    歡迎郵件交流程式設計心得"透明動畫”是遊戲中一定會用到的基本技巧,它通過圖案的連續顯示及圖案本身背景的透明化處理,在背景圖上產生出栩栩如生的動畫效果。看過之前筆記的朋友們應該知道,在筆記六裡我們介紹

在所有的添加方法上記錄一下(添加操作)

end 若是 java args pack hand exceptio row ace 使用代理 /store/src/com/louis/utils/BeanFactory.java package com.louis.utils; import j

Spring Boot教程使用Redis數據庫2

分享圖片 target object docs int cpp eas 序列 lean 除了String類型,實戰中我們還經常會在Redis中存儲對象,這時候我們就會想是否可以使用類似RedisTemplate<String, User>來初始化並進行操作。但是

Linux學習總結lamp之用戶認證和域名跳轉

lamp 用戶認證 域名跳轉 訪問日誌 用戶認證 為了某些網站的安全需要,特意授權特定用戶訪問,因此產生了網站訪問的用戶認證機制。我們可以針對某個站點進行認證,也可以指定某個目錄,或者是一個文件。編輯虛擬主機配置文件:vim /usr/local/apache2.4/conf/extra/ht

C之動態內存分配

C語言 malloc free calloc realloc 在一般的程序中,我們難免會遇到動態的申請內存,那麽動態內存分配的意義到底是什麽呢?在 C 語言中的一切操作都是基於內存的,變量和數組都是內存的別名。內存分配由編譯器在編譯期間決定,定義數組的時候必須指定數組長度,

前置操作符和後置操作符

C++ 前置 操作符 重載 我們之前在 C 語言中學習了前置 ++ 和後置 ++。應該知道 i++ 是將 i 的值作為返回值,i + 1;++i 是先 i+1,再返回 i 的值。那麽它們真的有區別嗎?我們來編程看看,在 VS 中進行反匯編,看看匯編代碼是怎樣處理的#includ

數據結構拓撲排序

活動 分享 wid http mage 刪除 例如 結構 過程   一、拓撲排序的定義   1.AOV網:在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關系,這樣的有向圖為頂點表示活動的網,稱為AOV網(Activity On Vertex Network