# XXZFramework
**Repository Path**: mengtest/XXZFramework
## Basic Information
- **Project Name**: XXZFramework
- **Description**: 一个简单的开源的Unity前端框架
- **Primary Language**: C#
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 4
- **Created**: 2021-09-23
- **Last Updated**: 2021-09-23
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# XXZFramework
#### 介绍
一个简单的开源的Unity前端框架
#### 软件架构
目前框架一共十一个模块:
1.单例模式基类
2.对象池
3.事件中心处理池子
4.Mono公共生命周期
5.场景管理器
6.资源加载模块
7.结合资源管理器版的对象池
8.输入控制模块
9.音频管理模块
10.UGUI框架
11.热更新模块
#### 安装教程
1. Unity2019.4.2f1LTS
2. VsualStudio2019
#### 使用说明
1. 直接将这套框架包package导入到项目文件夹中即可
### 前序:
目前只是很粗糙的简单形式版本的模块,供大家学习或者参考,再在此基础上根据项目的需求进行不同程度的拓展
### 一.单例模式基类
#### 0.单例基类的介绍
##### 0.1什么是单例?
一句话说就是创建一个全局变量,它具有唯一职责(单一职责原则),统一负责需要它处理的需求。
##### 0.2为什么需要单例模式?
我们都知道,值类型跟引用类型都是有生命周期的(它们所被包裹的大括号"{}"),一旦离开了它们自身生命周期,那么就会被当成垃圾,等待下次GC的时候被回收。但是,因为某些原因,我们并不希望我们的这个类被系统自动回收,我们需要它与程序同生共死(从程序运行到程序结束),而且它在运行期间必须是唯一的(不能被new,不能重新对其赋值),所以单例就此诞生!
##### 0.3为什么需要单例基类?
首先,做过游戏的都知道,我们的游戏中是有许许多多的单例管理器(XXXManager),它们内部代码除了处理其自身业务代码外,其他的实例化方式高度相似,那么根据面向对象三大原则之一:继承,我们就可以把相似的代码抽离出来成为父类,子类继承了父类就会继承了父类的成员变量跟方法,这样,我们就可以减少编写重复代码的时间,提高开发效率。
##### 0.4单例模式的利与弊?
###### 单例模式的优点:
按我个人的使用来讲,有了单例模式,我可以避免重复重新创建与销毁类;我可以拥有一个唯一的全局的类,而且由于系统内存中只存在这一个实例类,所以节约了内存。
###### 单例模式的缺点
单例模式的缺点可能就是一直占用了内存却不能被清空销毁吧。
#### 1.单例模式基类
BaseSingleton.cs:
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
///
/// 不继承MonoBehaviour的单例基类
///
/// 类型
public class BaseSingleton where T : class, new()
{
/*
* 我们的单例,为了外部不直接使用进行一些错误的操作,
* 我们只会用成员属性提供一个get方法,而不提供set方法
* 因为如果我们设置为Public,那么外部是可以进行下面这句语句的:
* BaseSingleton._instance = new BaseSingleTon();
* 上面这句是错误的,因为我们知道单例因该是唯一性,不可实例化的,还有不能修改引用的
* 还有可能有的人会说,我们可以加个private BaseSingleTon(){}这样的私有构造函数,这样外部就无法实例化了
* 但是,我们同理也顶替了我们内部本有的公共无参构造函数了,而我们的泛型约束T:new()是需要一个公共无参构造函数的
* 如果我们的子类再去继承这个单例类,就会报错了。
*/
private static T _instance;
// 提供给外界访问的属性
public static T GetInstance
{
// 供外界获得到我们的单例实例
get
{
// 其实这一块如果是多线程开发的话是需要有双锁的,但是,我们Unity是单主线程(生命周期)。
if (_instance == null)
{
// 对单例对象进行赋值
_instance = new T();
}
// 返回我们的单例
return _instance;
}
}
}
~~~
#### 1.继承MonoBehaviour的单例基类
MonoBaseSingleton.cs:
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
///
/// 我们的游戏中可能会遇到一些需要继承MonoBehaviour的游戏单例基类
/// 那么首先抛出问题:
/// 1.继承MonoBehaviour的类是需要拖拽到场景中的游戏对象上才可以被运行的
/// 2.如果我们场景中有成千上百个游戏对象,我们怎么拖拽,一个一个拖拽太麻烦,我们能否使用代码帮助实现
/// 3.我们的单例物体都是一些空物体,名字为XXXManager,
/// 那么如果我们有成千上百个Manager我们是不是要在显示面板上创建成千上百个Manager的空物体?
/// 那么能否通过代码解决这个问题?
///
/// 类的类型
public class MonoBaseSingleton : MonoBehaviour where T : MonoBehaviour
{
// 我们唯一的单例实例
private static T _instance;
// 提供一个外部访问得到的属性
public static T GetInstance
{
// get方法,返回一个我们的单例
get
{
/*
* 如果为空我们就进行赋值
* 但是,我们继承了MonoBehaviour的类是无法直接new()出来的,Unity的内部帮助我们实现了对象的实例化操作(
* 如果想了解可以看看反射,Unity底层就是基于反射实现的)
*/
if (_instance == null)
{
// 我们先手动实例化出一个物体,在把我们的游戏物体生成到我们的场景中
GameObject obj = new GameObject();
// 再把我们生成出来的游戏对象的名字进行修改成我们需要的名字
obj.name = typeof(T).ToString() + "Manager";
// 把我们的XXXManager物体添加上对应的管理器单例类
_instance = obj.AddComponent();
// 我们的单例管理器是不需要过场景之后就被移除的,所以我们要加上这句代码保护我们的单例管理器
DontDestroyOnLoad(obj);
}
return _instance;
}
}
}
~~~
### 二.对象池(缓存池)
#### 0.对象池的介绍
##### 0.1什么是对象池?
一句话说就是管理我们场景不需要(暂时不需要激活)的物体,在适当的时机激活或者失活它们。
##### 0.2为什么需要有对象池?
游戏中需要频繁的用到许许多多的物体,举个例子,我们常见的传统MMOARPG中一般野外场景是在一片区域内生成许许多多的怪物,玩家接取到击杀小怪的任务,然后去击杀,如果按我们最简单的方式,需要生成小怪的时候,我们就GameObject.Instantiate()生成,销毁的时候我们就直接DestroyImmediate(),那么这时候就会给我们的GC造成压力,我们会频繁GC回收我们实例化又死亡的怪物的内存,这时候就会造成一个情况就是玩家卡顿!这是很影响玩家体验感的一种巨大的问题!所以,对象池就应运而生了。我们会在场景加载完毕的时候,预先加载完且实例化出部分游戏对象,在玩家击杀了游戏对象的时候我们只是对其进行失活操作,而不直接销毁。
##### 0.3对象池的优与弊
###### 对象池的优点
减少频繁的GC,减少玩家卡顿感,而且对象池这种东西在开发中很常见也十分的重要,对象池的变种十分的多,但是最核心的思想还是没变的。
###### 对象池的缺点
对象池的话,我们需要预先实例化一部分怪物,这需要占用着我们的一部分内存而且不被释放。
#### 1.简单的对象池模板
前期准备:
1.创建一个Resources文件夹
2.创建一个脚本,脚本名字为ObjectPool
3.在显示面板上创建两个任意的物体(创建两个Cube,一个命名就为"Cube",另一个随便命名,两个不重名就行),然后拖拽进Resources文件夹中做成预制体
BaseSingleton.cs:
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
public class BaseSingleton where T : class, new()
{
private static T _instance;
public static T GetInstance
{
get
{
if (_instance == null)
{
_instance = new T();
}
return _instance;
}
}
}
~~~
ObjectPool.cs:
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
using System.Collections.Generic;
///
/// 对象池模块
///
public class ObjectPool : BaseSingleton
{
// 创建我们的对象池池子
// 参数 1 我们存储对象的容器名字,参数 2 我们真正用于存储对象的容器
public Dictionary> objPool = new Dictionary>();
///
/// 使用完之后返还对象池子里面
///
/// 需要返还的游戏对象
public void PushObj(GameObject obj)
{
// 第一步.先把对象失活了,这条语句十分重要,不然我们的游戏对象即使回收进了对象池子里面,一样还是会存在于场景之中。
obj.SetActive(false);
// 查找对象池子里面是否存在缓存我们对象容器
if (objPool.ContainsKey(obj.name))
{
// 把使用完的对象添加进容器池子里面(返还对象池子)
objPool[obj.name].Add(obj);
}
else
{
// 如果我们的对象池子里面没有存在可以用于缓存游戏对象的容器,那么就需要创建一个,再把要返还的对象存进去
objPool.Add(obj.name, new List() { obj });
}
}
///
/// 得到我们的对象池子中缓存的对象
/// 1.如果不存在对象的容器就创建一个容器并且生成我们预先设好的数量
/// 2.如果存在对象的容器但是容器内部缓存的游戏对象数量小于等于0,那么一样生成我们预先设好的数量
///
/// 生成游戏物体后的名字
/// 生成游戏物体的路径(Resources文件夹下游戏物体的路径)
///
public GameObject GetObj(string objName, string objPath)
{
// obj是用于我们获取到生成后的游戏物体,方便我们对它们进行改名字,修改位置等等
GameObject obj = null;
// 查看对象池子中是否存在缓存我们游戏对象的容器
if (!objPool.ContainsKey(objName))
{
// 如果不存在就生成一个新的容器
objPool.Add(objName, new List());
}
// 如果容器的数量小于等于0的时候,我们就预先初始化一小部分游戏对象
if (objPool[objName].Count <= 0)
{
for (int i = 0; i < 5; i++)
{
// 生成游戏对象
obj = GameObject.Instantiate(Resources.Load(objPath));
/*
* 修改游戏对象的名字,
* 因为新生成的游戏对象会在显示面板上会加上(clone),这很不方便我们查阅我们的对象。
* 所以我们需要对生成的游戏对象改变其名字为我们想要的名字方便我们管理
*/
obj.name = objName;
// 这句语句十分重要,我们预先生成的物体是不能暴露到场景中的
obj.SetActive(false);
// 再把新生成的游戏物体添加进我们的容器中
objPool[objName].Add(obj);
}
}
// 把我们容器的第一个物体给予出去
obj = objPool[objName][0];
// 再移出我们给出去的游戏物体,RemoveAt会帮助我们把空出来的位置进行重新排序
objPool[objName].RemoveAt(0);
// 在把移出去的游戏对象重新激活
obj.SetActive(true);
// 其实可以不需要返回这个物体的,但是为了方便后期项目可能需要对我们的游戏物体进行操作
// 所以我们就提供返回游戏物体,方便外部修改游戏物体的游戏参数
return obj;
}
///
/// 清空对象池
///
public void Clear()
{
objPool.Clear();
}
}
~~~
#### 2.对象池第一次优化
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
using System.Collections.Generic;
/* 优化方向:
* 1.我们可以明显的看见,我们创建的物体一直在显示面板上面,没有很好的整理的,
* 那么我们需要优化的第一个方向就是,如何像文件夹一样管 *理好我们的对象池,
* 例如:我们可以在显示面板上创建一个Pool空物体,然后让它们管理我们实例化出来的Cube和Sphere物体,
* 这样是不是就很*方便我们查看哪些是还在对象池里面的,哪些是出了对象池的,也把抽象化的对象池子,具体到了我们的显示面板上。
*/
///
/// 对象池模块
///
public class ObjectPool : BaseSingleton
{
// 创建我们的对象池池子
// 参数 1 我们存储对象的容器名字,参数 2 我们真正用于存储对象的容器
public Dictionary> objPool = new Dictionary>();
// 我们需要挂载的根节点
private GameObject poolRoot = null;
///
/// 使用完之后返还对象池子里面
///
/// 需要返还的游戏对象
public void PushObj(GameObject obj)
{
// 第一步.先把对象失活了,这条语句十分重要,不然我们的游戏对象即使回收进了对象池子里面,一样还是会存在于场景之中。
obj.SetActive(false);
// 判断我们显示面板上是否有我们用于管理对象池的空物体(Pool)
if(poolRoot == null)
poolRoot = new GameObject("Pool");
// 查找对象池子里面是否存在缓存我们对象容器
if (objPool.ContainsKey(obj.name))
{
// 把使用完的对象添加进容器池子里面(返还对象池子)
objPool[obj.name].Add(obj);
}
else
{
// 如果我们的对象池子里面没有存在可以用于缓存游戏对象的容器,那么就需要创建一个,再把要返还的对象存进去
objPool.Add(obj.name, new List() { obj });
}
// 在显示面板上,把我们的物体放进根节点上
obj.transform.parent = poolRoot.transform;
}
///
/// 得到我们的对象池子中缓存的对象
/// 1.如果不存在对象的容器就创建一个容器并且生成我们预先设好的数量
/// 2.如果存在对象的容器但是容器内部缓存的游戏对象数量小于等于0,那么一样生成我们预先设好的数量
///
/// 生成游戏物体后的名字
/// 生成游戏物体的路径(Resources文件夹下游戏物体的路径)
///
public GameObject GetObj(string objName, string objPath)
{
// obj是用于我们获取到生成后的游戏物体,方便我们对它们进行改名字,修改位置等等
GameObject obj = null;
// 查看对象池子中是否存在缓存我们游戏对象的容器
if (!objPool.ContainsKey(objName))
{
// 如果不存在就生成一个新的容器
objPool.Add(objName, new List());
}
// 如果容器的数量小于等于0的时候,我们就预先初始化一小部分游戏对象
if (objPool[objName].Count <= 0)
{
for (int i = 0; i < 5; i++)
{
// 生成游戏对象
obj = GameObject.Instantiate(Resources.Load(objPath));
/*
* 修改游戏对象的名字,
* 因为新生成的游戏对象会在显示面板上会加上(clone),这很不方便我们查阅我们的对象。
* 所以我们需要对生成的游戏对象改变其名字为我们想要的名字方便我们管理
*/
obj.name = objName;
// 这句语句十分重要,我们预先生成的物体是不能暴露到场景中的
obj.SetActive(false);
// 然后把我们生成的物体放进我们显示面板上Pool空物体中方便管理
// 放进去之前,我们需要判断一下我们的Pool空物体是否已经存在
if (poolRoot == null)
poolRoot = new GameObject("Pool");
obj.transform.parent = poolRoot.transform;
// 再把新生成的游戏物体添加进我们的容器中
objPool[objName].Add(obj);
}
}
// 把我们容器的第一个物体给予出去
obj = objPool[objName][0];
// 再移出我们给出去的游戏物体,RemoveAt会帮助我们把空出来的位置进行重新排序
objPool[objName].RemoveAt(0);
// 再继续把移出去的游戏对象重新激活
obj.SetActive(true);
// 让它在显示面板上断开与父物体的联系
obj.transform.parent = null;
// 其实可以不需要返回这个物体的,但是为了方便后期项目可能需要对我们的游戏物体进行操作
// 所以我们就提供返回游戏物体,方便外部修改游戏物体的游戏参数
return obj;
}
///
/// 用于过场景之后清空对象池
///
public void Clear()
{
// 清空对象池内部的键值
objPool.Clear();
// 把显示面板上的对象池的子物体全部清空
poolRoot = null;
}
}
~~~
#### 3.对象池第二次优化
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
using System.Collections.Generic;
/* 优化方向:
*2.我们的对象池是用List去装载我们的游戏物体的,这其实有点违背了我们面向对象的思想,现在我们需要把List换成一个类PoolData,去优化 *我们的对象池。
*/
///
/// 对象池中的容器模块
///
public class PoolData
{
/*
* 该类中我们只需要考虑下面的问题:
* 我们已经有了GetObj()与Push()方法,但是我们现在需要把List换成我们的PoolData,更加符合我们面向对象思想,
* 那么我们就需要把这两个方法内部具体的实现抽离出来,成为PoolData中供对象池调用的行为。
*/
// 这是我们的用来存储缓存对象的容器
private List objContainer = null;
///
/// 我们的构造函数,用于
///
public PoolData()
{
if(objContainer == null)
objContainer = new List();
}
public void PushObjInContainer(GameObject obj)
{
// 放回对象池子的容器
// 第一步:把对象先失活,让其从场景中消失
obj.SetActive(false);
// 第二步:把对象添加进对象容器中
objContainer.Add(obj);
}
public GameObject GetObjFromContainer()
{
// 移出对象池子的容器
// 第一步:我们需要先创建一个GameObject对象用来存储我们移出的物体
GameObject obj = objContainer[0];
// 第二步:把我们的对象从显示面板上激活出来
obj.SetActive(true);
// 第三步:把我们的对象从我们的容器中移出
objContainer.RemoveAt(0);
// 第四步:返回出去供外部使用
return obj;
}
///
/// 用于返回我们容器内部缓存的对象数量
///
/// 容器内部的对象数量
public int ContainerCount()
{
return objContainer.Count;
}
}
///
/// 对象池模块
///
public class ObjectPool : BaseSingleton
{
// 创建我们的对象池池子
// 参数 1 我们存储对象的容器名字,参数 2 我们真正用于存储对象的容器
public Dictionary objPool = new Dictionary();
// 我们需要挂载的根节点
private GameObject poolRoot = null;
///
/// 使用完之后返还对象池子里面
///
/// 需要返还的游戏对象
public void PushObj(GameObject obj)
{
// 判断我们显示面板上是否有我们用于管理对象池的空物体(Pool)
if(poolRoot == null)
poolRoot = new GameObject("Pool");
// 查找对象池子里面是否存在缓存我们对象容器
if (objPool.ContainsKey(obj.name))
{
// 把使用完的对象添加进容器池子里面(返还对象池子)
objPool[obj.name].PushObjInContainer(obj);
}
else
{
// 如果我们的对象池子里面没有存在可以用于缓存游戏对象的容器,那么就需要创建一个,再把要返还的对象存进去
objPool[obj.name] = new PoolData();
objPool[obj.name].PushObjInContainer(obj);
}
// 在显示面板上,把我们的物体放进根节点上
obj.transform.parent = poolRoot.transform;
}
///
/// 得到我们的对象池子中缓存的对象
/// 1.如果不存在对象的容器就创建一个容器并且生成我们预先设好的数量
/// 2.如果存在对象的容器但是容器内部缓存的游戏对象数量小于等于0,那么一样生成我们预先设好的数量
///
/// 生成游戏物体后的名字
/// 生成游戏物体的路径(Resources文件夹下游戏物体的路径)
///
public GameObject GetObj(string objName, string objPath)
{
// obj是用于我们获取到生成后的游戏物体,方便我们对它们进行改名字,修改位置等等
GameObject obj = null;
// 查看对象池子中是否存在缓存我们游戏对象的容器
if (!objPool.ContainsKey(objName))
{
// 如果不存在就生成一个新的容器
objPool.Add(objName, new PoolData());
// 然后因为容器是空的,所以我们预先生成一部分游戏对象,再把它们都存储进我们的容器中
for (int i = 0; i < 5; i++)
{
obj = GameObject.Instantiate(Resources.Load(objPath));
if (poolRoot == null)
poolRoot = new GameObject("Pool");
obj.name = objName;
obj.transform.parent = poolRoot.transform;
objPool[objName].PushObjInContainer(obj);
}
}
// 如果容器内部缓存对象的数量小于等于0的时候,我们就继续预先实例化一小部分游戏对象
if (objPool[objName].ContainerCount() <= 0)
{
for (int i = 0; i < 5; i++)
{
// 生成游戏对象
obj = GameObject.Instantiate(Resources.Load(objPath));
if (poolRoot == null)
poolRoot = new GameObject("Pool");
obj.name = objName;
obj.transform.parent = poolRoot.transform;
// 再把新生成的游戏物体添加进我们的容器中
objPool[objName].PushObjInContainer(obj);
}
}
obj = objPool[objName].GetObjFromContainer();
// 让它在显示面板上断开与父物体的联系
obj.transform.parent = null;
return obj;
}
///
/// 用于过场景之后清空对象池
///
public void Clear()
{
// 清空对象池内部的键值
objPool.Clear();
// 把显示面板上的对象池的子物体全部清空
poolRoot = null;
}
}
~~~
#### 4.对象池第三次优化
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Events;
/* 优化方向:
* 1.我们可以明显的看见,我们创建的物体一直在显示面板上面,没有很好的整理的,那么我们需要优化的第一个方向就是,如何像文件夹一样管理好我们的对象池,
* 例如:我们可以在显示面板上创建一个Pool空物体,然后再在Pool上创建对应对象名字的空物体,一个Cube空物体,一个Sphere空物体,
* 让它们管理我们实例化出来的Cube和Sphere物体,这样就可以很方便的,
* 让我们查看哪些是还在对象池里面的,哪些是出了对象池的,也把抽象化的对象池子,具体到了我们的显示面板上。
* 2.我们的对象池是用List去装载我们的游戏物体的,这其实有点违背了我们面向对象的思想,现在我们需要把List换成一个类PoolData,去优化我们的对象池。
*/
///
/// 对象池中的容器模块
///
public class PoolData
{
/*
* 该类中我们只需要考虑下面的问题:
* 我们已经有了GetObj()与Push()方法,但是我们现在需要把List换成我们的PoolData,更加符合我们面向对象思想,
* 那么我们就需要把这两个方法内部具体的实现抽离出来,成为PoolData中供对象池调用的行为。
*/
// 这是我们的用来存储缓存对象的容器
private List objContainer = null;
// 这是我们用于管理我们容器缓存对象的父节点
private GameObject fatherRoot = null;
///
/// 我们的构造函数,用于初始化我们的成员
///
/// /// 我们容器空物体放置的节点
/// /// 生成游戏物体后的用于存储的容器名字
public PoolData(GameObject poolRoot, string objName)
{
if(objContainer == null)
objContainer = new List();
if (fatherRoot == null)
{
fatherRoot = new GameObject(objName);
fatherRoot.transform.parent = poolRoot.transform;
}
}
public void PushObjInContainer(GameObject obj)
{
// 放回对象池子的容器
// 第一步:把对象先失活,让其从场景中消失
obj.SetActive(false);
// 第二步:把对象添加进对象容器中
objContainer.Add(obj);
// 第三步:把在显示面板上已经失活的对象放进我们的管理物体中
obj.transform.parent = fatherRoot.transform;
}
public GameObject GetObjFromContainer()
{
// 移出对象池子的容器
// 第一步:我们需要先创建一个GameObject对象用来存储我们移出的物体
GameObject obj = objContainer[0];
// 第二步:把我们的对象从显示面板上激活出来
obj.SetActive(true);
// 第三步:把我们的对象从我们的容器中移出
objContainer.RemoveAt(0);
// 第四步:把我们的对象从我们的父物体上断开联系
obj.transform.parent = null;
// 第五步:返回出去供外部使用
return obj;
}
///
/// 用于返回我们容器内部缓存的对象数量
///
/// 容器内部的对象数量
public int ContainerCount()
{
return objContainer.Count;
}
}
///
/// 对象池模块
///
public class ObjectPool : BaseSingleton
{
// 创建我们的对象池池子
// 参数 1 我们存储对象的容器名字,参数 2 我们真正用于存储对象的容器
public Dictionary objPool = new Dictionary();
// 我们需要挂载的根节点
private GameObject poolRoot = null;
///
/// 使用完之后返还对象池子里面
///
/// 需要返还的游戏对象
public void PushObj(GameObject obj)
{
// 判断我们显示面板上是否有我们用于管理对象池的空物体(Pool)
if(poolRoot == null)
poolRoot = new GameObject("Pool");
// 查找对象池子里面是否存在缓存我们对象容器
if (objPool.ContainsKey(obj.name))
{
// 把使用完的对象添加进容器池子里面(返还对象池子)
objPool[obj.name].PushObjInContainer(obj);
}
else
{
// 如果我们的对象池子里面没有存在可以用于缓存游戏对象的容器,那么就需要创建一个,再把要返还的对象存进去
objPool[obj.name] = new PoolData(poolRoot, obj.name);
objPool[obj.name].PushObjInContainer(obj);
}
}
///
/// 得到我们的对象池子中缓存的对象
/// 1.如果不存在对象的容器就创建一个容器并且生成我们预先设好的数量
/// 2.如果存在对象的容器但是容器内部缓存的游戏对象数量小于等于0,那么一样生成我们预先设好的数量
///
/// 生成游戏物体后的名字
/// 生成游戏物体的路径(Resources文件夹下游戏物体的路径)
/// 因为我们已经没有返回值了,如果需要对我们容器中的物体进行操作的话,我们就需要通过委托的函数把
/// 物体传出去
public void GetObj(string objName, string objPath, UnityAction callback)
{
// 判断我们显示面板上是否有我们用于管理对象池的空物体(Pool)
if (poolRoot == null)
poolRoot = new GameObject("Pool");
// 查看对象池子中是否存在缓存我们游戏对象的容器
if(objPool.ContainsKey(objName) && objPool[objName].ContainerCount() > 0)
callback(objPool[objName].GetObjFromContainer());
// 查看对象池中存在的对象的数量是否等于0
else if (objPool.ContainsKey(objName) && objPool[objName].ContainerCount() == 0)
{
// 首先,生成游戏对象,再把新生成的游戏物体添加进我们的容器中
ResourcesMgr.GetInstance.LoadAysncResources(objPath, (obj) => {
// 更改名字
obj.name = objName;
callback(obj);
});
}
// 如果不存在就生成一个新的容器
else if (!objPool.ContainsKey(objName))
{
objPool.Add(objName, new PoolData(poolRoot, objName));
ResourcesMgr.GetInstance.LoadAysncResources(objPath, (obj) => {
// 更改名字
obj.name = objName;
callback(obj);
});
}
}
///
/// 用于过场景之后清空对象池
///
public void Clear()
{
// 清空对象池内部的键值
objPool.Clear();
// 把显示面板上的对象池的子物体全部清空
poolRoot = null;
}
}
~~~
#### 5.对象池还可以怎么优化
1.UnityAction配合Resources的异步加载资源(后面介绍完资源管理器后会继续优化外面现有的对象池)。
2.我们的对象池容器没有设置上限,所以我们的对象池是无限的生成且存储游戏物体,没有限制的话,是十分耗费我们的内存的。
### 三.事件中心处理池(观察者模式)
#### 0.事件中心处理池的介绍
##### 0.1事件中心处理池
一句话说就是管理我们的所有对象中对于同一个事件的发生需要执行不同的反馈。
##### 0.2为什么需要有事件中心处理池
解耦合、解决复杂的代码逻辑书写、易拓展高效率、方便后期项目大了统一管理要处理的事件。
##### 0.3事件中心处理池的优与弊
###### 事件中心处理池的优点:
我们可以不再使用transform.find()找到我们需要的物体,然后getcompent<>()得到其身上挂载的组件,然后再去.出来我们需要的方法。如果游戏场景场景中,我们的BOSS死了,那么我们如果有多少个脚本需要执行BOSS死亡后的方法,就需要重复上面那一句话里面的所用到的API很多次,这首先是有非常非常大的一个耦合性,不同的脚本之间相互关联了起来,一旦某个脚本丢失引用,将对工程直接造成致命的打击。而使用了我们的事件中心池子就是一种解耦合的方式,我们的物体不需要再去直接的执行某个函数,需要执行的函数统统都可以添加进我们的事件中心池子之中,建立监听,事件中心池子则作为我们的观察者,一旦某个事件触发了,我们就给监听了这个事件的函数配发事件。
###### 事件中心处理池的缺点:
我们都知道任何被static修饰的变量都是存储在静态内存区的,而且静态变量具有唯一性且与程序同生共死,会在程序一直运行的时候占用我们的内存空间,但是,世界上是没有一个完美的银弹的,想要高效率就可能要牺牲某个方面,其实是个单例都会有这个问题,主要的对于事件中心的来说,事件中心池子里面存储的Value是一个委托函数,委托函数在C/C++里面像函数指针,因为C#禁止对指针的操作,才出来了这个玩意,在C++里面相当于callback。我的个人理解中,委托函数存储了建立监听的对象的引用,一旦我们忘记或者没有清除我们的引用(例如:我们的主角死了,那么他就需要移除对怪物死亡的监听了),就会造成严重性的内存泄漏问题,其次后面我们对象池子越来越复杂的时候,会出现装箱拆箱的操作(例如:我们怪物死了,那是小怪死了?精英怪死了?大BOSS死了?我们如何区别呢?)。
#### 1.事件中心处理池
EventPool.cs:
~~~C#
/****************************************************
文件:$FILE_BASE$.$FILE_EXT$
作者:小小泽
邮箱: 1245615197@qq.com
日期:$DATE$ $HOUR$:$MINUTE$
功能:$end$
*****************************************************/
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Events;
///
/// 事件中心处理池子
///
public class EventPool : BaseSingleton
{
// 用于处理事件派发的事件中心池子
private Dictionary> eventPool = new Dictionary>();
///
/// 在我们的事件中心池子中建立监听
///
/// 监听的事件
/// 配发的方法(我们收到事件触发时候需要处理的函数)
public void AddEventListener(string name, UnityAction