学习一个xlua框架总结
框架大体结构
场景结构

框架UML图
BuildTool 打包工具
记录文件列表如下:
Assets/BuildResources/UI/Prefab/TestUI.prefab|ui/prefab/testui.prefab.ab|Assets/BuildResources/UI/Res/3.png|Assets/BuildResources/UI/Res/1.png
以|
符号分割,[0] = 资源名,[1] = Bundle名,后面的都是依赖
GameStart 框架入口
框架入口,挂载到Root节点,读取文件列表,初始化lua等
using UnityEngine;
public class GameStart : MonoBehaviour
{
public GameMode GameMode;
public bool OpenLog;
void Start()
{
// 订阅lua初始化完成事件
Manager.Event.Subscribe(10000,OnLuaInit);
AppConst.GameMode = this.GameMode;
AppConst.OpenLog = this.OpenLog;
DontDestroyOnLoad(this);
// 读取并解析文件列表
Manager.Resource.ParseVersionFile();
// 初始化lua
Manager.Lua.Init();
}
void OnLuaInit(object args)
{
Manager.Lua.StartLua("main");
var func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
func.Call();
Manager.Pool.CreateGameObjectPool("UI", 10);
Manager.Pool.CreateGameObjectPool("Monster", 120);
Manager.Pool.CreateGameObjectPool("Effect", 120);
Manager.Pool.CreateAssetPool("AssetBundle", 10);
}
}
Manager类
存储所有模块脚本,持有所有模块,所有模块的入口,挂载到Manager节点
在Awake中初始化所有模块
private void Awake()
{
_resource = this.gameObject.AddComponent<ResourceManager>();
_lua = this.gameObject.AddComponent<LuaManager>();
_ui = this.gameObject.AddComponent<UIManager>();
_entity = this.gameObject.AddComponent<EntityManager>();
_scene = this.gameObject.AddComponent<SceneManager>();
_sound = this.gameObject.AddComponent<SoundManager>();
_event = this.gameObject.AddComponent<EventManager>();
_pool = this.gameObject.AddComponent<PoolManager>();
_net = this.gameObject.AddComponent<NetManager>();
}
资源加载与卸载流程
加载
卸载
ResourceManager资源管理器
解析文件列表,加载ab,并对外提供加载各种类型资源的方法。内部使用引用计数,在外部定时卸载引用计数为0的资源。包括编辑器模式加载和Bundle模式加载两种方式。
ParseVersion 解析版本文件
解析版本文件,缓存解析出的ab和依赖
public void ParseVersionFile()
{
// 版本文件路径
string url = Path.Combine(PathUtil.BunldeResourcePath, AppConst.FileListName);
var data = File.ReadAllLines(url);
for (int i = 0; i < data.Length; i++)
{
// 解析Bunlde信息
var bundleInfo = new BundleInfo();
string[] info = data[i].Split('|');
bundleInfo.AssetsName = info[0];
bundleInfo.BunldeName = info[1];
bundleInfo.Dependence = new List<string>(info.Length - 2);
for (int j = 2; j < info.Length; j++)
{
bundleInfo.Dependence.Add(info[j]);
}
// 缓存Bundle信息
m_BundleInfos.Add(bundleInfo.AssetsName, bundleInfo);
// 缓存lua文件名,用于加载lua脚本
if (info[0].IndexOf("LuaScripts") > 0)
{
Manager.Lua.LuaNames.Add(info[0]);
}
}
}
对外提供的Load方法
根据加载的资源类型,从不同的目录中加载文件
public void LoadUI(string assetName, Action<UObject> onComplate)
{
LoadAssetAsync(PathUtil.GetUIPath(assetName), onComplate);
}
public void LoadMusic(string assetName, Action<UObject> onComplate)
{
LoadAssetAsync(PathUtil.GetMusicPath(assetName), onComplate);
}
public void LoadSound(string assetName, Action<UObject> onComplate)
{
LoadAssetAsync(PathUtil.GetSoundPath(assetName), onComplate);
}
public void LoadEffect(string assetName, Action<UObject> onComplate)
{
LoadAssetAsync(PathUtil.GetEffectPath(assetName), onComplate);
}
public void LoadScene(string assetName, Action<UObject> onComplate)
{
LoadAssetAsync(PathUtil.GetScenePath(assetName), onComplate);
}
public void LoadLua(string assetName, Action<UObject> onComplate)
{
LoadAssetAsync(assetName, onComplate);
}
public void LoadPrefab(string assetName, Action<UObject> onComplate)
{
LoadAssetAsync(assetName, onComplate);
}
LoadAssetAsync 异步加载资源
根据模式,区分是从Edior中加载还是从Bundle中加载
private void LoadAssetAsync(string assetName, Action<UObject> onComplate)
{
if (AppConst.GameMode == GameMode.EditorMode)
EditorLoadAsset(assetName, onComplate);
else
StartCoroutine(LoadBunldeAsync(assetName, onComplate));
}
EditorLoadAsset 编辑器模式下加载
这里用到了AssetDatabase.LoadAssetAtPath
API去加载项目中的资源
private void EditorLoadAsset(string assetName, Action<UObject> onComplate)
{
#if UNITY_EDITOR
var obj = AssetDatabase.LoadAssetAtPath<UObject>(assetName);
if (obj == null)
Debug.LogError("asset name is not exist:" + assetName);
onComplate?.Invoke(obj);
#endif
}
GetBundle 从已加载的缓存中获取Bundle
注意,这里从缓存中获取bundle,如果缓存中有,返回Bundle并且引用计数+1
BundleData GetBundle(string name)
{
if(m_LoadedAssetBundle.TryGetValue(name,out var bundle))
{
bundle.Count++;
return bundle;
}
return null;
}
LoadBunldeAsync 异步加载Bundle
从读取AB文件到实例化至场景中,需要经历一下几个步骤:
- 读取AB文件
- 加载AB文件中的Bundle资源
- 实例化Bundle
这里分了如下几步去加载Bundle:
- 先从缓存中获取,如果缓存中有,加载Bundle,并且引用计数+1
- 缓存中没有,从资源池获取。如果资源池有,从资源池中取出Bundle,加载这个Bundle并且引用计数+1
- 缓存和资源池中都没有,根据传入的路径读取ab文件,加载这个Bundle,并且缓存起来,同时引用计数也要+1
这里有几个需要注意的点:
加载Bundle文件时,必须要先加载Bundle的依赖文件。 场景文件不需要加载Bundle。 依赖文件也不需要加载Bundle。
private IEnumerator LoadBunldeAsync(string assetName, Action<UObject> onComplate = null)
{
var bunldeName = m_BundleInfos[assetName].BunldeName;
var bundlePath = Path.Combine(PathUtil.BunldeResourcePath, bunldeName);
var dependences = m_BundleInfos[assetName].Dependence;
BundleData bundle = GetBundle(bunldeName);
if (bundle == null)
{
var obj = Manager.Pool.Spawn("AssetBundle",bunldeName);
if (obj != null)
{
var ab = obj as AssetBundle;
bundle = new BundleData(ab);
}
else
{
// 加载依赖
if (dependences != null && dependences.Count > 0)
{
foreach (var dependence in dependences)
{
yield return LoadBunldeAsync(dependence);
}
}
// 加载bundle
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
yield return request;
bundle = new BundleData(request.assetBundle);
m_LoadedAssetBundle.Add(bunldeName, bundle);
}
}
// 场景资源不需要加载ab
if (assetName.EndsWith(".unity"))
{
onComplate?.Invoke(null);
yield break;
}
// 加载依赖资源不需加载bundle
if (onComplate == null)
{
yield break;
}
// 加载bundle中的资源
AssetBundleRequest bunldeRequest = bundle.Bundle.LoadAssetAsync(assetName);
yield return bunldeRequest;
onComplate?.Invoke(bunldeRequest?.asset);
}
MinusOneBundleCount 减去一个资源的引用计数
这里分两种情况:
- Bundle的引用计数
>1
时,只做-1 - Bundle的引用计数
<=0
时,把Bundle放入资源池,等待资源池的定时释放
private void MinusOneBundleCount(string bundleName)
{
if (m_LoadedAssetBundle.TryGetValue(bundleName, out var bundle))
{
if (bundle.Count > 0)
{
bundle.Count--;
}
if (bundle.Count <= 0)
{
Manager.Pool.UnSpawn("AssetBundle",bundleName,bundle.Bundle);
m_LoadedAssetBundle.Remove(bundleName);
}
}
}
MinusBundleCount 减去资源的引用计数
这里需要对自身的引用数-1的同时,对引用的依赖的引用计数也要-1 这个函数对外提供,在销毁一个对象时,手动调用一下引用计数-1
public void MinusBundleCount(string assetName)
{
string bundleName = m_BundleInfos[assetName].BunldeName;
MinusOneBundleCount(bundleName);
// 依赖资源
var dependences = m_BundleInfos[assetName].Dependence;
if (dependences != null)
{
foreach (var dep in dependences)
{
string name = m_BundleInfos[dep].BunldeName;
MinusBundleCount(name);
}
}
}
UnloadBundle卸载Bundle资源
public void UnloadBundle(UObject obj)
{
var ab = obj as AssetBundle;
ab.Unload(true);
}
PoolManager 池管理器
池管理类,框架对资源池的设计思想是:可以有多个对象池和引用池,开发者可以自行按照类型去分类
CreatePool 创建对象池
创建一个指定类型的池,所有创建的池都放在Root/Pool/*
节点下
private void CreatePool<T>(string poolName, float releaseTime) where T : PoolBase
{
if (!m_Pools.TryGetValue(poolName, out var pool))
{
var go = new GameObject(poolName);
go.transform.SetParent(m_Parnt);
pool = go.AddComponent<T>();
pool.Init(releaseTime);
m_Pools.Add(poolName,pool);
}
}
CreateGameObjectPool 创建对象池
对外提供的方法,创建一个存储GameObject的池
public void CreateGameObjectPool(string poolName, float releaseTime)
{
this.CreatePool<GameObjectPool>(poolName, releaseTime);
}
CreateAssetPool 创建资源池
对外提供的方法,创建一个存储Bundle资源的池
public void CreateAssetPool(string poolName, float releaseTime)
{
this.CreatePool<AssetPool>(poolName, releaseTime);
}
Spawn 生成对象
对外提供的方法
public Object Spawn(string poolName, string assetName)
{
if (m_Pools.TryGetValue(poolName, out var pool))
{
return pool.Spawn(assetName);
}
return null;
}
UnSpawn 回收对象
对外提供的方法
public void UnSpawn(string poolName, string assetName, Object asset)
{
if (m_Pools.TryGetValue(poolName, out var pool))
{
pool.UnSpawn(assetName,asset);
}
}
PoolBase类
所有池类的父类
定时释放资源
内部维护一个释放时间间隔,定时释放池内资源
private void Update()
{
if (System.DateTime.Now.Ticks - m_LastReleaseTime >= m_ReleaseTime * 10000000)
{
m_LastReleaseTime = System.DateTime.Now.Ticks;
Release();
}
}
GameObjectPool 类
对象池,释放资源时,需要调用引用计数-1
public override void Release()
{
base.Release();
for (int i = 0; i < m_Objects.Count; i++)
{
while (i < m_Objects.Count &&
System.DateTime.Now.Ticks - m_Objects[i].LastUseTime.Ticks >= m_ReleaseTime * 10000000)
{
Destroy(m_Objects[i].Object);
Manager.Resource.MinusBundleCount(m_Objects[i].Name);
m_Objects.Remove(m_Objects[i]);
}
}
}
AssetPool类
资源池,定时卸载不用的Bundle
public override void Release()
{
for (int i = 0; i < m_Objects.Count; i++)
{
while (i < m_Objects.Count &&
System.DateTime.Now.Ticks - m_Objects[i].LastUseTime.Ticks >= m_ReleaseTime * 10000000)
{
Debug.Log($"卸载ab{m_Objects[i].Name}");
Manager.Resource.UnloadBundle(m_Objects[i].Object);
m_Objects.Remove(m_Objects[i]);
}
}
}
LuaManager Lua管理器
初始化Lua虚拟机,加载Lua脚本
classDiagram
class LuaManager{
+List~string~ LuaNames
+LuaEnv LuaEnv
-Dictionary~string,byte[]~ m_LuaScript
+Init()
+StartLua(string luaFileName)
+GetLuaScript(string luaFileName)
-Loader(ref string luaFileName)
-EditorLoadLuaScript()
-LoadLuaScript()
-AddLuaScript(string assetsName, byte[] luaScript)
-Update()
-OnDestroy()
}
class LuaBehaviour{
-LuaEnv m_LuaEnv
-Action m_LuaOnInit
-Action m_LuaUpdate
-Action m_LuaOnDestroy
~LuaTable m_ScriptEnv
+Init(string luaName)
~Clear()
-Awake()
-Update()
-OnDestroy()
-OnApplicationQuit()
}
LuaManager --|> MonoBehaviour : 继承
LuaBehaviour --|> MonoBehaviour : 继承
Init 初始化
对外提供的方法,在GameStart
中调用
这里做以下几件事:
- 初始化Lua虚拟机
- 添加第三方库
- 添加自定义Loader
- 加载所有的Lua脚本
public void Init()
{
LuaEnv = new LuaEnv();
LuaEnv.AddBuildin("rapidjson", XLua.LuaDLL.Lua.LoadRapidJson);
LuaEnv.AddLoader(Loader);
m_LuaScript = new Dictionary<string, byte[]> ();
if (AppConst.GameMode == GameMode.EditorMode)
{
EditorLoadLuaScript();
}
else
{
LoadLuaScript();
}
}
Update
调用Xlua的Tick函数,进行GC
private void Update()
{
LuaEnv?.Tick();
}
OnDestroy
释放lua虚拟机
private void OnDestroy()
{
if (LuaEnv != null)
{
LuaEnv.Dispose();
LuaEnv = null;
}
}
StartLua
执行Lua脚本,一般只调用一次,执行lua的main脚本
public void StartLua(string luaFileName)
{
LuaEnv.DoString($"require '{luaFileName}'");
}
EditorLoadLuaScript 编辑器模拟下加载所有的Lua脚本
private void EditorLoadLuaScript()
{
var luaFiles = Directory.GetFiles(PathUtil.LuaPath, "*.bytes", SearchOption.AllDirectories);
foreach (var luaFile in luaFiles)
{
var fileName = PathUtil.GetStandardPath(luaFile);
var file = File.ReadAllBytes(fileName);
AddLuaScript(PathUtil.GetUnityPath(fileName), file);
}
Manager.Event.Fire(10000);
}
LoadLuaScript 从Bundle中加载所有的Lua脚本
private void LoadLuaScript()
{
foreach (var luaName in LuaNames)
{
Manager.Resource.LoadLua(luaName, (UnityEngine.Object obj) =>
{
AddLuaScript(luaName, (obj as TextAsset)?.bytes);
if (m_LuaScript.Count < LuaNames.Count) return;
Manager.Event.Fire(10000);
LuaNames.Clear();
LuaNames = null;
});
}
}
AddLuaScript 缓存lua脚本
主要是避免在Loader中重复读取脚本
private void AddLuaScript(string assetsName, byte[] luaScript)
{
m_LuaScript[assetsName] = luaScript;
}
GetLuaScript 自定义Loader
public byte[] GetLuaScript(string luaFileName)
{
luaFileName = luaFileName.Replace(".", "/");
var fileName = PathUtil.GetLuaScriptPath(luaFileName);
if (!m_LuaScript.TryGetValue(fileName, out var luaScript))
{
Debug.Log($"lua script is not exist:{fileName}");
}
return luaScript;
}
LuaBehaviour类
绑定lua脚本,提供类似MonoBehaviour的生命周期。 同时也是所有需要在lua中挂载的脚本的父类。
Awake
为每一个lua脚本创建单独的环境,防止各脚本之前相互影响
private void Awake()
{
m_ScriptEnv = m_LuaEnv.NewTable();
var meta = m_LuaEnv.NewTable();
meta.Set("__index", m_LuaEnv.Global);
m_ScriptEnv.SetMetaTable(meta);
meta.Dispose();
m_ScriptEnv.Set("self", this);
}
Init
初始化,绑定生命周期的回调
public virtual void Init(string luaName)
{
m_LuaEnv.DoString(Manager.Lua.GetLuaScript(luaName), luaName, m_ScriptEnv);
m_ScriptEnv.Get("OnInit",out m_LuaOnInit);
m_ScriptEnv.Get("Update",out m_LuaUpdate);
m_ScriptEnv.Get("OnDestroy",out m_LuaOnDestroy);
m_LuaOnInit?.Invoke();
}
Update
调用lua中的Update
private void Update()
{
m_LuaUpdate?.Invoke();
}
OnDestroy
调用lua中的OnDestroy 清空绑定的回调,释放脚本环境
private void OnDestroy()
{
m_LuaOnDestroy?.Invoke();
Clear();
}
OnApplicationQuit
清空绑定的回调,释放脚本环境
private void OnApplicationQuit()
{
Clear();
}
Clear
清空绑定的回调,释放脚本环境 值得注意的是
释放lua虚拟机时,必须要先清空绑定的lua回调
protected virtual void Clear()
{
m_LuaOnInit = null;
m_LuaUpdate = null;
m_LuaOnDestroy = null;
m_ScriptEnv?.Dispose();
m_ScriptEnv = null;
}
OnDestroy
UIManager UI管理器
管理所有的UI,包括:UI分组、打开关闭UI
SetUIGroup
所有的UI分组都是动态创建的,UI分组脚本挂载到Root/UI/Group-*上
public void SetUIGroup(List<string> group)
{
for (int i = 0; i < group.Count; i++)
{
var go = new GameObject($"Group-{group[i]}", typeof(RectTransform));
go.transform.SetParent(m_UIParent,false);
var rectTransform = go.transform as RectTransform;
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.offsetMin = Vector2.zero;
rectTransform.offsetMax = Vector2.zero;
m_UIGroups.Add(group[i], go.transform);
}
}
这里有一个需要注意的点
- 通过new的方式创建GameObejct时,把创建好的对象设置到Canvas下,Transform不会自动转成RectTransform,如果不需要修改RectTransform的属性可以忽略
- new出来的对象放到Canvas下时,坐标会默认在屏幕左下角,需要手动设置。

GetUIGourp 获取UI分组
从缓存里获取,没啥好说的
public Transform GetUIGourp(string group)
{
m_UIGroups.TryGetValue(group, out var go);
return go;
}
OpenUI 打开UI
打开UI先从对象池读,对象池没有再去实例化一个,实例化的时候通过Bundle缓存、资源池、AB文件获取,整体流程如下:
UILogic类
继承LuaBehaviour 绑定lua脚本,提供类似MonoBehaviour的生命周期,提供打开、关闭的回调。
Init 初始化
绑定UI打开和关闭的回调
public override void Init(string luaName)
{
base.Init(luaName);
m_ScriptEnv.Get("OnOpen", out m_LuaOnOpen);
m_ScriptEnv.Get("OnClose", out m_LuaOnClose);
}
OnOpen
调用lua中的打开回调
public void OnOpen()
{
m_LuaOnOpen?.Invoke();
}
Close
回收UI到对象池,调用lua中的关闭回调。 隐藏UI在对象池的回收方法中
public void Close()
{
m_LuaOnClose?.Invoke();
Manager.Pool.UnSpawn("UI", AssetName, this.gameObject);
}
UI关闭到释放资源的流程
Clear
清空绑定的打开和关闭的lua回调
protected override void Clear()
{
m_LuaOnOpen = null;
m_LuaOnClose = null;
base.Clear();
}
EntityManager 实体管理器
管理所有的实体,包括:实体分组、显示隐藏实体。 与UI管理器不能说毫不相关,只能说一摸一样,详细的可以参考UI管理器及UILogic
SceneManager 场景管理器
管理场景的加载、切换、激活
LoadScene 叠加加载场景
叠加场景模式加载
public void LoadScene(string sceneName, string luaName)
{
Manager.Resource.LoadScene(sceneName, obj =>
{
StartCoroutine(StartLoadScene(sceneName, luaName, LoadSceneMode.Additive));
});
}
叠加加载场景和加载单个场景的区别
叠加场景加载完场景后不销毁原场景 加载单个场景,销毁原场景
ChangeScene 切换场景
单场景模式加载
public void ChangeScene(string sceneName, string luaName)
{
Manager.Resource.LoadScene(sceneName, obj =>
{
StartCoroutine(StartLoadScene(sceneName, luaName, LoadSceneMode.Single));
});
}
ActiveScene 激活场景
激活场景
活动场景是将用作新游戏对象(由脚本实例化)的目标的场景,在这些场景中会使用光照设置。以累加方式添加场景时(请参阅 LoadSceneMode.Additive),第一个场景将仍然保持为活动场景。使用此方法可将活动场景切换为您想作为目标的场景。
public void ActiveScene(string scenName)
{
var lastScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
var lastLogic = GetSceneLogic(lastScene);
lastLogic?.OnUnActive();
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(scenName);
UnityEngine.SceneManagement.SceneManager.SetActiveScene(scene);
var logic = GetSceneLogic(scene);
logic?.OnActive();
}
UnloadScene 卸载场景
对外提供的卸载场景的方法
public void UnloadScene(string sceneName)
{
StartCoroutine(UnLoadScene(sceneName));
}
StartLoadScene 加载场景的实现
每个场景中都有一个挂载SceneLogic
脚本的对象继承自LuaBehaviour
向lua提供场景的生命周期。 通过MoveGameObjectToScene
API将挂载SceneLogic
脚本的对象移动到新场景中去
private IEnumerator StartLoadScene(string sceneName, string luaName, LoadSceneMode mode)
{
if (IsLoadedScene(sceneName)) yield break;
var async = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(sceneName, mode);
async.allowSceneActivation = true;
yield return async;
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
var go = new GameObject(m_LogicName);
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(go, scene);
var logic = go.AddComponent<SceneLogic>();
logic.SceneName = sceneName;
logic.Init(luaName);
logic.OnEnter();
}
UnLoadScene 卸载场景的实现
异步卸载场景,调用lua的场景退出回调
private IEnumerator UnLoadScene(string sceneName)
{
var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName(sceneName);
if (!scene.isLoaded)
{
Debug.LogError("Scene not isLoaded");
yield break;
}
var logic = GetSceneLogic(scene);
logic?.OnQuit();
var async = UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(scene);
yield return async;
}
SceneLogic类
继承LuaBehaviour给lua提供场景的生命周期
using System;
public class SceneLogic : LuaBehaviour
{
public string SceneName;
private Action m_LuaOnActive;
private Action m_LuaOnUnActive;
private Action m_LuaOnEnter;
private Action m_LuaOnQuit;
public override void Init(string luaName)
{
base.Init(luaName);
m_ScriptEnv.Get("OnActive", out m_LuaOnActive);
m_ScriptEnv.Get("OnUnActive", out m_LuaOnUnActive);
m_ScriptEnv.Get("OnEnter", out m_LuaOnEnter);
m_ScriptEnv.Get("OnQuit", out m_LuaOnQuit);
}
public void OnActive()
{
m_LuaOnActive?.Invoke();
}
public void OnUnActive()
{
m_LuaOnUnActive?.Invoke();
}
public void OnEnter()
{
m_LuaOnEnter?.Invoke();
}
public void OnQuit()
{
m_LuaOnQuit?.Invoke();
}
protected override void Clear()
{
m_LuaOnActive = null;
m_LuaOnUnActive = null;
m_LuaOnEnter = null;
m_LuaOnQuit = null;
base.Clear();
}
}
SoundManager 声音管理器
声音分为两种类型,一种是BGM音乐一直循环播放的,一种是音效,只播放一次。 这里的实现是使用两个AudioSource分别播放BGM和音效
这个就比较简单了,没啥好说的直接上代码吧
using System.IO;
using UnityEngine;
public class SoundManager : MonoBehaviour
{
private AudioSource m_MusicAudio;
private AudioSource m_SoundAudio;
private float SoundVolume
{
get { return PlayerPrefs.GetFloat("SoundVolume", 1.0f); }
set
{
m_SoundAudio.volume = value;
PlayerPrefs.SetFloat("SoundVolume", value);
}
}
private float MusicVolume
{
get { return PlayerPrefs.GetFloat("MusicVolume", 1.0f); }
set
{
m_MusicAudio.volume = value;
PlayerPrefs.SetFloat("MusicVolume", value);
}
}
private void Awake()
{
m_MusicAudio = this.gameObject.AddComponent<AudioSource>();
m_MusicAudio.playOnAwake = false;
m_MusicAudio.loop = true;
m_SoundAudio = this.gameObject.AddComponent<AudioSource>();
m_SoundAudio.loop = false;
}
/// <summary>
/// 播放音乐
/// </summary>
/// <param name="name"></param>
public void PlayMusic(string name)
{
if (this.MusicVolume < 0.1f) return;;
string oldName = "";
if (m_MusicAudio.clip != null) oldName = m_MusicAudio.clip.name;
if (oldName == Path.GetFileNameWithoutExtension(name))
{
m_MusicAudio.Play();
return;
}
Manager.Resource.LoadMusic(name, obj =>
{
m_MusicAudio.clip = obj as AudioClip;
m_MusicAudio.Play();
});
}
/// <summary>
/// 暂停音乐
/// </summary>
public void PauseMusic()
{
m_MusicAudio.Pause();
}
/// <summary>
/// 取消暂停音乐
/// </summary>
public void UnPauseMusic()
{
m_MusicAudio.UnPause();
}
/// <summary>
/// 停止播放音乐
/// </summary>
public void StopMusic()
{
m_MusicAudio.Stop();
}
/// <summary>
/// 播放声音
/// </summary>
/// <param name="name"></param>
public void PlaySound(string name)
{
if (this.SoundVolume < 0.1f) return;
Manager.Resource.LoadSound(name, obj =>
{
m_SoundAudio.PlayOneShot(obj as AudioClip);
});
}
/// <summary>
/// 设置音乐音量
/// </summary>
/// <param name="value"></param>
public void SetMusicVolume(float value)
{
this.MusicVolume = value;
}
/// <summary>
/// 设置音效音量
/// </summary>
/// <param name="value"></param>
public void SetSoundVolume(float value)
{
this.SoundVolume = value;
}
}
EventManager 事件管理器
其实就是发布订阅模式
using System.Collections.Generic;
using UnityEngine;
public class EventManager : MonoBehaviour
{
public delegate void EventHandler(object args);
private Dictionary<int, EventHandler> m_Events = new Dictionary<int, EventHandler>();
public void Subscribe(int id, EventHandler e)
{
if (m_Events.ContainsKey(id))
{
m_Events[id] += e;
}
else
{
m_Events.Add(id, e);
}
}
public void UnSubscribe(int id, EventHandler e)
{
if (m_Events.ContainsKey(id))
{
if (m_Events[id] != null)
{
m_Events[id] -= e;
}
else
{
m_Events.Remove(id);
}
}
}
public void Fire(int id, object args = null)
{
if (m_Events.TryGetValue(id, out var handler))
{
handler(args);
}
}
}