今天筆者給大家?guī)硪粋€非常實(shí)用的無損(無感)替換父組件解決方案欢峰。可以避免繼承的子類組件替換原有組件時可能導(dǎo)致的數(shù)據(jù)丟失,或者繁瑣的恢復(fù)通用設(shè)置的操作。
寫在前面:
前段時間想要做本地化卜范,采用了繼承了 Text 組件,相較于新增一個專門用于標(biāo)識的腳本鹿榜,優(yōu)點(diǎn)是游戲?qū)ο笊峡梢陨賿煲粋€腳本海雪,但是問題來了,怎么無損的替換掉原有組件而不丟失數(shù)據(jù)呢舱殿?
解決方案:
在這里筆者收羅了2個解決方案:
-
Unity UGUI 本地化方案 - Localiztion Tool - Zero - CSDN博客
此工具的基本原理就是將Text控件的FileID和GUID換成LocaliztionText控件的FileID和GUID奥裸。 (基本就是一個文本級別的操作) ←作者原話 -
ButtonEx
額,別看命名是 Button 啥的怀薛,其實(shí)它就是我要重點(diǎn)講的第二個解決方案刺彩,內(nèi)容可引起極度舒適。枝恋。
實(shí)現(xiàn)原理:
ButtonEx 的作者對 Unity 編輯器的理解相當(dāng)?shù)纳羁檀淳蟆?shí)現(xiàn)原理雖然和第一個解決方案一模一樣。但他用到了Unity 自帶的序列化解決方案焚碌。因?yàn)椴灰蕾囉谖谋酒枞粒阅軌蛑С侄M(jìn)制格式的預(yù)制體,也就無需強(qiáng)制轉(zhuǎn)Scene十电、Prefab 為文本文件了(當(dāng)然知押,使用第三方版本管理就不說了)。
那上面提到的Unity Native解決方案究竟是個什么鬼鹃骂,又為什么說跟第一個解決方案原理一樣呢台盯?
答案就是編輯器模式下,每個資源都有一個編輯器層面的對象與之對應(yīng)畏线。也就是說每一個腳本都有一個 MonoScript 對象與之對應(yīng)著静盅,記錄著這個腳本的 FileId 和 GUID .而這個對象信息也會序列化持久保存在組件的 “m_Script” 字段中,也就是第一個解決方案作者提到的規(guī)律:
只不過 ButtonEx 的作者從上游出發(fā)寝殴,使用 ButtonEx 腳本資源在編輯器下的代言人:MonoScript 對象替換了原來的 Button組件的 MonoScript 對象從而實(shí)現(xiàn)了無損+無感的組件替換體驗(yàn)蒿叠。
現(xiàn)在,該明白為什么說他們原理一樣了吧:> 都是對 m_Script 字段進(jìn)行了更新蚣常。<
核心代碼:
/// <summary>
/// Convert to the specified component.
/// </summary>
protected static void ConvertTo<T>(Object context) where T : MonoBehaviour
{
var target = context as MonoBehaviour;
var so = new SerializedObject(target);
so.Update();
bool oldEnable = target.enabled;
target.enabled = false;
// Find MonoScript of the specified component.
foreach (var script in Resources.FindObjectsOfTypeAll<MonoScript>())
{
if (script.GetClass() != typeof(T))
continue;
// Set 'm_Script' to convert.
so.FindProperty("m_Script").objectReferenceValue = script;
so.ApplyModifiedProperties();
break;
}
(so.targetObject as MonoBehaviour).enabled = oldEnable;
}
是不是超級簡單呀市咽!
真的是一波操作猛如虎,回頭一看Native 抵蚊,真香施绎!
動畫演示:
Tips:
- 開篇嘮的是 本地化啊 ,Text 啊贞绳,但是通篇 ButtonEx 谷醉!好吧,給個 TextEx 的無損/無感切換的動畫演示算是首位呼應(yīng)咯熔酷。
- 動畫中孤紧,筆者演示了瘋狂修改各種屬性,然后來回切換組件的無感/無損體驗(yàn)拒秘。
演示代碼:
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(TextEx))]
public class TextExEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
}
}
#endif
public class TextEx : Text
{
public string m_key;
#if UNITY_EDITOR
[MenuItem("CONTEXT/Text/Convert To TextEx", true)]
static bool _ConvertToButtonEx(MenuCommand command)
{
return CanConvertTo<TextEx>(command.context);
}
[MenuItem("CONTEXT/Text/Convert To TextEx", false)]
static void ConvertToButtonEx(MenuCommand command)
{
ConvertTo<TextEx>(command.context);
}
[MenuItem("CONTEXT/Text/Convert To Text", true)]
static bool _ConvertToButton(MenuCommand command)
{
return CanConvertTo<Text>(command.context);
}
[MenuItem("CONTEXT/Text/Convert To Text", false)]
static void ConvertToButton(MenuCommand command)
{
ConvertTo<Text>(command.context);
}
protected static bool CanConvertTo<T>(Object context)
where T : MonoBehaviour
{
return context && context.GetType() != typeof(T);
}
protected static void ConvertTo<T>(Object context) where T : MonoBehaviour
{
var target = context as MonoBehaviour;
var so = new SerializedObject(target);
so.Update();
bool oldEnable = target.enabled;
target.enabled = false;
foreach (var script in Resources.FindObjectsOfTypeAll<MonoScript>())
{
if (script.GetClass() != typeof(T))continue;
so.FindProperty("m_Script").objectReferenceValue = script;
so.ApplyModifiedProperties();
break;
}
(so.targetObject as MonoBehaviour).enabled = oldEnable;
}
#endif
}
友情提示:
- 由于子組件是父組件的功能擴(kuò)展号显,必定會新增字段啥的,父組件替換子組件會導(dǎo)致子組件定制化的序列化數(shù)據(jù)丟失躺酒。故筆者并不推薦父組件反過來替換子組件(自嗨另算)押蚤。
- 每一個運(yùn)行的Unity 組件基本上都有一個 Editor腳本與之對應(yīng)用于繪制Inspector,在UGUI組件中羹应,Text / Image 這些的Editor 腳本是繼承GraphicEditor類型的揽碘,而 Button/Toggle/DropDown這些組件因?yàn)槔^承了 Selection,所以他們的 Editor腳本是繼承的 SelectionEditor。
- 隨著版本更新上述SelectionEditor已經(jīng)作古雳刺,同時上述 TextEx 演示代碼的重寫 OnInspectorGui方法劫灶,Inspector布局也變樣了,如果沒強(qiáng)迫癥的還可以玩玩掖桦。
寫的最后:
簡單的筆記本昏,也希望對讀者有所幫助吧!
當(dāng)然枪汪,還有大把擴(kuò)展的余地涌穆,譬如為之創(chuàng)建具有 EditorWindow 的自動化工作流。雀久。冰啃。