SRPG遊戲開發(三十四)第八章 遊戲中的資料 - 四 資料編輯器(Data Editor)
第八章 遊戲中的資料(Data in Game)
在之前的章節中,我們進行地圖物件的生成,移動等操作。
這一章本來可以進行戰鬥的編寫,不過資料缺失是一個問題。
所以這一章我們先來建立一些資料,以及如何編輯它們,是否需要生成配置檔案等。
文章目錄
- 第八章 遊戲中的資料(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;
最終效果:
- 圖 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
; -
檢測重複的
key
:CheckDumplicateKeys
; -
根據
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;
}
我們讓它們水平方向排列:
- 圖 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