文章目录
- XYFrame介绍
- 优点
- 获取框架源码
- 引入的第三方插件
- 作者信息
- 技术交流群反馈企鹅裙
- 画饼
- 使用文档
- 更新
- 封装自己的框架
XYFrame介绍
XYFrame是个人整理的自用unity小框架,非常适合中小项目的开发。与其说是框架不如说是工具集,其实就是把一些常用的unity管理器进行封装。
写这个框架的原因就是不想一直造轮子,游戏开发过程中发现很多功能都是通用的,比如单例、对象池、音频、存档等等。
优点
- 框架源码完全开源免费使用
- 轻量,主要就是对一些常用功能进行封装,避免重复造轮子,基本没有特别花里胡哨的操作
- 维护稳定,因为这是自用框架,自己平时会用,如果有任何问题会第一时间更新
- 实战项目,后续我会在不同的项目中用它,并将实战经验分享出来
- 代码耦合性极低,设计之初,框架每个模块之间尽量做到低关联,每个模块都可以选择单独提取使用,基本不用修改或者少修改即可使用
- 自带中文注释,易于使用理解
- 源码内自带非常完整常用的测试用例可以使用和演示
获取框架源码
【gitee地址】https://gitee.com/unity_data/xyframe
引入的第三方插件
- DOTween
作者信息
姓名:向宇
博客:https://xiangyu.blog.csdn.net/
技术交流群反馈企鹅裙
826534924
画饼
- 封装存储事件(
未完成
) - 封装有限状态机(
未完成
) - 用0GC更高性能的UniTask替换协程(
未完成
) - BUFF系统(
未完成
) - 对话系统(
未完成
) - 任务系统(
未完成
) - 背包系统(
未完成
) - 热更(
未完成
) - 本地化多语言(
未完成
)
使用文档
导入
你可以选择直接下载整个文件夹,用unity打开
也可以选择下载导入unity框架包
文件目录
Test目录
:保存一些非常完整常用的测试用例可以使用和演示,供新手快速入门使用,当然你完全可以删除它,不会影响框架的使用。可以打开一个空场景,可以通过菜单工具按钮进行测试
Frame目录
:框架的主体代码目录,为了方便后期框架的更新,我并不推荐你去修改它们,当然除非你知道自己在干什么。框架主体代码目录具体解释见下文
启动
启动场景在0.Init里放置一个GameApp脚本即可,运行项目会自动跳转到1.Start场景,从1.Start场景开始才是真正的游戏场景,0.Init只是进行初始化操作,不带任何的界面操作
GameApp游戏入口脚本,你可以像我一样一次性加载全部管理器,也可以按需加载,后续调用就使用GameApp.MonoManager.xxxx
方式即可
using UnityEngine;
using XYFrame;
/// <summary>
/// 游戏入口脚本
/// </summary>
public class GameApp : SingletonMono<GameApp>
{
//初始化管理器
[HideInInspector] public MonoManager MonoManager = null;
[HideInInspector] public EventManager EventManager = null;
[HideInInspector] public ResourcesManager ResourcesManager = null;
[HideInInspector] public ObjectPoolsManager ObjectPoolsManager = null;
[HideInInspector] public LoadSceneManager LoadSceneManager = null;
[HideInInspector] public UIManager UIManager = null;
[HideInInspector] public AudioManager AudioManager = null;
[HideInInspector] public SaveManager SaveManager = null;
//游戏入口
void Awake()
{
//全局单例赋值
MonoManager = MonoManager.Instance;
EventManager = EventManager.Instance;
ResourcesManager = ResourcesManager.Instance;
ObjectPoolsManager = ObjectPoolsManager.Instance;
LoadSceneManager = LoadSceneManager.Instance;
UIManager = UIManager.Instance;
AudioManager = AudioManager.Instance;
SaveManager = SaveManager.Instance;
//跳转下一个场景
LoadSceneManager.LoadNextScene();
}
}
ps
:当然我这里只是一个推荐加载方式,如果你不喜欢,也可以选择使用自己的方式,XYFrame是完全自由的。
1、单例模式
不继承MonoBehaviour的单例模式基类
public class TestModel : Singleton<TestModel>
继承MonoBehaviour的单例模式基类
public class TestUI : SingletonMono<TestUI>
在OnDestroy方法中访问单例对象,如果直接在在OnDestroy方法中访问单例对象,每次运行结束时会报错
在OnDestroy调用时,先判断IsExisted是否为true
private void OnDestroy() {
if(TestUI.IsExisted) TestUI.Instance.Log();
}
2、Mono管理器
在不继承MonoBehaviour情况下调用MonoBehaviour里的功能,比如协程、Update、LateUpdate、FixedUpdate
# 开启携程
coroutine = MonoManager.Instance.StartCoroutine(MyCoroutine());
# 停止协程
MonoManager.Instance.StopCoroutine(coroutine);
# 停止所有协程
MonoManager.Instance.AddLateUpdateListener(OnLateUpdate);
# 开启Update
MonoManager.Instance.AddUpdateListener(OnUpdate);
# 停止Update
MonoManager.Instance.RemoveUpdateListener(OnUpdate);
# 停止所有Update
MonoManager.Instance.RemoveAllUpdateListeners();
# 开启FixedUpdate
MonoManager.Instance.AddFixedUpdateListener(OnFixedUpdate);
# 停止FixedUpdate
MonoManager.Instance.RemoveFixedUpdateListener(OnFixedUpdate);
# 停止所有FixedUpdate
MonoManager.Instance.RemoveAllFixedUpdateListeners();
# 开启LateUpdate
MonoManager.Instance.AddLateUpdateListener(OnLateUpdate);
# 停止LateUpdate
MonoManager.Instance.RemoveLateUpdateListener(OnLateUpdate);
# 停止所有LateUpdate
MonoManager.Instance.RemoveAllLateUpdateListeners();
3、事件管理系统
这里我添加最多支持添加4个参数的事件,一般都够了,如果觉得还是不够,可以模仿我的方式继续添加即可
# 事件监听
EventManager.Instance.AddEventListener(EventNameEnum.Log, Log);
EventManager.Instance.AddEventListener<int>(EventNameEnum.AddHealth, AddHealth);
# 触发带1个参数事件
EventManager.Instance.Dispatch<int>(EventNameEnum.AddHealth, 1);
# 移除Player带1个参数事件监听
EventManager.Instance.RemoveEventListener<int>(EventNameEnum.AddHealth, go.GetComponent<Player>().AddHealth);
# 移除整个带1个参数事件
EventManager.Instance.RemoveEvent<int>(EventNameEnum.AddHealth);
4、工具类
封装unity协程工具,避免 GC(垃圾回收)
提前new好协程所需要的WaitForEndOfFrame、WaitForFixedUpdate、WaitForFrameStruct类的对象,避免GC。
# 使用
IEnumerator DelayedAction()
{
yield return CoroutineTool.WaitForEndOfFrame(); // 等待到当前帧的结束
yield return CoroutineTool.WaitForFixedUpdate(); // 等待到下一个固定更新
yield return CoroutineTool.WaitForSeconds(2.0f); // 等待2秒(游戏时间)
yield return CoroutineTool.WaitForSecondsRealtime(2.0f); // 等待2秒(现实时间,不受游戏时间缩放影响)
yield return CoroutineTool.WaitForFrame(); // 等待一帧
yield return CoroutineTool.WaitForFrame(3); // 等待3帧
}
封装Stopwatch工具类,计算执行一段代码所用的时间
# 测试调用,打印执行一段代码10000次需要多少秒
public class ToolsTest : MonoBehaviour {
private void Start() {
StopwatchUtility.PrintTime(Execute, 10000);
}
void Execute(){
"测试字符".GetType();
}
}
5、Resources资源管理器
# 异步加载资源
ResourcesManager.Instance.LoadAsync<GameObject>("Prefabs/Cube", (obj)=>{
Instantiate(obj);
});
# 同步加载封装虽然和unity自带的方法是一样的,但是好处是自己封装可以统一管理和进行自定义注释
GameObject prefab = ResourcesManager.Instance.Load<GameObject>("Prefabs/Cube");
# 同步加载Resources文件夹中指定类型的资源。
GameObject[] gos = ResourcesManager.Instance.LoadAll<GameObject>("Prefabs");
for (int i = 0; i < gos.Length; i++)
{
Instantiate(gos[i]);
}
# 异步卸载所有用Resources方式加载到内存中且当前没有被任何地方使用的资源。
ResourcesManager.Instance.UnloadUnusedAssets(() =>
{
Debug.Log("异步卸载所有资源成功");
});
6、对象池管理器
# 生成游戏对象
GameObject go = ObjectPoolsManager.Instance.Spawn(prefab, transform.position, Quaternion.identity, transform);
# 5秒后回收
ObjectPoolsManager.Instance.Despawn(go, 5f);
# 生成和回收游戏对象时自动调用的方法
public class Cube : MonoBehaviour {
//生成游戏对象时自动调用
void OnSpawn(){
Debug.Log("生成游戏对象");
}
//回收游戏对象时自动调用
void OnDespawn(){
Debug.Log("回收游戏对象");
//重置参数
transform.position = Vector3.zero;
GetComponent<Rigidbody>().velocity = Vector3.zero;
}
}
7、场景管理器
# 同步加载当前场景、下一个场景、上一个场景
LoadSceneManager.Instance.LoadActiveScene();
LoadSceneManager.Instance.LoadNextScene();
LoadSceneManager.Instance.LoadPreviousScene();
# 异步切换场景
LoadSceneManager.Instance.LoadSceneAsync(1);
LoadSceneManager.Instance.LoadSceneAsync("Scene1");
# 异步加载场景获取加载进度条
public class SceneTest : MonoBehaviour
{
private void Start()
{
LoadSceneManager.Instance.LoadSceneAsync(1, Loading, Completed);
}
void Loading(float progress)
{
Debug.Log($"加载进度是{progress*100}%");
}
void Completed(AsyncOperation asyncOperation)
{
Debug.Log("场景加载完成");
}
}
8、序列化字典,场景,vector,color,Quaternion
可序列化字典简单的使用
和普通字典的使用方法一样,只需要把Dictionary换成SerializableDictionary,并且使用时不需要先实例化了
public class SerializedTest : MonoBehaviour {
//可序列化字典
public SerializableDictionary<string, string> serializableDictionary;
private void Start() {
//添加键值对
serializableDictionary.Add("name1", "小明");
serializableDictionary["name2"] = "小红";
//检查是否包含指定的键
string key = "name1";
Debug.Log($"是否包含键为{key}:{serializableDictionary.ContainsKey(key)}");
//获取指定键的值
Debug.Log($"获取键为name2的值:{serializableDictionary["name2"]}");
//移除键值对
serializableDictionary.Remove("name1");
//获取键值对数量
Debug.Log($"获取键值对数量:{serializableDictionary.Count}");
//清空字典
//serializableDictionary.Clear();
}
}
序列化场景
代码通过使用SceneField类和SceneFieldPropertyDrawer属性绘制器,开发者可以在自定义的脚本中方便地引用和管理场景对象,并在Inspector面板中进行编辑和选择操作。这对于需要频繁切换场景或者处理多个场景的情况非常有用。
public class SerializedTest : MonoBehaviour {
//可序列化场景
public SerializableScene scene1;
public SerializableScene scene2;
private void Start() {
SceneManager.LoadScene(scene1);//跳转场景
}
}
序列化vector
//普通Vector
public Vector3 vector3;
public Vector2 vector2;
//可序列化Vector
public SerializedVector3 serializedVector3;
public SerializedVector2 serializedVector2;
//Vector3和SerializedVector3可以随意转换
Debug.Log(vector3);
vector3 = serializedVector3;
Debug.Log(vector3);
serializedVector3 = Vector3.zero;
Debug.Log(serializedVector3);
序列化color
//颜色
public Color color;
//可序列化的颜色
public SerializedColor serializedColor;
color = new Color(1.0f, 0.5f, 0.0f, 1.0f);
Debug.Log(color);
serializedColor = new SerializedColor(0.0f, 1.0f, 0.0f, 1.0f);
Debug.Log(serializedColor);
color = serializedColor;
Debug.Log(color);
序列化旋转Quaternion
//旋转
public Quaternion quaternion;
//可序列化旋转
public SerializedQuaternion serializedQuaternion;
// 定义一个欧拉角(绕X、Y、Z轴的旋转)
Vector3 eulerAngles = new Vector3(30f, 45f, 60f);
// 将欧拉角转换为四元数
quaternion = Quaternion.Euler(eulerAngles);
// 将四元数转换为欧拉角
eulerAngles = quaternion.eulerAngles;
Debug.Log(eulerAngles);
//直接赋值
serializedQuaternion = Quaternion.Euler(eulerAngles);
serializedQuaternion = quaternion;
//互相转换
serializedQuaternion = quaternion.ConvertToSQuaternion();
quaternion = serializedQuaternion.ConvertToQuaternion();
9、UI管理器/UI框架
调用代码会自动创建画布
# 显示WelcomeUI面板,创建的脚本名字记得跟预制体物体名字一致
UIManager.Instance.ShowUI<WelcomeUI>("WelcomeUI");
# 提示框
UIManager.Instance.ShowTips("成功!", Color.green);
我们可以在UI初始化时,提前获取所有控件信息并存储在字典里,并且给特殊控件绑定好各种事件,比如按钮点击事件、单选框或者多选框值改变事件、滑动条值改变事件
public class SettingsUI : UIBase
{
private void Start()
{
SetSliderValue();
}
#region 按钮点击事件
string CloseBtn = "bg/退出按钮";
string ResetBtn = "bg/重置按钮";
protected override void OnButtonClick(string btnName)
{
if (btnName == CloseBtn) OnCloseBtn();
if (btnName == ResetBtn) OnResetBtn();
}
void OnCloseBtn()
{
Close();//关闭界面
UIManager.Instance.ShowTips("关闭界面!", Color.red);//提示
}
void OnResetBtn()
{
UIManager.Instance.ShowTips("重置成功!", Color.blue);
AudioManager.Instance.ResetVolume();
SetSliderValue();
}
#endregion
#region 滑动条事件
string MasterSlide = "bg/Volume/Master/Slide";
string BGMSlide = "bg/Volume/BGM/Slide";
string BGSSlide = "bg/Volume/BGS/Slide";
string SoundSlide = "bg/Volume/Sound/Slide";
string VoiceSlide = "bg/Volume/Voice/Slide";
protected override void OnSliderValueChanged(string toggleName, float value)
{
if (toggleName == MasterSlide) OnSliderMasterVolume(value);
if (toggleName == BGMSlide) OnSliderBGMVolume(value);
if (toggleName == BGSSlide) OnSliderBGSVolume(value);
if (toggleName == SoundSlide) OnSliderSoundVolume(value);
if (toggleName == VoiceSlide) OnSliderVoiceVolume(value);
}
//修改音量
void OnSliderMasterVolume(float value)
{
AudioManager.Instance.SetMasterVolume(value);
}
void OnSliderBGMVolume(float value)
{
AudioManager.Instance.SetBGMVolume(value);
}
void OnSliderBGSVolume(float value)
{
AudioManager.Instance.SetBGSVolume(value);
}
void OnSliderSoundVolume(float value)
{
AudioManager.Instance.SetSoundVolume(value);
}
void OnSliderVoiceVolume(float value)
{
AudioManager.Instance.SetVoiceVolume(value);
}
#endregion
//按音量修改滑动条值
void SetSliderValue()
{
GetControl<Slider>(MasterSlide).value = AudioManager.Instance.MatserVolume;
GetControl<Slider>(BGMSlide).value = AudioManager.Instance.BGMVolume;
GetControl<Slider>(BGSSlide).value = AudioManager.Instance.BGSVolume;
GetControl<Slider>(SoundSlide).value = AudioManager.Instance.SoundVolum;
GetControl<Slider>(VoiceSlide).value = AudioManager.Instance.VoiceVolume;
}
}
效果
10、声音/音频/音效管理器
包括带自带对象池的2d、3d音效播放,AudioMixer的使用和如何控制音量,实现bgm背景音和BGS环境音的淡入淡出效果
BGM
# 播放BGM1
AudioManager.Instance.PlayBGM(BGM1);
# 播放BGM2
AudioManager.Instance.PlayBGM(BGM2);
# 暂停BGM
AudioManager.Instance.PauseBGM();
# 取消暂停BGM
AudioManager.Instance.UnPauseBGM();
# 停止BGM
AudioManager.Instance.StopBGM();
音效
# 播放2D音效
AudioManager.Instance.PlaySound(Sound1);
# 在指定目标对象身上播放3D音效
AudioManager.Instance.PlaySound(Sound2, gameObject);
# 使用对象池在世界空间中指定的位置播放3D音效
AudioManager.Instance.PlaySound(Sound2, Vector3.one);
BGS环境音
# 播放环境音1
AudioManager.Instance.PlayBGS(BGS1);
# 播放环境音2
AudioManager.Instance.PlayBGS(BGS2);
# 暂停环境音
AudioManager.Instance.PauseBGS();
# 取消暂停环境音
AudioManager.Instance.UnPauseBGS();
# 停止环境音
AudioManager.Instance.StopBGS();
播放MS提示音效
AudioManager.Instance.PlayMS(ms);
播放和停止Voice角色语音
# 播放角色语音
AudioManager.Instance.PlayVoice(voice);
# 停止角色语音
AudioManager.Instance.StopVoice();
设置音量
# 设置总音量为1
AudioManager.Instance.SetMasterVolume(1);
# 设置BGM音量为0.8
AudioManager.Instance.SetBGMVolume(0.8f);
# 设置音效音量为0.5
AudioManager.Instance.SetSoundVolume(0.5f);
# 设置环境音音量为0.2
AudioManager.Instance.SetBGSVolume(0.2f);
# 设置角色语音音量为0
AudioManager.Instance.SetVoiceVolume(0);
# 重置音量
AudioManager.Instance.ResetVolume();
获取音量
float matserVolume = AudioManager.Instance.MatserVolume;
float bGMVolume = AudioManager.Instance.BGMVolume;
float bGSVolume = AudioManager.Instance.BGSVolume;
float soundVolum = AudioManager.Instance.SoundVolum;
float voiceVolume = AudioManager.Instance.VoiceVolume;
效果
11、存档存储数据持久化系统
实现对存档的创建,获取,保存,加载,删除,缓存,加密,支持多存档
存档文件支持很多数据类似,这里我选择Json,可读性较强,易修改。
SaveData和setting分别存储用户存档和设置型存档。
用户存档下根据saveID分成若干文件夹用于存储具体的对象。
// GameSetting类中存储着游戏名称,作为全局数据
[Serializable]
public class GameSetting
{
public string gameName;
}
[Serializable]
public class GameSetting2
{
public string gameName;
}
public class SaveTest : MonoBehaviour {
GameSetting gameSetting;
GameSetting2 gameSetting2;
SaveItem saveItem;
Dictionary<string, string> info = new Dictionary<string,string>();
private void Awake() {
//添加测试数据
gameSetting = new GameSetting();
gameSetting.gameName = "测试";
gameSetting2 = new GameSetting2();
gameSetting2.gameName = "测试2";
}
private void OnGUI()
{
GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
buttonStyle.fontSize = 25; // 设置字体大小
int width = 400;
int height = 150;
//设置型存档
if (GUI.Button(new Rect(0, 0, width, height), "保存设置数据1", buttonStyle))
{
SaveManager.Instance.SaveSetting(gameSetting);
}
if (GUI.Button(new Rect(0, height, width, height), "追加设置数据2", buttonStyle))
{
SaveManager.Instance.SaveSetting(gameSetting2);
}
if (GUI.Button(new Rect(0, height*2, width, height), "加载设置数据1", buttonStyle))
{
string gameName = SaveManager.Instance.LoadSetting<GameSetting>().gameName;
Debug.Log("gameName: " + gameName);
}
if (GUI.Button(new Rect(0, height*3, width, height), "删除设置存档", buttonStyle))
{
SaveManager.Instance.DeleteAllSetting();
}
//用户存档
if (GUI.Button(new Rect(width, 0, width, height), "添加一个存档", buttonStyle))
{
saveItem = SaveManager.Instance.CreateSaveItem();
}
if (GUI.Button(new Rect(width, height, width, height), "保存用户存档数据1", buttonStyle))
{
info["name"] = "小明";
info["age"] = "15";
info["heright"] = "180";
SaveManager.Instance.SaveObject(info, saveItem);
}
if (GUI.Button(new Rect(width, height*2, width, height), "追加数据2", buttonStyle))
{
SaveManager.Instance.SaveObject(gameSetting, saveItem);
}
if (GUI.Button(new Rect(width, height*3, width, height), "打印用户存档数据1", buttonStyle))
{
Dictionary<string, string> info = SaveManager.Instance.LoadObject<Dictionary<string, string>>(saveItem);
Debug.Log("姓名:" + info["name"] + ",年龄:" + info["age"] + ",身高:" + info["heright"]);
}
if (GUI.Button(new Rect(width, height*4, width, height), "获取所有用户存档", buttonStyle))
{
SaveManager.Instance.GetAllSaveItem();//最新的在最后面
SaveManager.Instance.GetAllSaveItemByCreatTime();//最近创建的在最前面
SaveManager.Instance.GetAllSaveItem();//最近更新的在最前面
}
//删除用户存档
if (GUI.Button(new Rect(width*2, 0, width, height), "删除用户存档数据1", buttonStyle))
{
SaveManager.Instance.DeleteObject<Dictionary<string, string>>(saveItem);
}
if (GUI.Button(new Rect(width*2, height, width, height), "删除所有用户存档", buttonStyle))
{
SaveManager.Instance.DeleteAllSaveItem();
}
if (GUI.Button(new Rect(width*2, height*2, width, height), "删除某一个用户存档", buttonStyle))
{
SaveManager.Instance.DeleteSaveItem(saveItem);
}
//删除所有存档 用户+设置
if (GUI.Button(new Rect(width*3, 0, width, height), "删除所有存档", buttonStyle))
{
SaveManager.Instance.DeleteAll();
}
}
}
更新
-
2024/10/8
第一次更新
-
2024/10/9
修改UI框架
新增AudioManager音频管理器
-
2024/10/10
新增SaveManager数据存档持久化管理器
UI界面基类优化
-
2024/10/11
框架目录调整
SoundVolume命名错误修复
-
2024/10/14
修改目录结构和新增mono基类方法和工具类
封装自己的框架
如果你并不是很想使用XYFrame,而是想封装自己的框架,也可以查看我的专栏,里面详细分享了我逐步实现每个管理器的思路和方法。
注意本专栏不是教你如何使用我的框架,而是希望每个人都可以开发定制属于自己的框架。
标签:框架,AudioManager,void,height,Instance,unity,width,public,XYFrame From: https://blog.csdn.net/qq_36303853/article/details/142833921