快速實現資料編輯器——不要再傻傻地用程式碼一行行繪製介面了
我最開始做編輯器的時候,確實也是用EditorGUILayout一行一行寫的。
Unity的EditorGUI這套東西,在實現介面上確實上已經比傳統的“拖控制元件+設屬性+加監聽”要快多了,確實容易就此滿足。尤其是以前回合制遊戲的編輯器,其實也就是個單層陣列,工作量並不大。
而從客戶端過來的人,因為以前引擎稀爛,本來就要設專人用大量精力做編輯器,他們認為在這種地方浪費時間是理所應當的。反正工期也長,既然有專門做編輯器的人,讓他們閒著也不好。
直到——之前專案的策劃非要我在專案原型階段拿一個編輯器出來,而且還是照抄別的遊戲的整套功能的那種,大概是個包含各種定製功能的彈幕遊戲,內建Action,Trigger和其他奇怪的玩意兒。如果用普通的方式,幾十個類,沒有一萬行程式碼怕是搞不定。
而我最多也就一週時間。
雖然明明可以用編輯xml檔案等序列化資料檔案的方式來代替,但他們硬要有編輯器才開始工作,似乎覺得編輯器是彈個響指一夜之間變出來的玩意兒。不考慮工具價效比也是中國策劃的通病了。
我當時也沒有別的辦法。由於之前的資料格式是XML(直接從競品偷的),我便根據檔案內容整理出了一個表述資料結構的XML,然後寫編輯器程式碼讀取這個XML並生成整個樹狀介面,這樣就不用一個一個類去實現了,再加上一些拖拽Asset,預覽等需求,大概用了三天便完成了這個功能。
由於後期的編輯器修改需求無非就是增減屬性,增刪Class。直接改下那個作為配置的XML檔案就可以了。所以編輯器方面就無需再花費精力,後來那個XML檔案都直接交給策劃自己改了。畢竟等我改需要時間,他們即改即用。
而且很顯然,這東西是通用的,放任何專案裡只要花個個把小時改下XML檔案就能把功能實現出來,除了略醜之外,和其他專案用專人跟進整個專案搞出來的東向並沒有啥區別。
但是這個東西畢竟也算專案程式碼,不太合適直接放出來,所以這裡我說的其實是和它無關的後續事項。
——雖然之前的方案用起來是挺方便,但它是“完全”的嗎?
其實並不是。
因為這個配置檔案和實際用的資料類是分開編寫的。每增加一個屬性,雖然編輯器那邊不需要我插手,但我還是需要修改實際的資料類,並修改對應部分的Parse程式碼才可以完成這個更新流程。編輯器那邊可以偷懶用字串,程式裡卻還是隻能用列舉。
也就是說,編輯器端的配置檔案和我這邊的資料類以及Parse程式碼依然是完全重複的勞動。
在我的既有“世界觀”裡,對資料檔案寫Parse程式碼轉換成資料類是一件理所應當的工作,花的時間也不長,便止步於此。但現在看來,我這樣的想法,又和認為“反正編輯器需要有一個人專門跟進,便連顯而易見的效率改進都不做”的人有什麼區別呢?
[AppleScript] 純文字檢視 複製程式碼
|
[CustomEditor ( typeof ( Type ) ) ] |
這是所有寫過編輯器的人非常熟悉的一行程式碼,因為它是編輯器的入口。
但是:
[AppleScript] 純文字檢視 複製程式碼
|
[CustomPropertyDrawer ( typeof ( Type ) ) ] |
恐怕就沒幾個人知道了。
它和CustomEditor功能類似,都是自定義特定型別的編輯器介面,但它的物件不是MonoBehaviour,而是一個欄位上的資料。
[AppleScript] 純文字檢視 複製程式碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
[CustomPropertyDrawer ( typeof ( UserStruct ) ) ] public
class
UserStrutDraw :
PropertyDrawer { public
override float GetPropertyHeight ( SerializedProperty
property ,
GUIContent label ) { return
0 f; } public
override void OnGUI ( Rect
position ,
SerializedProperty property ,
GUIContent label ) { EditorGUI.BeginProperty ( position ,
label ,
property ) ; EditorGUILayout.BeginHorizontal ( ) ; EditorGUILayout.PropertyField ( property .FindPropertyRelative ( "name" ) , new
GUIContent ( "姓名:" ) ) ; EditorGUILayout.PropertyField ( property .FindPropertyRelative ( "sex" ) , new
GUIContent ( "性別:" ) ) ; EditorGUILayout.EndHorizontal ( ) ; EditorGUI.EndProperty ( ) ; } } |
建立這樣一個類後,用到UserStruct這個資料的編輯器介面都會發生變化(或者是公開屬性直接在屬性皮膚顯示,又或者是用EditorGUILayout.PropertyField呈現)
但這樣並不方便,因為同一段編輯器程式碼會用在多個型別上,所以通常的做法是:[CustomPropertyDrawer(typeof(Type))]中的Type不指定具體型別,而是指定一個PropertyAttribute元標籤物件。
[AppleScript] 純文字檢視 複製程式碼
1
2
|
public
class
UserDisplayAttribute :
PropertyAttribute { } |
然後在需要應用應用這個編輯器的地方打上UserDisplayAttribute這個元標籤。
[AppleScript] 純文字檢視 複製程式碼
1
2
3
4
5
|
[System.Serializable] public
class
Profile { [UserDisplayAttribute] public
UserStruct user; } |
便能夠有和之前相同的效果。
此外,編輯器類的基類PropertyDrawer是用來定義某個屬性的,它具有獨佔性。但你也可以繼承自DecoratorDrawer,它是“裝飾”的意思,是可以疊加的,可以用它來做一些介面繪製工作。
[AppleScript] 純文字檢視 複製程式碼
1
2
3
4
5
6
|
[System.Serializable] public
class
Profile { [DrawLine] [UserDisplayAttribute] public
UserStruct user; } |
另外,Attribute物件也是可以有內部屬性的
[AppleScript] 純文字檢視 複製程式碼
1
2
3
|
public
class
UserDisplayAttribute :
PropertyAttribute { public
Color color; } |
直接寫在括號內就可以為這些屬性賦值,然後就可以在相應的PropertyDrawer類裡讀取到這個值,並處理。
[AppleScript] 純文字檢視 複製程式碼
1
2
3
4
5
6
|
[System.Serializable] public
class
Profile { [DrawLine] [UserDisplayAttribute ( color
=
Color.red ) ] public
UserStruct user; } |
這就為我們開通了另一條,不通過CustomEditor做介面的方法。而這種方法程式碼量更少,也更容易重用。我們可以在寫資料類的時候順便加上這些元標籤,然後用EditorGUILayout.PropertyField呈現整個資料類的根結點,然後用Unity自己的物件層級功能一層層展開,不需要為每條屬性書寫編輯器程式碼。對Unity自帶呈現不滿的地方,用PropertyDrawer類重新定義就可以。
陣列也是可以重定義的。
而且用這種方法,以前一些比較麻煩的元件功能也變得容易實現了,諸如Tab
[AppleScript] 純文字檢視 複製程式碼
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
|
[CustomPropertyDrawer ( typeof ( TabAttribute ) ) ] public
class
TabDraw :
PropertyDrawer { public
override float GetPropertyHeight ( SerializedProperty
property ,
GUIContent label ) { return
0 f; } public
override void OnGUI ( Rect
position ,
SerializedProperty property ,
GUIContent label ) { GUIStyle
buttonActive =
new
GUIStyle ( GUI.skin. button )
{
normal =
GUI.skin. button .active
} ; string []
tabNames =
( attribute
as
TabAttribute ) .tabNames; EditorGUILayout.BeginHorizontal ( ) ; int
count =
tabNames.Length; for
( int
i =
0 ;
i <
count; i + + ) { if
( GUILayout.Button ( tabNames ,
i = =
property .intValue
? buttonActive :
GUI.skin. button ) ) { property .intValue
=
i; } } EditorGUILayout.EndHorizontal ( ) ; } } public
class
TabAttribute :
PropertyAttribute { public
string []
tabNames; } / / 使用示例 [Tab ( tabNames
=
new
string []
{
"tab1" , "tab2" } ) ] public
int tabIndex; |
還有比較重要的屬性中文化
[AppleScript] 純文字檢視 複製程式碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
[CustomPropertyDrawer ( typeof ( LabelAttribute ) , false ) ] public
class
LabelDrawer :
PropertyDrawer { public
override void OnGUI ( Rect
position ,
SerializedProperty property ,
GUIContent label ) { label . text
=
( attribute
as
LabelAttribute ) . label ; EditorGUI.PropertyField ( position ,
property ,
label ) ; } } public
class
LabelAttribute :
PropertyAttribute { public
string
label ; public
LabelAttribute ( string
label ) { this. label
=
label ; } } [ / p][p = 30 ,
2 ,
left][Label ( "中文屬性名" ) ] public
int testInt; |
所以我們只需要寫好資料類,然後適當加幾個樣式元標籤,根據遊戲內容自己實現一些特殊的元標籤以便和遊戲預覽部分通訊,以及針對佈局需求用DecoratorDrawer繪製介面。然後外面再包一個EditorWindows,將遊戲的資料用ScriptableObject整體序列化以及儲存。
這樣我們在遊戲開發過程中,編輯器就可以自動完成了,資料部分也是高效的二進位制序列化格式,讀取即使用,也不需要重寫一遍Parse。
要說缺點的話,也就是限死了必須用Unity的序列化格式。當然,如果你願意的話,也可以寫個反射指令碼把它轉換成JSON,XML等其他格式,但在“技能編輯器”這類應用環境內,由於只有客戶端在使用,並不需要“通用性”(雖說這個格式C#也能內建讀取就是了)
至於你說,策劃和程式用的是不同的Unity工程,所以不能用一樣的資料格式……
首先策劃和美術起碼得用一樣的工程,否則同步資源太浪費時間了,不同步資源?是讓策劃瞎著眼睛配置資料嗎?程式部分如果不想暴露程式碼,可以編譯成DLL放到他們的工程目錄內,這樣用上去和使用同一工程是一樣的。
你非要兩邊程式碼不共用,就意味著編輯器那邊不僅要實現資料編輯,還要把部分遊戲邏輯修改複製一份到另一邊,很容易不一致,並導致委曲求全,編輯器使用非常困難。
關鍵是耗費巨大,又沒有實際的好處。
只要編輯器和執行時使用同一套CS程式碼,就可以通過這套東西節約大量開發時間,以及需求變動時修改導致的等待時間。
然而,雖然有這套東西,但是Unity自己的原始屬性皮膚確實比較難用,雖說都可以實現,但像Tab,陣列之類的功能,一個個實現也很費時間
進入網站往下拉可以看到全部功能介紹的動圖。
除了大量定義好的元標籤之外,還提供了一個任意型別序列化的功能,便於容納字典等其他複雜型別。
從原始碼看,它還重寫了Unity的那套Attribute的底層,不再限制元標籤必須在欄位上,可以放到方法上實現諸如Button之類的功能。
[AppleScript] 純文字檢視 複製程式碼
1
2
3
4
|
[Button ( "label" ) ] public
void TestMethod ( ) { Debug.Log ( "test" ) ; } |
在它的基礎上開始擴充套件,應該是更好的做法。
相關文章
- 怎麼自己製作地圖?如何快速實現簡單地圖繪製?地圖
- 不要再爭論程式碼風格了!
- 實時程式碼編輯器
- 【漫畫】不要再問我快速排序了排序
- 地圖編輯器幾種實現的總結地圖
- Windows系統下22款實用程式碼編輯器Windows
- 百度地圖繪製多邊型帶編輯功能薦地圖
- Asp.Net Core 使用Monaco Editor 實現程式碼編輯器ASP.NET
- 巧用 Swagger 線上編輯器生成前端介面程式碼Swagger前端
- Unity3D實現地圖編輯器的外掛Unity3D地圖
- Xamarin iOS教程之編輯介面編寫程式碼iOS
- 不到200行 JavaScript 程式碼如何實現富文字編輯器JavaScript
- java實現編輯器(一)Java
- 編碼規範:不要用引數控制程式碼邏輯
- 基於GPT3的程式碼編輯器Cursor試用-你的智慧程式碼編輯助手GPT
- OKR與影響地圖,別再傻傻分不清OKR地圖
- 遊戲地圖編輯器 (轉)遊戲地圖
- 如何製作室內地圖,哪裡可以快速繪製室內地圖地圖
- [提問交流]ot編輯器寫好文章,再點選編輯出現,插入程式碼段的地方會出現無標題文件
- 線上程式碼編輯器選型
- 程式碼編輯器:sublime text for MacMac
- 用Rust編寫的快如閃電的程式碼編輯器:lapceRust
- 不要再選擇MySQL了MySql
- Quill編輯器實現原理初探UI
- 快速實現地圖遷移資料視覺化地圖視覺化
- canvas繪製機器貓程式碼例項Canvas
- 利用Python快速繪製海報級別地圖Python地圖
- Android模擬器繪製實現概述Android
- 多功能程式碼編輯器 Nova中文最新啟用版
- 程式碼線上編譯器(上)- 編輯及編譯編譯
- 編寫觸發器實現兩張表資料同步,sql程式碼如下:觸發器SQL
- 室內三維地圖編輯器,什麼軟體可以編輯地圖地圖
- 一個室內地圖編輯器,製作停車場地圖的平臺地圖
- Sublime Text 4 for Mac(程式碼編輯器)Mac
- Sublime Text程式碼編輯器Mac/WindowsMacWindows
- 10個線上HTML程式碼編輯器HTML
- 實用的Plist編輯器:Penguin for MacNGUIMac
- 利用 javascript 實現富文字編輯器JavaScript