Unity的程序基础框架,视频地址:https://www.bilibili.com/video/BV1C441117wU
一、建立目录
在Assets下建立标准目录,如下图:
二、功能模块
三、脚本
3.1、单例模式基类
知识点:单例模式是设计模式中很常用的一种模式,它的目的是让一个类在程序运行期间有且只有一个实例。因为只有一个实例,减少了内存消耗和系统性能开销。单例模式有三个特点:
(1)单例类只能有一个实例。
(2)单例类必须自己创建自己的唯一实例。
(3)单例类必须给所有其他对象提供这一实例。
脚本:BaseManager.cs
//定义单例模式基类,采用泛型构造,使用无参构造函数约束泛型T的类型。
public class BaseManager<T> where T:new()
{
//定义单例模式基类实例变量
private static T instance;
//定义单例模式基类中实例化方法
public static T GetInstance()
{
//如果实例为空,则实例化对象
if (instance == null)
instance = new T();
return instance;
}
}
//------------------------------------------------------------------------------------
//单例模式基类的使用,新的单例模式类直接继承基类
public class GameManager : BaseManager<GameManager>
{
}
public class ItemManager : BaseManager<ItemManager>
{
}
//其他单例模式类的使用
public class test
{
void main()
{
GameManager.GetInstance();
ItemManager.GetInstance();
}
}
3.2、缓存池模块
在GameObject大量生成->销毁的情况下(如子弹射击),销毁的物体只是删除对内存空间的引用,但并未释放实例化物体占用的内存空间。当内存空间占满后进行GC(Garbage Collection),将造成游戏运行卡顿。为了避免这种情况,通过缓存池对销毁物体的内存空间进行暂存,当需要创建新物体时直接从缓存池取出,新物体不再占用新的内存空间(除非缓存池中存放的物体数量不够),从而减少内存的消耗。
知识点:命名空间System.Collections.Generic中有两个非常重要,而且常用的泛型集合类,它们分别是Dictionary
脚本:PoolMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class PoolMgr : BaseManager<PoolMgr>
{
//定义构造类,每类放入缓存池的物体应当有一个标识其类别的父对象,及存储物体的容器
public class PoolData
{
public GameObject PoolFather;
public List<GameObject> PoolList;
//FObj物体用于在缓存池内表明物体具体类别,PoolObj物体用于代表缓存池,多个FObj的父对象都设置为PoolObj以体现层级关系
public PoolData(GameObject Obj,GameObject PoolObj)
{
PoolFather = new GameObject(Obj.name);
PoolFather.transform.parent = PoolObj.transform;
PoolList = new List<GameObject>() { };
PutObj(Obj);
}
public void PutObj(GameObject Obj)
{
Obj.SetActive(false);
PoolList.Add(Obj);
Obj.transform.parent = PoolFather.transform;
}
public GameObject GetObj()
{
GameObject Obj = null;
Obj = PoolList[0];
PoolList.RemoveAt(0);
//取出物体时使其激活
Obj.SetActive(true);
//取出缓存池的物体断开和缓存池物体的父子联系
Obj.transform.parent = null;
return Obj;
}
}
//创建缓存池,定义字典参数PoolDict实现多个对象的存储,其中第一个string参数说明缓存物体的类别,用自定义的PoolData类型作为第二个参数存储对象类别(PoolFather)和对象List列表(PoolList)。
public Dictionary<string, PoolData> PoolDict = new Dictionary<string, PoolData>();
//定义一个物体,放入缓存池中的物体挂接到该物体下面,实现界面的简介易懂
private GameObject PoolObj;
//把物体放入缓存池,第一个参数存储物体名字,第二个参数存储物体实例
public void PutObj(string name,GameObject Obj)
{
//如果缓存池物体为空,则创建一个
if (PoolObj == null)
PoolObj = new GameObject("Pool");
//如果缓存池中有此类物体的List,则添加进该类物体的List中;如缓存池中无此类物体,则新建一个此类物体的List,并将该物体存入新建的List。
if (PoolDict.ContainsKey(name))
{
//在对应name类的List中存入物体
PoolDict[name].PutObj(Obj);
}else
{
//新建一个name类的PoolData,并把Obj物体添加进PoolData的List里
PoolDict.Add(name, new PoolData(Obj,PoolObj));
}
}
//采用异步加载方式从缓存池中取出指定物体,通过物体名字进行索引
public void GetObj(string name,UnityAction<GameObject> CallBack)
{
//如果缓存池中存在指定的物体名,且该类物体数量大于0则取出,否则重新实例化一个物体
if (PoolDict.ContainsKey(name) && PoolDict[name].PoolList.Count > 0)
{
CallBack(PoolDict[name].GetObj());
//调用PoolData中的GetObj()方法,从缓存池中取出物体
//Obj = PoolDict[name].GetObj();
}
else
{
ResourcesMgr.GetInstance().LoadAsync<GameObject>(name,(PObj)=>
{
PObj.name = name;
CallBack(PObj);
});
//重新实例化要取出的物体,从Resources目录中调用预制体
//Obj = GameObject.Instantiate(Resources.Load(name));
//把实例化的物体名字改为传入的调用名字,否则物体实例化后会在名字后面加上(clone),不利于后续处理
//Obj.name = name;
}
}
//切场景的时候应当清除缓存池并把缓存池物体置空,否则调用会报错
public void PoolClear()
{
PoolDict.Clear();
PoolObj = null;
}
}
// PoolGetTest .cs----------------------------------------------------------------------
//从缓存池中取出物体脚本。当按下鼠标左键时,从缓存池取出指定名称为"Test/ Monster "的物体,当缓//存池中没有物体时,将从取出Resources/Test/目录下的Cube预制体进行实例化。按下鼠标右键时,则对"Test/ Player "物体进行相同操作。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolGetTest : MonoBehaviour
{
void Update()
{
if(Input.GetMouseButtonDown(0))
{
PoolMgr.GetInstance().GetObj("Test/Monster",(Obj)=> { });
}
if (Input.GetMouseButtonDown(1))
{
PoolMgr.GetInstance().GetObj("Test/Player", (Obj) => { });
}
}
}
// PoolPutTest .cs----------------------------------------------------------------------
//将物体放入缓存池脚本。将此脚本挂在Resources/Test/目录下的预制体上,当该物体被实例化调用后,延迟1秒后放入缓存池。
public class PoolPutTest : MonoBehaviour
{
//当对象激活时,会进入的生命周期函数
private void OnEnable()
{
Invoke("Put",1);
}
void Put()
{
PoolMgr.GetInstance().PutObj(this.gameObject.name,this.gameObject);
}
}
3.3、事件中心模块
通过Dictionary
脚本:EventMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class EventMgr : BaseManager<EventMgr>
{
//Dictionary的Key对应字符串类型的事件名(如怪物死亡,玩家死亡等),Value对应的是监听这个事件的委托函数们,此处使用UnityEngine.Events命名空间下自带的事件标识UnityAction。
private Dictionary<string, UnityAction<object>> EventDict = new Dictionary<string, UnityAction<object>>();
//添加事件监听,添加指定事件名字的监听器,并说明准备处理该事件的方法
public void AddEventListener(string name,UnityAction<object> action)
{
//如果事件管理模块中有name名字的事件,则在该事件中加入一个传入的处理方法
if(EventDict.ContainsKey(name))
{
EventDict[name] += action;
}
//如果事件管理模块中没有name名字的事件,则添加这个事件以及对应的处理方法
else
{
EventDict.Add(name, action);
}
}
//移除事件监听,当物体销毁时应当在事件管理器中移除该物体监听事件执行的方法
public void RemoveEventListener(string name,UnityAction<object> action)
{
if (EventDict.ContainsKey(name))
EventDict[name] -= action;
}
//事件触发器,指定名字的事件将被触发,并传递参数说明触发事件的具体对象
public void EventTrigger(string name,object info)
{
if (EventDict.ContainsKey(name))
{
EventDict[name].Invoke(info);
}
}
//当切换场景时,清空事件管理器中存储的事件及对应的方法
public void Clear()
{
EventDict.Clear();
}
}
//EventTestPlayer.cs-------------------------------------------------------------------
//挂在Player物体上,用来测试在事件触发器中添加当发生某事件时,需要触发的方法。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventTestPlayer : MonoBehaviour
{
//在事件触发器中添加当发生"MonsterDead"事件时,要触发的方法
private void Start()
{
EventMgr.GetInstance().AddEventListener("MonsterDead", MonsterDeadDo);
}
//怪物死亡事件触发后,要执行的方法,如获得奖励
public void MonsterDeadDo(object info)
{
Debug.Log((info as EventTestMonster).name+"死亡得奖励");
}
//当物体销毁时,要删除事件触发器中已添加的方法
private void OnDestroy()
{
EventMgr.GetInstance().RemoveEventListener("MonsterDead", MonsterDeadDo);
}
}
//EventTestMonster.cs-------------------------------------------------------------------------------------------------------
//挂在Monster物体上,用来测试当发生某事件时如何触发,并传递参数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventTestMonster : MonoBehaviour
{
void Start()
{
Dead();
}
//如果怪物死亡则触发"MonsterDead"事件,并把本身作为参数传递,以便判断是具体哪个怪物触发的事件。触发事件意味着执行该事件对应的一切方法。
void Dead()
{
EventMgr.GetInstance().EventTrigger("MonsterDead",this);
}
}
3.4、公共MONO模块
所有脚本都执行Update会形成一定系统资源浪费,通过公共Mono模块制作一个唯一的Update脚本,可在一定程度上降低程序开销。同时也可用公共Mono模块制作一个唯一的开启协程的脚本。
脚本:MonoController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
//Mono管理者
public class MonoController : MonoBehaviour
{
//定义UpdateEvent作为帧更新事件
private event UnityAction UpdateEvent;
void Start()
{
DontDestroyOnLoad(this.gameObject);
}
// 在自身Update的时候同时执行UpdateEvent帧更新事件
void Update()
{
if(UpdateEvent!=null)
{
UpdateEvent();
}
}
//提供给外部的添加帧更新事件
public void AddUpdateListener(UnityAction action)
{
UpdateEvent += action;
}
//提供给外部的移除帧更新事件
public void RemoveUpdateListener(UnityAction action)
{
UpdateEvent -= action;
}
}
脚本:MonoMgr.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Internal;
//为MonoController.cs提供外部接口
public class MonoMgr : BaseManager<MonoMgr>
{
//定义一个MonoController类型的变量
private MonoController Controller;
public MonoMgr()
{
//当MonoMgr被调用时,创建一个名为"MonoController"的游戏物体,并在该游戏物体上挂载MonoController脚本组件。
//将上面定义的Controller变量赋值为新创建游戏物体上的脚本组件
GameObject Obj = new GameObject("MonoController");
Controller = Obj.AddComponent<MonoController>();
}
//提供接口,以便通过MonoMgr从外部执行添加帧更新事件
public void AddUpdateListener(UnityAction action)
{
Controller.AddUpdateListener(action);
}
//提供接口,以便通过MonoMgr从外部执行移除帧更新事件
public void RemoveUpdateListener(UnityAction action)
{
Controller.RemoveUpdateListener(action);
}
//在继承MonoBehaviour的脚本中输入StartCoroutine(),点击按F12可打开MonoBehaviour中的协程相关函数,把相关功能封装到MonoMgr中,从外部即可通过MonoMgr调用协程
public Coroutine StartCoroutine(string methodName)
{
return Controller.StartCoroutine(methodName);
}
public Coroutine StartCoroutine(IEnumerator routine)
{
return Controller.StartCoroutine(routine);
}
public Coroutine StartCoroutine(string methodName, [DefaultValue("null")] object value)
{
return Controller.StartCoroutine(methodName,value);
}
//可依次将以下协程函数进行封装,以便通过MonoMgr从外部调用协程函数
//public Coroutine StartCoroutine_Auto(IEnumerator routine);
//public void StopAllCoroutines();
//public void StopCoroutine(IEnumerator routine);
//public void StopCoroutine(Coroutine routine);
//public void StopCoroutine(string methodName);
}
MonoTest.cs----------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonoTest:MonoBehaviour
{
private void Start()
{
//定义并实例化变量TT为未继承MonoBehaviour类的对象
TestTest TT=new TestTest();
//通过MonoMgr添加帧更新方法
MonoMgr.GetInstance().AddUpdateListener(TT.UpdateTest);
}
}
public class TestTest
{
//测试通过MonoMgr调用协程
public TestTest()
{
MonoMgr.GetInstance().StartCoroutine(CoroutineTest());
}
//测试帧更新执行情况
public void UpdateTest()
{
Debug.Log("TestTestUpdate");
}
//测试协程执行情况
IEnumerator CoroutineTest()
{
yield return new WaitForSeconds(1f);
Debug.Log("CoroutineTest");
}
}
3.5、场景切换模块
管理场景切换过程中及切换后需要做的事。尤其是在新场景中需要动态的改变一些事情,如通过配置文件加载新场景中的物体,或根据玩家数据动态创建玩家。另,如果需要使用场景切换功能,必须在Unity3D的Build Setting中把需要加载的场景添加进Scenes in Build中。
脚本:ScenesMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
public class ScenesMgr : BaseManager<ScenesMgr>
{
// 同步加载场景
public void LoadScene(string SceneName,UnityAction Action)
{
SceneManager.LoadScene(SceneName);
//加载SceneName场景后,执行action事件
Action();
}
//提供给外部的异步加载场景接口
public void LoadSceneAsync(string SceneName, UnityAction Action)
{
MonoMgr.GetInstance().StartCoroutine(ReallyLoadSceneAsync(SceneName, Action));
}
//通过协程实现异步加载场景
private IEnumerator ReallyLoadSceneAsync(string SceneName,UnityAction Action)
{
AsyncOperation Ao = SceneManager.LoadSceneAsync(SceneName);
//如果场景没有加载完,则一直返回Ao.progress(0-1之间)表示的加载进度
while (!Ao.isDone)
{
//可使用事件管理模块触发进度条更新事件,触发事件的对象为Ao.progress,外部可在需要的时候添加对应的执行方法
EventMgr.GetInstance().EventTrigger("进度条更新", Ao.progress);
//通过获取加载进度,为更新加载进度条提供支持
yield return Ao.progress;
}
Action();
}
}
3.6、资源加载模块
脚本:ResourcesMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class ResourcesMgr : BaseManager<ResourcesMgr>
{
//同步加载资源,当前资源加载完毕后再执行后续脚本
public T Load<T>(string ResPathName)where T:Object
{
//加载路径为name的资源
T res = Resources.Load<T>(ResPathName);
//如果加载的资源为游戏物体,则直接实例化并返回,否则直接返回
if (res is GameObject)
return GameObject.Instantiate(res);
else
return res;
}
//异步加载资源,当前资源加载的同时直接执行后续脚本,避免因加载较大资源时的卡顿感
//因为异步加载只有资源加载完后,才可对资源进行操作,所以定义一个加载完成事件,命名为CallBack,用于执行资源加载完成后需要的操作
public void LoadAsync<T>(string ResPathName,UnityAction<T> CallBack) where T:Object
{
MonoMgr.GetInstance().StartCoroutine(ReallyLoadAsync(ResPathName, CallBack));
}
private IEnumerator ReallyLoadAsync<T>(string ResPathName, UnityAction<T> CallBack) where T:Object
{
ResourceRequest Res = Resources.LoadAsync<T>(ResPathName);
yield return Res;
if (Res.asset is GameObject)
CallBack(GameObject.Instantiate(Res.asset) as T);
else
CallBack(Res.asset as T);
}
}
ResMgrTest.cs--------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ResMgrTest : MonoBehaviour
{
void Update()
{
//点鼠标左键调用同步加载
if(Input.GetMouseButtonDown(0))
ResourcesMgr.GetInstance().Load<GameObject>("Test/Monster");
//点鼠标右键调用异步加载
if(Input.GetMouseButtonDown(1))
ResourcesMgr.GetInstance().LoadAsync<GameObject>("Test/Player",DoSomething);
}
private void DoSomething(GameObject Obj)
{
//异步加载完毕后,需要执行的操作
}
}
3.7、输入控制模块
通常需要在角色的Update里面添加输入控制脚本,如需要操作多个角色或物体,则需要在每个角色的Update里重复书写输入控制脚本。这里把输入控制功能独立出来,通过事件管理模块分发至每一个角色中,以达到简化代码,降低程序耦合性的目的。
脚本:InputMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InputMgr :BaseManager<InputMgr>
{
//定义一个布尔值,用于检测是否允许输入生效
private bool InputCheck=false;
//在InputMgr构造函数中,添加Update监听事件
public InputMgr()
{
MonoMgr.GetInstance().AddUpdateListener(InputUpdate);
}
public void InputCheckAllow(bool Check)
{
InputCheck = Check;
}
private void InputUpdate()
{
CheckKeyCode(KeyCode.W);
CheckKeyCode(KeyCode.S);
CheckKeyCode(KeyCode.A);
CheckKeyCode(KeyCode.D);
}
private void CheckKeyCode(KeyCode Key)
{
//如果InputCheck为false,意为不允许输入,则直接返回,不再分发按键事件
if (!InputCheck)
return;
if(Input.GetKeyDown(Key))
//事件管理模块,分发按键按下抬起事件
EventMgr.GetInstance().EventTrigger("按下某键",Key);
if (Input.GetKeyUp(Key))
//事件管理模块,分发按键按下抬起事件
EventMgr.GetInstance().EventTrigger("抬起某键",Key);
}
}
InputTest.cs--------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class InputTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//将输入检测设置为开启
InputMgr.GetInstance().InputCheckAllow(true);
EventMgr.GetInstance().AddEventListener("按下某键",CheckInputDown);
EventMgr.GetInstance().AddEventListener("抬起某键",CheckInputUp);
}
private void CheckInputDown(object Key)
{
KeyCode Code = (KeyCode)Key;
switch (Code)
{
case KeyCode.W:
Debug.Log("前进");
break;
case KeyCode.S:
break;
case KeyCode.A:
break;
case KeyCode.D:
break;
}
}
private void CheckInputUp(object Key)
{
KeyCode Code = (KeyCode)Key;
switch(Code)
{
case KeyCode.W:
Debug.Log("停止前进");
break;
case KeyCode.S:
break;
case KeyCode.A:
break;
case KeyCode.D:
break;
}
}
// Update is called once per frame
void Update()
{
}
}
3.8、音效管理模块
统一管理音乐音效的调用和管理
脚本:MusicMgr.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class MusicMgr : BaseManager<MusicMgr>
{
private AudioSource BGMusic=null, ASource = null;
private float MusicVolume=0.1f,SoundVolume = 0.1f;
private GameObject SoundObj = null;
private List<AudioSource> SoundList = new List<AudioSource>();
public MusicMgr()
{
MonoMgr.GetInstance().AddUpdateListener(Update);
}
private void Update()
{
//遍历音效List,如果该音效没有在播放,则删除该音效,并从List中删除
for(int i=SoundList.Count-1;i>=0;i--)
{
if(!SoundList[i].isPlaying)
{
GameObject.Destroy(SoundList[i]);
SoundList.RemoveAt(i);
}
}
}
public void PlayBGM(string BGMName)
{
if(BGMusic == null)
{
GameObject Obj = new GameObject();
Obj.name = "BgMusic";
BGMusic = Obj.AddComponent<AudioSource>();
}
ResourcesMgr.GetInstance().LoadAsync<AudioClip>("Music/BGMusic/" + BGMName, (Clip) =>
{
BGMusic.clip = Clip;
BGMusic.volume = MusicVolume;
BGMusic.loop = true;
BGMusic.Play();
});
}
public void SetBGMVolume(float MVolume)
{
MusicVolume = MVolume;
if (BGMusic == null)
return;
BGMusic.volume = MusicVolume;
}
public void PauseBGM()
{
if (BGMusic == null)
return;
BGMusic.Pause();
}
public void StopBGM()
{
if (BGMusic == null)
return;
BGMusic.Stop();
}
public void PlaySound(string SoundName,bool IsLoop,UnityAction<AudioSource> CallBack=null)
{
if(SoundObj == null)
{
SoundObj = new GameObject();
SoundObj.name = "Sounds";
}
ResourcesMgr.GetInstance().LoadAsync<AudioClip>("Music/Sounds/" + SoundName, (Clip) =>
{
ASource = SoundObj.AddComponent<AudioSource>();
ASource.clip = Clip;
ASource.volume = SoundVolume;
ASource.loop = IsLoop;
ASource.Play();
SoundList.Add(ASource);
if (CallBack != null)
CallBack(ASource);
});
}
public void SetSoundVolume(float SVolume)
{
SoundVolume = SVolume;
//将音效列表中所有音效的音量都改变
for(int i=0; i<SoundList.Count; i++)
{
SoundList[i].volume = SoundVolume;
}
}
public void StopSound(AudioSource ASource)
{
if(SoundList.Contains(ASource))
{
SoundList.Remove(ASource);
ASource.Stop();
GameObject.Destroy(ASource);
}
}
}
MusicTest.cs---------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicTest : MonoBehaviour
{
float Mvolume = 0;
AudioSource AudioClip;
private void OnGUI()
{
if (GUI.Button(new Rect(100, 50, 100, 50), "播放背景音乐"))
{
MusicMgr.GetInstance().SetBGMVolume(0);
MusicMgr.GetInstance().PlayBGM("AfterBattle");
}
if (GUI.Button(new Rect(100, 150, 100, 50), "暂停背景音乐"))
MusicMgr.GetInstance().PauseBGM();
if (GUI.Button(new Rect(100, 250, 100, 50), "停止背景音乐"))
MusicMgr.GetInstance().StopBGM();
//设置音量值Mvolume随时间推移增加,每帧调用MusicMgr中的SetBGMVolume改变音量
//实现背景音乐音量从小到大渐变效果
Mvolume += Time.deltaTime/100;
MusicMgr.GetInstance().SetBGMVolume(Mvolume);
if (GUI.Button(new Rect(300, 50, 100, 50), "播放音效"))
{
MusicMgr.GetInstance().SetSoundVolume(0);
MusicMgr.GetInstance().PlaySound("AfterBattle",false,(AClip)=>
{
AudioClip = AClip;
});
}
if (GUI.Button(new Rect(300, 250, 100, 50), "停止音效"))
{
MusicMgr.GetInstance().StopSound(AudioClip);
AudioClip = null;
}
//每帧调用MusicMgr中的SetSoundVolume改变音量实现音效音量从小到大渐变效果
MusicMgr.GetInstance().SetSoundVolume(Mvolume);
}
}
3.9、UI管理模块
首先通过BasePanel.cs实现对每个面板的管理,该脚本主要功能是通过代码快速找到UI面板下所有的UI控件对象,方便在子类里处理逻辑,并且提供显示/隐藏面板功能。子类UITest通过继承BasePanel类,获得UI控件对象,并执行需要的逻辑。
然后通过UIMgr.cs来管理所有显示的面板,提供给外部显示/隐藏的控制接口。这里通过CreatPanel脚本演示了调用UIMgr实现对面板的显示/隐藏控制。
脚本:BasePanel.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
//UI面板基类,通过代码快速找到所有子控件,方便在子类中处理逻辑
//找到自己面板下所有的控件对象
//提供显示和隐藏的功能
public class BasePanel : MonoBehaviour
{
//通过里式转换原则,存储面板下的所有控件。string存储控件的名字,List存储控件的类型
//因为Button类控件下同时有Image和Button两个组件,所以采用List存储同一个名字控件下的多个组件
private Dictionary<string, List<UIBehaviour>> UIDict = new Dictionary<string, List<UIBehaviour>>();
void Awake()
{
FindUIChildren<Button>();
FindUIChildren<Text>();
FindUIChildren<Image>();
FindUIChildren<Toggle>();
FindUIChildren<Slider>();
FindUIChildren<Scrollbar>();
//可以把所有类型的UI控件都列出来找一遍
}
// 从存储容器UIDict中得到对应名字的对应UI控件
protected T GetUIComponents<T>(string UIName)where T:UIBehaviour
{
if (UIDict.ContainsKey(UIName))
{
for(int i=0;i<UIDict[UIName].Count;i++)
{
if (UIDict[UIName][i] is T)
return UIDict[UIName][i] as T;
}
}
return null;
}
//找到子物体中的UI控件,并存入字典UIDict中以备调用
private void FindUIChildren<T>() where T:UIBehaviour
{
//采用泛型数组,存储物体下包含子物体的所有UI组件
T[] UIChildren = this.GetComponentsInChildren<T>();
//对泛型数组进行遍历,将找到的所有UI组件都存入字典UIDict中,以备调用
for (int i = 0; i < UIChildren.Length; i++)
{
//如果字典中已有相应的控件名,则说明同一控件下有两个组件,则把新找到的组件继续存入同名控件的List中
if (UIDict.ContainsKey(UIChildren[i].gameObject.name))
UIDict[UIChildren[i].gameObject.name].Add(UIChildren[i]);
//如果字典中无相应控件名,则将找到的UI组件根据名字和类型存入字典中
else
UIDict.Add(UIChildren[i].gameObject.name, new List<UIBehaviour>() { UIChildren[i] });
}
}
//虚函数,可在子类中重写具体逻辑
public virtual void ShowMe()
{
}
public virtual void HideMe()
{
}
}
UITest.cs------------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UITest : BasePanel
{
void Start()
{
GetUIComponents<Button>("Start").onClick.AddListener(ClickStart);
GetUIComponents<Button>("Quit").onClick.AddListener(ClickQuit);
}
public void InitInfo()
{
Debug.Log("初始化信息");
}
public void ClickStart()
{
//点击Start按钮以后载入Loading界面
//UIMgr.GetInstance().ShowPanel("LoadingPanel", UI_Layer.MidLayer, DoWhenLoaded);
Debug.Log("StartGame");
}
public void ClickQuit()
{
Debug.Log("QuitGame");
}
}
脚本:UIMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
//使用枚举来存储UI层级
public enum UI_Layer
{
BotLayer,MidLayer,TopLayer,SystemLayer,
}
//管理所有显示的面板
//提供给外部显示和隐藏的接口
public class UIMgr : BaseManager<UIMgr>
{
//建立字典,作为存储面板的容器,string存储面板名字,BasePanel存储面板脚本
public Dictionary<string, BasePanel> PanelDict = new Dictionary<string, BasePanel>();
private Transform BotLayer, MidLayer, TopLayer, SystemLayer;
public UIMgr()
{
//获取并创建Canvas和EventSystem,并使其在载入新场景时不被移除
GameObject Obj = ResourcesMgr.GetInstance().Load<GameObject>("Test/Canvas");
Transform TestCanvas = Obj.transform;
GameObject.DontDestroyOnLoad(Obj);
BotLayer = TestCanvas.Find("Bot");
MidLayer = TestCanvas.Find("Mid");
TopLayer = TestCanvas.Find("Top");
SystemLayer = TestCanvas.Find("System");
Obj = ResourcesMgr.GetInstance().Load<GameObject>("Test/EventSystem");
GameObject.DontDestroyOnLoad(Obj);
}
public void ShowPanel<T>(string PanelName, UI_Layer Layer, UnityAction<T> CallBack= null ) where T : BasePanel
{
//如果显示面板时,该面板已经被显示,则直接执行载入后的逻辑。
if (PanelDict.ContainsKey(PanelName))
{
//可在子类中重写ShowMe虚函数方法,执行里面的逻辑
PanelDict[PanelName].ShowMe();
if (CallBack != null)
CallBack(PanelDict[PanelName] as T);
return;
}
ResourcesMgr.GetInstance().LoadAsync<GameObject> ("Test/"+PanelName,(UIObj)=>
{
//把加载的Panel作为Canvas的子对象,并且设置相对位置
Transform UIFather = BotLayer;
switch (Layer)
{
case UI_Layer.MidLayer:
UIFather = MidLayer;
break;
case UI_Layer.TopLayer:
UIFather = TopLayer;
break;
case UI_Layer.SystemLayer:
UIFather = SystemLayer;
break;
}
//把将要显示的Panel挂到传入的UILayer,成为其的子物体
UIObj.transform.SetParent(UIFather);
//设置相对位置和大小
UIObj.transform.localPosition = Vector3.zero;
UIObj.transform.localScale = Vector3.one;
(UIObj.transform as RectTransform).offsetMax = Vector2.zero;
(UIObj.transform as RectTransform).offsetMin = Vector2.zero;
//得到预设体上的BasePanel脚本,并且执行。因为是异步加载,必须是panel预设体加载完成后,才可执行其上的脚本。
T PanelScripts = UIObj.GetComponent<T>();
//处理预设体加载完成后的逻辑
if (CallBack != null)
CallBack(PanelScripts);
//可在子类中重写ShowMe虚函数方法,执行里面的逻辑
PanelScripts.ShowMe();
//把新加载面板的信息存入PanelDict容器以备调用
PanelDict.Add(PanelName, PanelScripts);
});
}
//隐藏面板
public void HidePanel(string PanelName)
{
if (PanelDict.ContainsKey(PanelName))
{
//可在子类中重写HideMe虚函数方法,执行里面的逻辑
PanelDict[PanelName].HideMe();
//如果PanelDict中有指定名字的面板,则删除该面板游戏对象,并从PanelDict中删除相应面板的存储信息
GameObject.Destroy(PanelDict[PanelName].gameObject);
PanelDict.Remove(PanelName);
}
}
}
CreatPanel.cs--------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreatPanel : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
UIMgr.GetInstance().ShowPanel<UITest>("TestPanel", UI_Layer.MidLayer,DoWhenLoaded);
}
private void DoWhenLoaded(UITest DoSomething)
{
DoSomething.InitInfo();
Invoke("DelayHidePanel", 5);
}
private void DelayHidePanel()
{
UIMgr.GetInstance().HidePanel("TestPanel");
}
}