Unity笔记-32-UI框架(实现)
资源统一调配
单例模版
public class Singleton<T> where T:class//class表示是引用类型
{
private static T _singleton;
//单例属性
public static T Instance
{
get
{
if (_singleton == null)
{
//因为是单例,必须要有构造,但是如果有公有构造就不行,必须是私有构造
//但是如果是私有构造的化,这里不能使用new的方式了
//必须使用反射
//派生的单例类中必须有私有的无参构造
_singleton = (T)Activator.CreateInstance(typeof(T), true);
}
return _singleton;
}
}
//这里要注意,这是单例模版的基类,它自身默认公有构造,不要给它定义私有构造,作为基类,是要被继承的,它的子类自身定义私有构造即可
//如果基类定义了私有构造,那么其就无法被继承,因为,子类无法调用父类的构造,继承构造的本质是首先构造父类再构造子类
}
这里需要重点说明的是,由于单例需要私有构造,但是作为所有单例的父类,它本身不能是私有构造,因为继承要求首先构造父类,如果此模版定义为私有,它的私有构造就无法被子类访问,也就不能被继承。因此,这里对于单例的创建使用反射,派生单例类中必须有私有构造,此模版通过反射来构造派生类,同样对于泛型T,它必须是引用类型,因为它必须要有构造函数。
统一资源管理类
namespace UIFrame
{
public class AssetsManager : Singleton<AssetsManager>
{
private AssetsManager()
{
assetsCache = new Dictionary<string, Object>();
}
private Dictionary<string, Object> assetsCache;//缓存机制
/// <summary>
/// 获取资源
/// </summary>
/// <param name="path">资源路径</param>
/// <returns>返回资源</returns>
[Obsolete("旧的资源加载方法,新方法:GetAsset")]
public Object OldGetAsset(string path)
{
Object assetObj = null;
if (!assetsCache.ContainsKey(path))
{
assetObj = Resources.Load(path);
assetsCache.Add(path, assetObj);
}
else
{
assetObj = assetsCache[path];
}
return assetObj;
}
/// <summary>
/// 获取资源
/// </summary>
/// <param name="path">资源路径</param>
/// <returns>返回资源</returns>
public Object GetAsset(string path)
{
Object assetObj = null;
if (!assetsCache.TryGetValue(path, out assetObj))//尝试获取资源,返回是否获取的bool
{
assetObj = Resources.Load(path);
assetsCache.Add(path, assetObj);
}
return assetObj;
}
}
}
该类的作用是加载资源,并设置缓存机制,对于第一次加载的资源,通过资源路径path 去加载资源,并将此资源存入缓存字典中,第二次加载该资源,直接从缓存字典中获取即可。
资源路径配置与Json解析
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UIFrame
{
public class JsonDataManager : Singleton<JsonDataManager>
{
private JsonDataManager()
{
panelDataDic = new Dictionary<int, Dictionary<string, string>>();
localizationDic = new Dictionary<int, Dictionary<string, string[]>>();
ParsePanelData();
ParseLocalizationData();
}
#region Save Structure
//Panel解析数据
private JsonPanelsModel panelData;
//Panel解析数据【字典版】
private Dictionary<int, Dictionary<string, string>> panelDataDic;
//本地化内容解析数据
private JsonLocalizationModel LocalizationData;
//本地化内容解析后的数据【字典版】
private Dictionary<int, Dictionary<string, string[]>> localizationDic;
#endregion
/// <summary>
/// Json解析Panel配置文件
/// </summary>
private void ParsePanelData()
{
//获取配置文本资源
TextAsset panelConfig = AssetsManager.Instance.GetAsset(SystemDefine.PanelConfigPath) as TextAsset;
panelData = JsonUtility.FromJson<JsonPanelsModel>(panelConfig.text);
//将panelData转化为可方便检索的字典
for(int i = 0; i < panelData.AllData.Length; i++)
{
Dictionary<string, string> crtDic = new Dictionary<string, string>();
panelDataDic.Add(i, crtDic);
for(int j = 0; j < panelData.AllData[i].Data.Length; j++)
{
crtDic.Add(panelData.AllData[i].Data[j].PanelName, panelData.AllData[i].Data[j].PanelPath);
}
}
}
/// <summary>
/// Json解析本地化配置文件
/// </summary>
private void ParseLocalizationData()
{
//获取配置文本资源
TextAsset LocalizationConfig = AssetsManager.Instance.GetAsset(SystemDefine.LocalizationConfigPath) as TextAsset;
LocalizationData = JsonUtility.FromJson<JsonLocalizationModel>(LocalizationConfig.text);
//将panelData转化为可方便检索的字典
for (int i = 0; i < LocalizationData.AllData.Length; i++)
{
Dictionary<string, string[]> crtDic = new Dictionary<string, string[]>();
localizationDic.Add(i, crtDic);
for (int j = 0; j < LocalizationData.AllData[i].Data.Length; j++)
{
crtDic.Add(LocalizationData.AllData[i].Data[j].TextObjName, LocalizationData.AllData[i].Data[j].LanguageText);
}
}
}
/// <summary>
/// 通过名称返回路径
/// </summary>
/// <param name="panelName">模块名称</param>
/// <returns>模块路径</returns>
public string FindPanelPath(string panelName, int sceneId = 0)
{
if (!panelDataDic.ContainsKey(sceneId))
{
return null;
}
if (!panelDataDic[sceneId].ContainsKey(panelName))
{
return null;
}
return panelDataDic[sceneId][panelName];
}
/// <summary>
/// 通过游戏对象名返回本地化语言数组
/// </summary>
/// <param name="textObjName">元件名称</param>
/// <param name="sceneId">场景ID</param>
/// <returns>本地化语言数组</returns>
public string[] FindTextLocalization(string textObjName,int sceneId=0)
{
if (!localizationDic.ContainsKey(sceneId))
{
return null;
}
if (!localizationDic[sceneId].ContainsKey(textObjName))
{
return null;
}
return localizationDic[sceneId][textObjName];
}
}
}
{
"AllData": [
{
"SceneName":"MainScene",
"Data": [
{
"PanelName": "MainPanel",
"PanelPath": "Panels/MainPanel"
},
{
"PanelName": "LockPanel",
"PanelPath": "Panels/LockPanel"
},
{
"PanelName": "SystemPanel",
"PanelPath": "Panels/SystemPanel"
}
]
},
{
"SceneName": "FightScene",
"Data": [
{
"PanelName": "FightPanel",
"PanelPath": "Panels/FightPanel"
}
]
}
]
}
以合适的Json配置文件,存储资源路径,并创建对应的模型类,使用统一的Json数据管理类去解析Json配置文件,并将数据存放于字典中,方便后续查找,最终开放唯一查找接口,通过名字与场景ID查找对应数据
系统常数类
namespace UIFrame
{
public static class SystemDefine
{
#region ConfigurationPath
public const string PanelConfigPath = "Configuration/UIPanelConfig";
public const string LocalizationConfigPath = "Configuration/LanguageTextConfig";
#endregion
#region Scene ID
public enum Scene
{
MainScene=0,
LockScene=1
}
#endregion
#region Widget Token
public static string[] WIDGET_TOKEN
= new string[] { "_F", "_S", "_T" };
#endregion
}
}
将配置文件的路径写在常数类中,方便调用
UI模态处理
基本思路是使用栈,当需要加载新的UI模块时,栈顶元素执行暂停方法,在将新的UI模块入栈并执行进入方法。
这里我们需要一个挂在所有UI模块上的基类,该类管理UI模块的状态调配(暂停,恢复,进入,退出等动画处理以及其他数据处理),并能够获取其他任意的元件,每一个模块都有独立的模块类去继承该基类重写方法去实现自身独立的状态调配
同样,我们还需要一个挂在所有模块的所有元件的基类,该类管理其元件的所有可能需要操作的组件,这里需要创建一个UIMono以提供所有可能组件的操作,事件添加,并让这个基类去继承它
为了方便我们管理所有模块以及其中的所有元件,我们还需要一个UI管理类,该类能够存储所有的模块,所有的元件,以及管理当前栈中的模块以实现模态处理,并且给出对外接口:通过模块名称,元件名称就能查找到对应元件的方法;以及模块入栈,出栈的方法。显然,这里需要用到字典
以上仅仅考虑到框架逻辑,我们还考虑到业务逻辑
因此,对于所有的模块,还需要绑定一个模块控制基类来专门写业务逻辑,控制该模块内部元件的事件,每一个模块都有独立的控制类去继承该基类并实现自身业务逻辑
以下代码较多,依次为:UI管理类,UI模块基类,UI组件基类,UI控制类,UI类型,UI类型管理,UIMono
后面两个类,逻辑比较简单,容易理解,即是将模块数据单独用一个模型类存储并使用该模型的管理类去管理存储,以方便UI管理类中的调配,如果觉得麻烦,直接使用UI模块名称和路径即可
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UIFrame
{
//UI管理类
public class UIManager : Singleton<UIManager>
{
private UIManager()
{
uiModules = new Dictionary<UIType, UIModuleBase>();
uiWidgets = new Dictionary<string, Dictionary<string, UIWidgetBase>>();
uiStack = new Stack<UIModuleBase>();
_canvas = GameObject.Find("Canvas").transform;
}
//管理所有UI模块
private Dictionary<UIType, UIModuleBase> uiModules;
//UI模块的栈存储
private Stack<UIModuleBase> uiStack;
//管理所有UI元件
private Dictionary<string, Dictionary<string, UIWidgetBase>> uiWidgets;
//主画布
private Transform _canvas;
#region UI Modules GameObject
/// <summary>
/// 获取并存储UI模块
/// </summary>
/// <param name="uiType">UI模型</param>
/// <returns>UI模块基类</returns>
private UIModuleBase GetUIModule(UIType uiType)
{
UIModuleBase crtModule = null;
if(!uiModules.TryGetValue(uiType,out crtModule))
{
crtModule = InstantiateUIModule(AssetsManager.Instance.GetAsset(uiType.Path) as GameObject);
uiModules.Add(uiType,crtModule);
}else if (crtModule == null)//之前可能存到字典里了,但是可能丢失
{
crtModule = InstantiateUIModule(AssetsManager.Instance.GetAsset(uiType.Path) as GameObject);
uiModules[uiType] = crtModule;
}
return crtModule;
}
/// <summary>
/// 创建UI模块
/// </summary>
/// <param name="prefab">预制体</param>
/// <returns>UI模块基类</returns>
private UIModuleBase InstantiateUIModule(GameObject prefab)
{
GameObject crtModuleObj = GameObject.Instantiate(prefab);
crtModuleObj.transform.SetParent(_canvas, false);//位置归零?
//crtModuleObj.name = prefab.name;
return crtModuleObj.GetComponent<UIModuleBase>();
}
#endregion
#region UI Module Stack
/// <summary>
/// 通过PanelName压栈
/// </summary>
/// <param name="uiPanelName">模块名称</param>
public void PushUI(string uiPanelName)
{
UIType uIType = UITypeManager.Instance.GetUIType(uiPanelName);
UIModuleBase crtModuleBase = GetUIModule(uIType);
if (uiStack.Count != 0)
{
//栈顶暂停
uiStack.Peek().OnParse();
}
uiStack.Push(crtModuleBase);
//当前窗口执行进入方法
crtModuleBase.OnEnter();
}
/// <summary>
/// 栈顶元素出栈
/// </summary>
public void PopUI()
{
//出栈并执行离开方法
if (uiStack.Count != 0)
{
uiStack.Pop().OnExit();
}
else
{
Debug.LogWarning("UI栈顶没有元素,无法出栈!");
}
if (uiStack.Count != 0)
{
uiStack.Peek().OnResume();
}
}
#endregion
#region UI Widgets-->Module (Un)Register
/// <summary>
/// 注册UI模块
/// </summary>
/// <param name="moduleName">模块名称</param>
private void RegisterUIModuleToUIWidgets(string moduleName)
{
if (!uiWidgets.ContainsKey(moduleName))
{
uiWidgets.Add(moduleName,new Dictionary<string, UIWidgetBase>());
}
else
{
Debug.LogWarning("该模块已经存在,无需再次添加...");
}
}
/// <summary>
/// 移除UI模块
/// </summary>
/// <param name="moduleName">模块名称</param>
private void UnRegisterUIModuleFromUIWidgets(string moduleName)
{
if (uiWidgets.ContainsKey(moduleName))
{
uiWidgets.Remove(moduleName);
}
else
{
Debug.LogWarning("无该模块,无法删除");
}
}
#endregion
#region UI Widget Add/Remove
/// <summary>
/// 添加元件
/// </summary>
/// <param name="moduleName">模块名称</param>
/// <param name="widgetName">元件名称</param>
/// <param name="uIWidgetBase">元件脚本组件</param>
public void AddUIWidget(string moduleName,string widgetName,UIWidgetBase uIWidgetBase)
{
//如果模块不存在,添加模块
RegisterUIModuleToUIWidgets(moduleName);
if (uiWidgets[moduleName].ContainsKey(widgetName))
{
Debug.LogWarning("该元件已经存在");
}
else
{
uiWidgets[moduleName].Add(widgetName,uIWidgetBase);
}
}
/// <summary>
/// 移除元件
/// </summary>
/// <param name="moduleName">模块名称</param>
/// <param name="widgetName">元件名称</param>
public void RemoveUIWidget(string moduleName, string widgetName)
{
if (uiWidgets[moduleName].ContainsKey(widgetName))
{
uiWidgets[moduleName].Remove(widgetName);
}
else
{
Debug.LogWarning("该元件不存在");
}
}
#endregion
#region Find Widget
/// <summary>
/// 获取元件脚本组件
/// </summary>
/// <param name="moduleName">模块名称</param>
/// <param name="widgetName">元件名称</param>
/// <returns>元件基类</returns>
public UIWidgetBase FindWidget(string moduleName, string widgetName)
{
//防止空引用
RegisterUIModuleToUIWidgets(moduleName);
UIWidgetBase uIWidgetBase = null;
//尝试获取元件
uiWidgets[moduleName].TryGetValue(widgetName, out uIWidgetBase);
return uIWidgetBase;
}
#endregion
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Localization;
namespace UIFrame
{
//当前组件依赖于CanvasGroup组件
[RequireComponent(typeof(CanvasGroup))]
//模块基类
public class UIModuleBase : MonoBehaviour
{
protected CanvasGroup _canvasGroup;
private Transform[] allChild;
public virtual void Awake()
{
_canvasGroup = GetComponent<CanvasGroup>();
//获取当前模块的所有子对象
allChild = GetComponentsInChildren<Transform>();
//修改模块名称
gameObject.name = gameObject.name.Remove(gameObject.name.Length-7);
AddWidgetBehaviour();
}
#region Control Bind
/// <summary>
/// 绑定控制器
/// </summary>
/// <param name="controllerBase">控制器基类</param>
protected void BindController(UIControllerBase controllerBase)
{
controllerBase.ControllerInit(this);
}
#endregion
#region Set Widgets
/// <summary>
/// 添加元件行为基类脚本
/// </summary>
private void AddWidgetBehaviour()
{
//遍历子对象
for(int i = 0; i < allChild.Length; i++)
{
//遍历所有标记
for(int j = 0; j < SystemDefine.WIDGET_TOKEN.Length; j++)
{
if (allChild[i].name.EndsWith(SystemDefine.WIDGET_TOKEN[j]))
{
AddComponentForWidget(i);
}
}
}
}
//为了方便子类重写,如果添加其他组件的话
/// <summary>
/// 元件初始化
/// </summary>
/// <param name="index">元件索引</param>
protected virtual void AddComponentForWidget(int index)
{
//给该元素添加UIWidgetBase组件
UIWidgetBase uIWidgetBase = allChild[index].gameObject.AddComponent<UIWidgetBase>();
uIWidgetBase.UIWidgetInit(this);
}
#endregion
#region Find Widgets
/// <summary>
/// 寻找所有的元件
/// </summary>
/// <param name="widgetName">元件名称</param>
/// <returns>元件基类</returns>
public UIWidgetBase FindWidgetInCurrentModule(string widgetName)
{
return UIManager.Instance.FindWidget(name,widgetName);
}
#endregion
#region State Call
/// <summary>
/// 进入当前模块
/// </summary>
public virtual void OnEnter()
{
_canvasGroup.blocksRaycasts = true;
//LocalizationManager.Instance.LocalizationInit();
}
/// <summary>
/// 退出当前模块
/// </summary>
public virtual void OnExit()
{
_canvasGroup.blocksRaycasts = false;
//Destroy(gameObject);
}
/// <summary>
/// 暂停当前模块
/// </summary>
public virtual void OnParse()
{
_canvasGroup.blocksRaycasts = false;
}
/// <summary>
/// 恢复当前模块
/// </summary>
public virtual void OnResume()
{
_canvasGroup.blocksRaycasts = true;
}
#endregion
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UIInterface;
namespace UIFrame
{
//元件基类
public class UIWidgetBase : UIMono
{
/// <summary>
/// 元件当前的模块
/// </summary>
public UIModuleBase currentModule;
/// <summary>
/// 元件初始化,并将其加入UI管理的元件字典
/// </summary>
/// <param name="uIModuleBase">元件所在模块</param>
public void UIWidgetInit(UIModuleBase uIModuleBase)
{
currentModule = uIModuleBase;
UIManager.Instance.AddUIWidget(currentModule.name, name, this);
}
/// <summary>
/// 元件销毁,并在UI管理的元件字典中移除自身
/// </summary>
protected virtual void OnDestroy()
{
UIManager.Instance.RemoveUIWidget(currentModule.name, name);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UIFrame
{
/// <summary>
/// 专注于写业务逻辑
/// </summary>
public class UIControllerBase
{
protected UIModuleBase crtModule;
public void ControllerInit(UIModuleBase uIModuleBase)
{
crtModule = uIModuleBase;
ControllerStart();
}
protected virtual void ControllerStart()
{
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UIFrame
{
public class UIType
{
public string Name { get; set; }
public string Path { get; set; }
public UIType(string path)
{
Path = path;
Name = path.Substring(path.LastIndexOf('/')+1);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UIFrame
{
public class UITypeManager :Singleton<UITypeManager>
{
private UITypeManager()
{
_uiTypes = new Dictionary<string, UIType>();
}
//UIType缓存池
private Dictionary<string, UIType> _uiTypes;
/// <summary>
/// 通过UIPanelName获取路径
/// </summary>
/// <param name="uiPanelName">UI模块名称</param>
public UIType GetUIType(string uiPanelName)
{
UIType uiType = null;
if(!_uiTypes.TryGetValue(uiPanelName,out uiType))
{
uiType = new UIType(JsonDataManager.Instance.FindPanelPath(uiPanelName));
_uiTypes.Add(uiPanelName,uiType);
}
return uiType;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace UIInterface
{
public class UIMono : MonoBehaviour,
IRectTransform, IText, IImage, IRawImage,
IButton, IInputField
{
#region components
private RectTransform _rectTransform;
private Text _text;
private Image _image;
private RawImage _rawImage;
private Button _button;
private InputField _inputField;
#endregion
#region Mono CallBack
protected virtual void Awake()
{
_rectTransform = GetComponent<RectTransform>();
_text = GetComponent<Text>();
_image = GetComponent<Image>();
_rawImage = GetComponent<RawImage>();
_button = GetComponent<Button>();
_inputField = GetComponent<InputField>();
}
#endregion
public virtual void AddOnClickListener(UnityAction action)
{
_button.onClick.AddListener(action);
}
public virtual void AddOnValueChangeListener(UnityAction<string> action)
{
_inputField.onValueChanged.AddListener(action);
}
public virtual Color GetImageColor()
{
return _image.color;
}
public virtual string GetInputFieldText()
{
return _inputField.text;
}
public virtual Sprite GetSprite()
{
return _image.sprite;
}
public virtual void SetImageColor(Color color)
{
_image.color = color;
}
public virtual void SetInputFieldText(string text)
{
_inputField.text = text;
}
public virtual void SetSprite(Sprite sprite)
{
_image.sprite = sprite;
}
public virtual string GetText()
{
return _text.text;
}
public virtual void SetText(string text)
{
_text.text = text;
}
public virtual void SetTextColor(Color color)
{
_text.color = color;
}
}
}
框架完成后,只需要提供一个游戏外观接口即可,以加载基本模块
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UIFrame;
public class GameFacade : MonoBehaviour
{
/// <summary>
/// 游戏启动
/// </summary>
private void Start()
{
UIManager.Instance.PushUI("MainPanel");
}
}
本地化语言
提供一个本地化语言管理类,一个本地化语言类挂在所有文本对象上,本地化语言管理类存储所有本地化语言类的语言变更方法,统一调配即可,通过配置文件事先加载所有对象的文本并创建对象的时候载入
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace Localization
{
public class LocalizationManager : Singleton<LocalizationManager>
{
private SupportLanguage _supportLanguage;
//存储所有方法的委托对象
private Action<int> localizationEventHandle;
private LocalizationManager()
{
}
public void LocalizationInit()
{
int id = PlayerPrefs.GetInt("LanguageID");
ChangeLanguage((SupportLanguage)id);
}
/// <summary>
/// 添加事件监听
/// </summary>
/// <param name="action"></param>
public void AddLocalizationListener(Action<int> action)
{
localizationEventHandle += action;
}
/// <summary>
/// 移除事件监听
/// </summary>
/// <param name="action"></param>
public void RemoveLocalizationListener(Action<int> action)
{
localizationEventHandle -= action;
}
/// <summary>
/// 更改语言
/// </summary>
/// <param name="supportLanguage"></param>
public void ChangeLanguage(SupportLanguage supportLanguage)
{
//_supportLanguage = supportLanguage;
localizationEventHandle((int)supportLanguage);
PlayerPrefs.SetInt("LanguageID", (int)supportLanguage);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UIFrame;
namespace Localization
{
[RequireComponent(typeof(Text))]
public class LocalizationText : MonoBehaviour
{
private Text _text;
private string[] languageTexts;
private void Awake()
{
_text = GetComponent<Text>();
languageTexts = JsonDataManager.Instance.FindTextLocalization(name);
_text.text = languageTexts[PlayerPrefs.GetInt("LanguageID")];//
}
//private void Start()
//{
// //_text.text = languageTexts[(int)LocalizationManager.Instance._supportLanguage];
//}
private void OnEnable()
{
LocalizationManager.Instance.AddLocalizationListener(SetLanguageText);
}
private void OnDisable()
{
LocalizationManager.Instance.RemoveLocalizationListener(SetLanguageText);
}
private void SetLanguageText(int languageID)
{
_text.text = languageTexts[languageID];
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Localization
{
public enum SupportLanguage
{
简体中文 = 0,
English = 1,
にほん = 2
}
}
|