赞
踩
本框架是我从github上clone下的,只能算是自己一些理解
若,若是侵权请联系我删除,若是有错误请指出
原项目地址
本框架包含了以下类
BaseManager、BasePanel、EventCenter、InputMgr、MonoController、MonoMger、MusicMgr、PoolMgr、ResMgr、UIManager
框架的作用便是协助开发,提升开发效率,但也不能一味的使用框架丧失了新功能的开发,不断学习,对自己的框架进行更行是程序员必须做的,下面我们就开始简单介绍这些框架
最基础的类,大部分的类继承于它
此类为单例模式,项目中所有的使用单例模式的类都继承于它,并且此类使用了泛型,将大大减少重复性代码的出现
相信大家对单例模式都不陌生,就不注重细说了
//使用where对泛型T进行约束
public class BaseManager<T> where T:new()
{
private static T instance;
//静态函数,建T的实体instance
public static T GetInstance()
{
if (instance == null)
instance = new T();
return instance;
}
//返回这个类的实体instance
}
因为我们大部分类都继承自了BaseManager,而BaseManager没有继承MonoBehaviour,所以在这些类中我们无法使用Unity为我们提供的周期函数,所以我们定义了一个MonoController类让它实现周期函数
此框架为基本框架,如有需要亦可修改使用
public class MonoController : MonoBehaviour { //设置事件,用此事件监听需要循环更新执行的函数 private event UnityAction updateEvent; private event UnityAction fixedUpdateEvent; //此物体不可移除 private void Start() { DontDestroyOnLoad(this.gameObject); } //如果此事件不为空,则在此类的Update里调用 private void Update() { if (updateEvent != null) updateEvent(); } //随时间更行的调用 private void FixedUpdate() { if (fixedUpdateEvent != null) fixedUpdateEvent(); } //对方法添加监听 public void AddUpdateListener(UnityAction func) { updateEvent += func; } //移除 public void RemoveUpdateListener(UnityAction func) { updateEvent -= func; } public void AddFixedUpdateListener(UnityAction func) { fixedUpdateEvent += func; } public void RemoveFiUpdateListener(UnityAction func) { fixedUpdateEvent -= func; } }
在拥有了MonoController后,我们需要解决的就是对MonoController的使用问题了,为此我们需要创建一个管理类MonoMgr,将此类继承自BaseManager做成单例模式,这样我们便能方便的调用MonoController了
using System.Collections; using System.Collections.Generic; using System.ComponentModel; using UnityEngine; using UnityEngine.Events; public class MonoMgr : BaseManager<MonoMgr> { //为了能够使用并管理MonoController,我们首先需要一个MonoController对象 private MonoController controller; //由于是单例模式,我们创建构造函数来进行一些必要的初始化 public MonoMgr() { //MonoController并不是单例模式,为了使用我们要让它在游戏中生成,首先新创建一个游戏物体名为MonoController GameObject obj = new GameObject("MonoController"); //挂载脚本,并获得此脚本的引用controller controller = obj.AddComponent<MonoController>(); } //调用controller里的添加监听的方法,注意传入的参数为无参无返回值的方法 public void AddUpdateListener(UnityAction func) { controller.AddUpdateListener(func); } //同上,不过此方法针对的是随时间更新 public void AddFixUpdateListener(UnityAction func) { controller.AddFixedUpdateListener(func); } //移除监听在Update里的方法 public void RemoveUpdeteListener(UnityAction func) { controller.RemoveUpdateListener(func); } //同上移除监听在FixUpdater的方法 public void RemoveFixUpdateListener(UnityAction func) { controller.RemoveFiUpdateListener(func); } //开启协程方法及其重载方法,使用时注意选择 public Coroutine StartCoroutine(IEnumerator routine) { return controller.StartCoroutine(routine); } public Coroutine StartCoroutine(string methodName, [DefaultValue("null")] object value) { return controller.StartCoroutine(methodName, value); } public Coroutine StartCoroutine(string methodName) { return controller.StartCoroutine(methodName); } }
资源加载类,通过Resource文件夹进行资源的加载
需要注意一点,此时加载出来的物体的this.name都会成为原名加上(clone)
所以在使用缓存池PoolMgr.PushObj时谨慎使用this.name
//单例模式继承自BaseManager,使用时可以通过GetInstance调用 public class ResMgr : BaseManager<ResMgr> { //同步加载 //使用是要注意给出T的类型,如ResMagr.GetInstance().Load<GameObject>() public T Load<T>(string objName) where T : Object { T res = Resources.Load<T>(objName); //如果res是一个GameObject if (res is GameObject) { T t = GameObject.Instantiate(res); t.name = objName; return t; } else //else情况示例:TextAsset、AudioClip return res; } //异步加载,异步加载使用起来在观感上更加顺滑,适用于较大的资源 //异步加载使用协程 public void LoadAsync<T>(string name, UnityAction<T> callback) where T : Object { //由于是单例模式,要使用协程需要用到MonoMgr MonoMgr.GetInstance().StartCoroutine(ReallyLoadAsync<T>(name, callback)); } private IEnumerator ReallyLoadAsync<T>(string _name, UnityAction<T> callback) where T : Object { ResourceRequest r = Resources.LoadAsync<T>(name); yield return r;//直到系统读取完 if (r.asset is GameObject) { T t = GameObject.Instantiate(r.asset) as T; t.name = _name;//去点(clone)字段 callback(t);//callback方法作用很大,例如获得其组件脚本,改变其状态 } else r.asset.name = _name; callback(r.asset as T); } }
缓存池系统。系统频繁生成物体和销毁物体是很浪费性能的,为此我们可以将暂时用不到的物体使用SetActive(false)来隐藏掉,并将他们存入缓存池,在需要使用时在取出
public class PoolData { //缓存池中可能有多个不同种类的物体,为方便管理故需要设置一个父物体 public GameObject fatherObj; //使用List链式结构来存储物体 public List<GameObject> poolList; //构造函数,进行PoolData的一些初始化 public PoolData(GameObject obj,GameObject poolObj) { fatherObj = obj; fatherObj.transform.parent = poolObj.transform; poolList = new List<GameObject>(); PushObj(obj); } //将物体push进缓存池 public void PushObj(GameObject obj) { //存储、设置父物体、隐藏 poolList.Add(obj); obj.transform.SetParent(fatherObj.transform); obj.SetActive(false); } //将物体从缓存池取出 public GameObject GetObj() { GameObject obj = null; obj = poolList[0]; poolList.RemoveAt(0); obj.SetActive(true); obj.transform.parent = null; return obj; } } //缓存池定义完成,我们需要一个对缓存池进行管理的单例模式类 public class PoolMgr : BaseManager<PoolMgr> { //使用字典存储数据 private Dictionary<string, PoolData> poolDic = new Dictionary<string, PoolData>(); //缓存池的父物体 private GameObject poolObj; //从缓存池中取出 public void GetObj(string objName, UnityAction<GameObject> callback) { //若缓存池中存在,取出 if (poolDic.ContainsKey(objName) && poolDic[objName].poolList.Count > 0) callback(poolDic[objName].GetObj()); //若不存在则使用ResMgr动态加载 else { //注意生成的物体的this.name此时并不是objName,而是objName(clone),若是使用this.name进行PushObj的话,则无法使用objName从字典中进行Get ResMgr.GetInstance().LoadAsync<GameObject>(objName,callback); } } //将物体存如缓存池 //使用时注意objName,使用动态加载出来的物体的this.name会在原名后加上(clone)字样,此时使用this.name进行PushObj操作时,实际是创建了另一个池子,所以使用时推荐直接使用"objName"的方式而不是this.name的方式 public void PushObj(string objName, GameObject obj) { if (poolObj == null) poolObj = new GameObject("Pool");//实例化,此后所以在缓存池的物体全部为其紫萼u提 if(poolDic.ContainsKey(objName))//如果缓存池中已经存在其类型,则将物体加入其中 poolDic[objName].PushObj(obj); else//若缓存池中没有此类物体,则添加至字典 poolDic.Add(objName,new PoolData(obj,poolObj)); //我们采用的就结构是PoolData 类,里面含有链式结构PoolList } //清空缓存池 public void Clear() { poolDic.Clear(); poolObj = null; } }
事件中心,通过事件我们来解决各个函数的运行问题,减少脚本的耦合性,是结构桁架美观
事件系统的构成主要依赖于委托,委托没有学号的小伙伴最好先去学习下
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; //两个类使用此接口,可用此接口来调用对象,可使用as判断 public interface iEveninfo { } //创建类继承自接口,以后便可以通过接口来引用此类 public class EventInfo<T> : iEveninfo { //注意参数名actions,多加了一个S,此委托监听多个函数 public Action<T> actions; //action,将要监听的函数 public EventInfo(Action<T> action) { actions += action; } } //同上,不过此时的委托为无参委托 public class EventInfo : iEveninfo { public Action actions; public EventInfo(Action action) { actions += action; } } //事件系统为单例模式,继承自BaseManager public class EventCenter : BaseManager<EventCenter> { //使用字典结构,可以方便的通过“name"来触发委托,注意此时用的是接口 public Dictionary<string, iEveninfo> eventDic = new Dictionary<string, iEveninfo>(); //添加监听,注意监听的函数为带参函数 public void AddEventListener<T>(string eventNumber, Action<T> action) { //如果字典中已经有此eventNumber,将此方法添加至字典中 if (eventDic.ContainsKey(eventNumber)) (eventDic[eventNumber] as EventInfo<T>).actions += action;//使用as来转换,此时接口转化成了EventInfo<T> //若没有此eventNumber,则在字典中添加事件名和事件方法 else eventDic.Add(eventNumber,new EventInfo<T>(action)); } //添加监听,同上不过方法无参数 public void AddEventListener(string eventNumber, Action action) { if (eventDic.ContainsKey(eventNumber)) (eventDic[eventNumber] as EventInfo).actions += action; else eventDic.Add(eventNumber,new EventInfo(action)); } //触发监听的事件,由于存在泛型T,及我们的所监听的函数需要一个类型为T的参数才能触发 public void EventTrigger<T>(string eventNumber, T info) { //如果字典里存在此事件并且actions不为空 if(eventDic.ContainsKey(eventNumber)&& (eventDic[eventNumber] as EventInfo<T>).actions!=null ) (eventDic[eventNumber] as EventInfo<T>).actions.Invoke(info); } //同上,但不需要参数 public void EventTrigger(string eventNumber) { if (eventDic.ContainsKey(eventNumber)&& (eventDic[eventNumber] as EventInfo).actions != null) (eventDic[eventNumber] as EventInfo).actions.Invoke(); } //移除监听 public void RemoveEventListener<T>(string eventNumber, Action<T> action) { if (eventDic.ContainsKey(eventNumber)) (eventDic[eventNumber] as EventInfo<T>).actions -= action; } //如上,移除监听 public void RemoveEventListener(string eventNumber, Action action) { if (eventDic.ContainsKey(eventNumber)) (eventDic[eventNumber] as EventInfo).actions -= action; } //清空字典 public void Clear() { eventDic.Clear(); } }
输入系统,我们的对键盘鼠标的操作都可以在此进行定义,由于我们的操作要实时检测输入,所以在定义InputMgr时我们要使用到前面的MonoMgr
Unity2020版本 推出了新的Input System,有兴趣的小伙伴可以去学习,本文暂且不表
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; //继承自BaseManager public class InputMgr : BaseManager<InputMgr> { //输入开关 private bool isStart = false; private float timer; //构造函数,对设置初始化 public InputMgr() { MonoMgr.GetInstance().AddUpdateListener(MyUpDate);//帧更新,应该设置时间更新 MonoMgr.GetInstance().AddFixUpdateListener(MyFixUpdate); } public void StartOrEndCheck(bool isOpen) { isStart = isOpen; } private void MyUpDate() { if(!isStart) return; //在其中可设置你需要的输入方式,以下是我自己的定义 FindMousePoint();//找到鼠标的点,并实现事件 CheckHMove();//鼠标WS实现上下移动 CheckVMove();//鼠标AD实现左右移动 ChecKKeyCode(KeyCode.J)//实现按下J的事件监听 } //随时间更新 void MyFixUpdate() { if(!isStart) return; CheckMouseClick(0.12f); } //检测鼠标输入 void CheckMouseClick(float t) { timer += Time.deltaTime; if (Input.GetMouseButton(0) && timer >= t) { EventCenter.GetInstance().EventTrigger("Fire"); timer = 0; } } //鼠标输入,得到点击的位置 void FindMousePoint() { Vector3 point = Input.mousePosition; point.z = -10; point = Camera.main.ScreenToWorldPoint(point); Debug.Log(point); EventCenter.GetInstance().EventTrigger("FindMousePoint", point); } //检测WS键,执行VMove事件 public void CheckVMove() { float v = Input.GetAxisRaw("Vertical");//对于WS EventCenter.GetInstance().EventTrigger("VMove",v); } //检测AD键,执行HMove事件 public void CheckHMove() { float h = Input.GetAxisRaw("Horizontal");//对于AD EventCenter.GetInstance().EventTrigger("HMove",h); } //检测键盘输入,根据按压类别启动不同事件 public void CheckKeyCode(KeyCode key) { if (Input.GetKeyDown(key)) EventCenter.GetInstance().EventTrigger("KeyIsDown",key); if(Input.GetKey(key)) EventCenter.GetInstance().EventTrigger("KeyAlwaysDown",key); if(Input.GetKeyUp(key)) EventCenter.GetInstance().EventTrigger("KeyIsOpen",key); } }
UI的基础类,我们创建出的所有UI都可以继承自此类,当然也可以只选择为UI的父物体创建,用父物体的脚本来控制子物体的空间
BasePanel并不是管理,而是实现UI功能
public class BasePanel : MonoBehaviour { //使用字典存储UI名和其身上的控件 public Dictionary<string, List<UIBehaviour>> controlDic = new Dictionary<string, List<UIBehaviour>>(); private void Awake()//在Start之前就得到下列 { //或的控件并保存在字典中,可根据自己UI搭载的控件更改 FinChildControl<Button>(); FinChildControl<Image>(); FinChildControl<Scrollbar>(); FinChildControl<Text>(); } //返回名为controlName的UI身上的T控件《若没有则返回null protected T GetControl<T>(string controlName) where T : UIBehaviour { if (controlDic.ContainsKey(controlName)) { for(int i=0;i<controlDic[controlName].Count;i++) { if (controlDic[controlName][i] is T)//使用is判断是否为T控件 return controlDic[controlName][i] as T; } } return null; } //找到物体及其子物体的UI控件保存至字典中 private void FinChildControl<T>() where T : UIBehaviour { T[] controls = this.GetComponentsInChildren<T>();//注意用的是GetComents,返回的是一组 string objName;//使用名字对控件按物体分类 for (int i = 0; i < controls.Length; i++) { objName = controls[i].gameObject.name; if(controlDic.ContainsKey(objName)) controlDic[objName].Add(controls[i]);//注意controlDic中存储的类型为<string, List<UIBehaviour>,所以我们的同一个UI的所有控件都在这个链表中 else controlDic.Add(objName,new List<UIBehaviour>(){controls[i]});//添加UI到字典中 } } //虚函数,可在继承类中自己定义 public virtual void ShowMe() { } //同上 public virtual void HideMe() { } }
举例,我们创建一个Button,创建此脚本并给与Button,由于继承了BasePanel,Awak函数执行后就获得了Button组件,我们用GetControl得到此UI的Button,并注册事件
public class But : BasePanel
{
private Button b;//创建Button
private void Start()
{
GetControl<Button>(this.name).onClick.AddListener(Click);
}
void Click()
{
Debug.Log("ButtonClick");
}
}
UIManager作为UI管理类,我们能够使用它对UI进行管理,呼出UI、关闭UI,操作UI等,我们将UI层划分为三层,分别为Bot(底层)、Mid(中层)、Top(高层)
//设置枚举,表示层级 public enum E_UI_Layer { Bot, Mid, Top, } //继承自BaseManager,单例模式随时调用 public class UIManager : BaseManager<UIManager> { //用字典存储给UI,方便管理 public Dictionary<string, BasePanel> panelDic = new Dictionary<string, BasePanel>(); private Transform bot; private Transform mid; private Transform top; //构造函数,用于初始化 public UIManager() { //利用ResMgr加载画布 GameObject obj = ResMgr.GetInstance().Load<GameObject>("UI/Canvas"); Transform canvas = obj.transform; GameObject.DontDestroyOnLoad(obj);//UI不随场景切换销毁 bot = canvas.Find("bot"); mit = canvas.Find("mid"); top = canvas.Find("top"); //UI系统没有EventSystem无法启动 obj = ResMgr.GetInstance().Load<GameObject>("UI/EventSystem"); GameObject.DontDestroyOnLoad(obj); } //将此Panel移动到Top层,并且callback委托可传可不传函数 public void ShowPanel<T>(string panelName, E_UI_Layer layer = E_UI_Layer.Top, UnityAction<T> callback = null) where T : BasePanel { //若字典中有注册过此面板 if (panelDic.ContainsKey(panelName)) { panelDic[panelName].ShowMe(); if (callback != null) callback(panelDic[panelName] as T); return; } //若字典中未注册过此面板,从Resource文件夹中加载 //使用了lambda表达式 ResMgr.GetInstance().LoadAsync<GameObject>("UI/"+panelName,(obj)=> { //把它作为Canvas的子对象 //并且设置它的相对位置 //找到父对象 Transform father = bot; switch (layer) { case E_UI_Layer.Mit: father = mit; break; case E_UI_Layer.Top: father = top; break; } //设置父对象 obj.transform.SetParent(father); //设置相对位置和大小 obj.transform.localPosition = Vector3.zero; obj.transform.localScale = Vector3.one; (obj.transform as RectTransform).offsetMax = Vector2.zero; (obj.transform as RectTransform).offsetMin = Vector2.zero; //得到预设体身上的脚本(继承自BasePanel) T panel = obj.GetComponent<T>(); //执行外面想要做的事情 if (callback != null) { callback(panel); } //在字典中添加此面板 panelDic.Add(panelName, panel); }); } public void HidePanel(string panelName) { if (panelDic[panelName]) { panelDic[panelName].HideMe(); //个人感觉没必要 panelDic[panelName].HideMe(); GameObject.Destroy(panelDic[panelName].gameObject); panelDic.Remove(panelName); } } }
音乐音效管理类,都是些基础,大家应该都能看懂吧
public class MusicMgr : BaseManager<MusicMgr> { private AudioSource bkMusic = null; private float bkValue = 1; private float soundValue = 1; private GameObject soundObj = null; private List<AudioSource> soundList = new List<AudioSource>(); public MusicMgr() { MonoMgr.GetInstance().AddUpdateListener(update); } private void update() { for (int i = soundList.Count - 1; i >= 0; i--) { if (!soundList[i].isPlaying) { GameObject.Destroy(soundList[i]); soundList.RemoveAt(i); } } } public void PlayBKMusic(string musciName) { if (bkMusic == null) { GameObject obj = new GameObject("BKMusic"); bkMusic = obj.AddComponent<AudioSource>(); } ResMgr.GetInstance().LoadAsync<AudioClip>("Music/bk" + musciName, (clip) => { bkMusic.clip = clip; bkMusic.loop = true; bkMusic.volume = bkValue; bkMusic.Play(); }); } public void ChangeBKValue(float v) { bkValue = v; if(bkMusic==null) return; bkMusic.volume = bkValue; } public void PauseBKMusic() { if(bkMusic==null) return; bkMusic.Pause(); } public void StopBkMusic() { if(bkMusic==null) return; bkMusic.Stop(); } public void PlaySound(string musicName, bool isLoop, UnityAction<AudioSource> callback = null) { if (soundObj == null) { soundObj = new GameObject(); soundObj.name = "Sounds"; } AudioSource source = soundObj.AddComponent<AudioSource>(); ResMgr.GetInstance().LoadAsync<AudioClip>("Music/Sounds/"+musicName, (clip) => { source.clip = clip; source.loop = isLoop; source.volume = soundValue; source.Play(); if (callback != null) callback(source); }); } public void ChangeSoundValue(float value) { soundValue = value; for (int i = 0; i < soundList.Count; i++) soundList[i].volume = value; } public void StopSound(AudioSource source) { if (soundList.Contains(source)) { soundList.Remove(source); source.Stop(); GameObject.Destroy(source); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。