之前一直使用唐老师的程序基础框架,因为需求,也不停的在框架上修修改改,不同的项目需求就再重改一次,在我看完筑梦老师的ui框架后就想,直接修改为一套较为完整的ui框架。
在基于唐老师的程序基础框架上参考初见老师的fantasyUI和筑梦老师的ZMUIFramework来搭建一套较完整的ui框架,因为我长期使用唐老师框架,所以基本上保留唐老师框架内的全部接口和调用方法,以便完成后可以快速替换以前的工程。
alone-snow/BaseProject
框架功能:
- ui管理系统,使用UIManager统一管理面板创建销毁。
- 自动化生成系统,一键生成数据引用与常用控件事件监听。
- 界面搭建与逻辑分离,分工合作更为便捷,不会因为修改同一脚本导致git冲突。
- 脱离monobehaviour,但使用unity生命周期相同的虚函数,不熟悉框架的人也能快速上手,老工程也能快速替换。
- 界面预加载,解决界面引用过大资源导致的卡顿。
- 界面堆栈,调用下一界面时自动隐藏自己,下一界面主动关闭时再显示自己,面板之间反复压栈隐藏,再弹出显示。
- 界面队列弹出,快速压入多个面板,在需要时再先进先出依次显示。
- 界面快速关闭,esc键快速关闭当前面板。
- 子界面,面板挂载面板,父面板隐藏时,子面板一同隐藏
一 面板搭建
1.面板物体创建
空物体下挂载以上脚本,因为所有面板使用统一配置,所以再设置完canve参数后就能保存为模板预制体,更改图层顺序就能设置面板层级,比如游戏界面类面板为100,提示类面板等为200,加载类系统面板为300
2.面板元素搭建
和普通面板搭建一致,命名随意
3.panelUI
此脚本是根据初见老师的fantasyUI修改而来的,ComponentName代表面板名称,自动生成该名称的面板类,其中AssetName和BundleName暂时没用,如果有需要可以自己修改PanelUIEditor中的自动生成脚本。
4.面板元素引用
需要将要引用的物体拖拽到脚本上,它会自动判断物体的类型,是GameObject则会判断是否有注册脚本,有就引用该脚本,如果有多个注册脚本则判断优先级。自定义则会判断名称前缀是否有“[""]”内容,有就在物体上查找该脚本(比如[Button]btnSure,它就会查找Button组件,生成脚本时就会生命Public Button btnSure字段),没有就引用本身。
- 字段的名称,当物体名称不适合作为字段名称时可以更改为自己想要的名称
- 下拉面板为物体使用类型,当自动判断的类型不符合需求时,可以手动更改,不存在更改脚本时,不会更改下拉内容
- 引用物体,物体本体。
修改注册脚本与优先级可以在PanelUIEditor中修改
//注册列表
public static List<Type> UIComponentType = new List<Type>()
{
typeof(Slider),
typeof(Toggle),
typeof(Dropdown),
typeof(ScrollRect),
typeof(InputField),
typeof(TMP_Text),
typeof(TMP_Dropdown),
typeof(TMP_InputField),
typeof(Button),
typeof(Text),
typeof(Image),
typeof(RawImage),
typeof(ListHandler),
};
//优先级列表
public static List<Type> UIComponentTypeSort = new List<Type>()
{
typeof(Slider),
typeof(Toggle),
typeof(Dropdown),
typeof(ScrollRect),
typeof(InputField),
typeof(Button),
typeof(ListHandler),
};
5.注册面板
保存预制体,然后点击注册面板,面板便会注册到框架内,预制体可以放在任何文件夹下,没有限制。
6.脚本生生成
点击Generate Code,脚本会生成在Application.dataPath+"/Scripts/UI/Entity/"文件夹下
using UnityEngine;
public partial class ArchivePanel : BasePanel
{
public TMPro.TextMeshProUGUI txtTransitionCost;
public TMPro.TextMeshProUGUI txtJumpCost;
public TMPro.TextMeshProUGUI txtSearchCost;
public TMPro.TextMeshProUGUI txtMaxSearchAmount;
public TMPro.TextMeshProUGUI txtSearchStrength;
public TMPro.TextMeshProUGUI txtName;
public UnityEngine.UI.Button btnDelete;
public UnityEngine.UI.Button btnStart;
public UnityEngine.UI.Button btnQuit;
public UnityEngine.UI.ScrollRect svArchiveList;
public override void Init(PanelUI ui)
{
txtTransitionCost = ui.GetReference<TMPro.TextMeshProUGUI>("txtTransitionCost");
txtJumpCost = ui.GetReference<TMPro.TextMeshProUGUI>("txtJumpCost");
txtSearchCost = ui.GetReference<TMPro.TextMeshProUGUI>("txtSearchCost");
txtMaxSearchAmount = ui.GetReference<TMPro.TextMeshProUGUI>("txtMaxSearchAmount");
txtSearchStrength = ui.GetReference<TMPro.TextMeshProUGUI>("txtSearchStrength");
txtName = ui.GetReference<TMPro.TextMeshProUGUI>("txtName");
btnDelete = ui.GetReference<UnityEngine.UI.Button>("btnDelete");
btnStart = ui.GetReference<UnityEngine.UI.Button>("btnStart");
btnQuit = ui.GetReference<UnityEngine.UI.Button>("btnQuit");
svArchiveList = ui.GetReference<UnityEngine.UI.ScrollRect>("svArchiveList");
string btnDeleteButtonName = "btnDelete";
btnDelete.onClick.AddListener(()=>{ OnClick(btnDeleteButtonName); });
string btnStartButtonName = "btnStart";
btnStart.onClick.AddListener(()=>{ OnClick(btnStartButtonName); });
string btnQuitButtonName = "btnQuit";
btnQuit.onClick.AddListener(()=>{ OnClick(btnQuitButtonName); });
}
}
7.编写逻辑脚本
然后我们就能在其他地方新建的新的脚本写逻辑即可
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public partial class ArchivePanel : BasePanel
{
public override void Awake()
{
txtName.text = "name";
Debug.Log("panel is awake");
}
protected override void OnClick(string btnName)
{
switch (btnName)
{
case "btnQuit":
Debug.Log("quit");
break;
}
}
}
二 逻辑脚本编写
1.生命周期函数
Awake()
在面板被加载到场景上时调用,预加载面板时,会调用Awake而不会调用Start和OnEnable。
OnEnable()
在面板被激活时调用
Start()
在面板第一次激活时调用
Update()
每帧更新
OnDisable()
面板失活时调用
OnDestory()
面板被摧毁时调用
2.其他生命周期函数
IsOutHide()
返回一个bool值,判断该面板是否可以快速关闭,默认返回true
OnOutHide()
当快速关闭时调用
ShowMe()
被显示时触发,通常用于ui的显示动画,与OnEnable区分,便于动画与逻辑分开编写,默认为空
HideMe(UnityAction callBack)
被隐藏时调用,可以播放隐藏动画,结束时再调用callback,告诉管理器,该面板完成隐藏了,(注意:不调用callback,会导致管理器一直等待该面板隐藏),默认为直接调用callback
3.事件函数
OnClick(string btnName)
按钮被点击时触发,btnName:触发控件字段名称
OnToggleValueChanged(string toggleName, bool value)
toggle修改时触发,toggleName:触发控件字段名称
OnInputFieldValueChanged(string inputFieldName, string value)
输入字段修改触发,inputFieldName:触发控件字段名称
OnDropdownValueChanged(string dropdownName, int value)
下拉列表修改触发,dropdownName:触发控件字段名称
4.内置方法
ShowPanel<T>(UnityAction<T> callBack = null)
在此面板上显示一个子面板
HidePanel(BasePanel basePanel = null)
隐藏指定面板,为空时隐藏自己
三 UIManager的使用
1.常用方法
//预加载面板
PreparePanel<T>()
//显示栈面板,与PushPanel功能一致
StackPanel<T>(UnityAction<T> callBack = null)
//堆栈面板,将上一个栈面板隐藏,显示这个栈面板
PushPanel<T>(UnityAction<T> callBack = null)
//弹出一个栈面板显示,注意无论原本栈面板是否隐藏都失去栈面板身份,所以非必要不要手动调用
PopPanel()
//显示队列面板,若已经有队列面板显示则进入队列等待,不显示,不触发回调,直到弹出。
QueuePanel<T>(UnityAction<T> callBack = null)
//队列面板中加入一个队列面板,不显示
EnqueuePanel<T>(UnityAction<T> callBack = null)
//弹出一个队列面板显示,若已有队列面板显示,则不执行
DequeuePanel()
//显示一个普通面板
ShowPanel<T>(UnityAction<T> callBack = null)
//显示一个指定的面板
ShowPanel<T>(T panel, UnityAction<T> callBack = null)
//显示一个子面板
ShowChilderPanel<T>(BasePanel parentPanel, UnityAction<T> callBack = null)
//隐藏一个面板
HidePanel<T>(UnityAction callBack = null)
//隐藏一个指定面板
HidePanel(BasePanel basePanel, UnityAction callBack = null)
//获得一个面板
T GetPanel<T>()
//清除所有的队列和栈面板,不会隐藏已显示的但会失去特殊面板身份
ClearAllQueueAndStack()
//隐藏现有显示面板,ifClearQueueAndStack:是否清除所有的队列和栈面板
//若不清除,则会在隐藏特殊面板时显示下一个特殊面板,导致面板清除不干净
HideAllPanel(bool ifClearQueueAndStack = true)
//销毁所有面板,excludeVisible:是否销毁已显示的面板
DestroyAllPanel(bool excludeVisible = true)
资源
alone-snow/BaseProject
克隆下后,只需要复制/Assets/Scripts/ProjectBase文件夹就行,
该文件夹下原版是唐老师的程序基础框架,经过修改的有
- uimanager
- poolmgr(添加了几个方便的方法,没有太多修改)
- ResMgr(基于之前的发步的参考 Mirror 序列化方法的序列化与反序列化解决方案,添加的序列化方法)
- InputMgr(老版输入系统,新版输入系统还在完善,新版输入系统兼容多手柄,键位修改等)
因为框架我才更改完善,应该会有比较多的问题,也没什么机会去广泛测试
所以有任何问题请询问QQ:1768927122
发在评论区我大概率看不到。