找回密码
 立即注册
查看: 249|回复: 0

Unity Xlua热更新框架(七):声音与事件管理

[复制链接]
发表于 2023-4-10 12:21 | 显示全部楼层 |阅读模式
11. 声音管理器


  • 背景音乐
      播放、暂停、恢复、停止、音量

  • 音效
      播放、音量

Scripts/Framework/Manager/创建SoundManager.cs,给BuildResources/Audio下面添加音频
  1. publicclassSoundManager:MonoBehaviour{AudioSource m_MusicAudio;//背景音乐AudioSource m_SoundAudio;//音效privatefloat SoundVolume
  2.     {get{return PlayerPrefs.GetFloat("SoundVolume",1.0f);}set{
  3.             m_SoundAudio.volume =value;
  4.             PlayerPrefs.SetFloat("SoundVolume",value);}}privatefloat MusicVolume
  5.     {get{return PlayerPrefs.GetFloat("MusicVolume",1.0f);}set{
  6.             m_MusicAudio.volume =value;
  7.             PlayerPrefs.SetFloat("MusicVolume",value);}}privatevoidAwake(){//背景音乐需要循环,并且不能直接播放,因为背景音乐肯定有
  8.         m_MusicAudio =this.gameObject.AddComponent<AudioSource>();
  9.         m_MusicAudio.playOnAwake =false;
  10.         m_MusicAudio.loop =true;//音效不需要循环,音效不一定有,不用设置playOnAwake
  11.         m_SoundAudio =this.gameObject.AddComponent<AudioSource>();
  12.         m_SoundAudio.loop =false;}publicvoidPlayMusic(string name){//音量小于0.1f因为听不见了,同时节省开销,直接不播放了if(this.MusicVolume <0.1f){return;}//如果背景音乐是正在播放的,也跳过string oldName ="";if(m_MusicAudio.clip !=null){
  13.             oldName = m_MusicAudio.clip.name;}if(oldName == name.Substring(0,name.IndexOf("."))){//m_MusicAudio.Play();return;}//否则就去播放背景音乐
  14.         Manager.Resource.LoadMusic(name,(UnityEngine.Object obj)=>{
  15.             m_MusicAudio.clip = obj asAudioClip;
  16.             m_MusicAudio.Play();});}//暂停播放背景音乐publicvoidPauseMusic(){
  17.         m_MusicAudio.Pause();}//继续播放背景音乐publicvoidOnUnPauseMusic(){
  18.         m_MusicAudio.UnPause();}//停止播放背景音乐publicvoidStopMusic(){
  19.         m_MusicAudio.clip =null;
  20.         m_MusicAudio.Stop();}//设置背景音乐音量publicvoidSetMusicVolume(floatvalue){this.MusicVolume =value;}//设置音效音量publicvoidSetSoundVolume(floatvalue){this.SoundVolume =value;}}
复制代码
设计UI


  1. privatestaticSoundManager _sound;publicstaticSoundManager Sound
  2. {get{return _sound;}}publicvoidAwake(){
  3.     _resource =this.gameObject.AddComponent<ResourceManager>();
  4.     _lua =this.gameObject.AddComponent<LuaManager>();
  5.     _ui =this.gameObject.AddComponent<UIManager>();
  6.     _entity =this.gameObject.AddComponent<EntityManager>();
  7.     _scene =this.gameObject.AddComponent<MySceneManager>();
  8.     _sound =this.gameObject.AddComponent<SoundManager>();}
复制代码


XLua.CSharpCallLua

如果希望把一个lua函数适配到一个C# delegate(一类是C#侧各种回调:UI事件,delegate参数,比如List:ForEach;另外一类场景是通过LuaTable的Get函数指明一个lua函数绑定到一个delegate)。或者把一个lua table适配到一个C# interface,该delegate或者interface需要加上该配置。
:::info
如果lua需要添加Action,并且带参数,需要添加这个标签
:::
  1. [CSharpCallLua]publicstaticList<Type> mymodule_cs_call_lua_list =newList<Type>(){typeof(UnityEngine.Events.UnityAction<float>),};
复制代码
为什么需要这么作,因为Slider的Action要传float并且,lua中的Action也要传float,无参数的不会报错,有参数需要在静态列表中添加说明。添加完之后,在XLua-GenerateCode一下,就可以用了。


再次构建Bundle会报错,因为这些扩展方法是编辑器下使用的,lua不允许使用,避免编辑器方法生成code代码,需要生成黑名单列表
如果出现不播放声音的问题,是因为delegate之前出现的错误,导致直接把音量0写进playerprefs了,所以一直没有办法播放,音量为0.
  1. //黑名单[BlackList]publicstaticList<List<string>> BlackList =newList<List<string>>(){newList<string>(){"UnityEngine.Light","ShadowRadius"},newList<string>(){"UnityEngine.Light","shadowRadius"},newList<string>(){"UnityEngine.Light","SetLightDirty"},newList<string>(){"UnityEngine.Light","shadowAngle"},};
复制代码
重新构建Bundle就可以工作了。
  1. functionOnInit()print("lua OnInit")
  2. end
  3. functionOnOpen()print("lua OnOpen")--Manager.Scene:LoadScene("Test01","scene.Scene01")local btn_play_music = self.transform:Find("Options Panel/ButtonsAndSlider/Play Music/Play Music On Button"):GetComponent("Button");local btn_stop_music = self.transform:Find("Options Panel/ButtonsAndSlider/Play Music/Play Music Off Button"):GetComponent("Button");local btn_pause_music = self.transform:Find("Options Panel/ButtonsAndSlider/Pause Music/Pause Music On Button"):GetComponent("Button");local btn_unpause_music = self.transform:Find("Options Panel/ButtonsAndSlider/Pause Music/Pause Music Off Button"):GetComponent("Button");local btn_play_sound = self.transform:Find("Options Panel/ButtonsAndSlider/Play Sound/Play Sound Button"):GetComponent("Button");local slider_music_volume = self.transform:Find("Options Panel/ButtonsAndSlider/Music Volume/Music Volume Slider"):GetComponent("Slider");local slider_sound_volume = self.transform:Find("Options Panel/ButtonsAndSlider/Sound Volume/Sound Volume Slider"):GetComponent("Slider");
  4.    
  5.     btn_play_music.onClick:AddListener(function()
  6.                 Manager.Sound:PlayMusic("main.mp3");
  7.             end
  8.     )
  9.     btn_stop_music.onClick:AddListener(function()
  10.                 Manager.Sound:StopMusic();
  11.             end
  12.     )
  13.     btn_pause_music.onClick:AddListener(function()
  14.                 Manager.Sound:PauseMusic();
  15.             end
  16.     )
  17.     btn_unpause_music.onClick:AddListener(function()
  18.                 Manager.Sound:OnUnPauseMusic();
  19.             end
  20.     )
  21.     btn_play_sound.onClick:AddListener(function()
  22.                 Manager.Sound:PlaySound("ui_select.mp3");
  23.             end
  24.     )
  25.    
  26.     slider_music_volume.onValueChanged:AddListener(function(volume)
  27.                 Manager.Sound:SetMusicVolume(volume);print(volume);
  28.             end
  29.     )
  30.     slider_sound_volume.onValueChanged:AddListener(function(volume)
  31.                 Manager.Sound:SetSoundVolume(volume);print(volume);
  32.             end
  33.     )
  34.    
  35.     slider_music_volume.value=1;
  36.     slider_sound_volume.value=1;
  37. end
  38. functionUpdate()--print("lua Update")
  39. end
  40. functionOnClose()print("lua OnClose")
  41. end
复制代码
由于Action没有在退出前清理掉,会报错,虽然不影响,但还是休整一下。
  1. functionOnInit()print("lua OnInit")
  2. end
  3. functionOnOpen()print("lua OnOpen")--Manager.Scene:LoadScene("Test01","scene.Scene01")
  4.     btn_play_music = self.transform:Find("Options Panel/ButtonsAndSlider/Play Music/Play Music On Button"):GetComponent("Button");
  5.     btn_stop_music = self.transform:Find("Options Panel/ButtonsAndSlider/Play Music/Play Music Off Button"):GetComponent("Button");
  6.     btn_pause_music = self.transform:Find("Options Panel/ButtonsAndSlider/Pause Music/Pause Music On Button"):GetComponent("Button");
  7.     btn_unpause_music = self.transform:Find("Options Panel/ButtonsAndSlider/Pause Music/Pause Music Off Button"):GetComponent("Button");
  8.     btn_play_sound = self.transform:Find("Options Panel/ButtonsAndSlider/Play Sound/Play Sound Button"):GetComponent("Button");
  9.     slider_music_volume = self.transform:Find("Options Panel/ButtonsAndSlider/Music Volume/Music Volume Slider"):GetComponent("Slider");
  10.     slider_sound_volume = self.transform:Find("Options Panel/ButtonsAndSlider/Sound Volume/Sound Volume Slider"):GetComponent("Slider");
  11.    
  12.     slider_music_volume.value= Manager.Sound.MusicVolume;
  13.     slider_music_volume.value= Manager.Sound.SoundVolume;
  14.    
  15.     btn_play_music.onClick:AddListener(function()
  16.             Manager.Sound:PlayMusic("main.mp3");
  17.         end
  18.     )
  19.     btn_stop_music.onClick:AddListener(function()
  20.             Manager.Sound:StopMusic();
  21.         end
  22.     )
  23.     btn_pause_music.onClick:AddListener(function()
  24.             Manager.Sound:PauseMusic();
  25.         end
  26.     )
  27.     btn_unpause_music.onClick:AddListener(function()
  28.             Manager.Sound:OnUnPauseMusic();
  29.         end
  30.     )
  31.     btn_play_sound.onClick:AddListener(function()
  32.             Manager.Sound:PlaySound("ui_select.mp3");
  33.         end
  34.     )
  35.    
  36.     slider_music_volume.onValueChanged:AddListener(function(volume)
  37.             Manager.Sound:SetMusicVolume(volume);
  38.         end
  39.     )
  40.     slider_sound_volume.onValueChanged:AddListener(function(volume)
  41.             Manager.Sound:SetSoundVolume(volume);
  42.         end
  43.     )
  44. end
  45. functionUpdate()--print("lua Update")
  46. end
  47. functionOnClose()print("lua OnClose")
  48.     btn_play_music.onClick:RemoveAllListeners();
  49.     btn_stop_music.onClick:RemoveAllListeners();
  50.     btn_pause_music.onClick:RemoveAllListeners();
  51.     btn_unpause_music.onClick:RemoveAllListeners();
  52.     btn_play_sound.onClick:RemoveAllListeners();
  53.     slider_music_volume.onValueChanged:RemoveAllListeners();
  54.     slider_sound_volume.onValueChanged:RemoveAllListeners();
  55. end
复制代码
由于删除了Listener,在OnClose函数中,但是OnClose函数的调用和UILogic的逻辑相关,因此队UILgoic也进行调整
  1. //继承LuaBehaviourpublicclassUILogic:LuaBehaviour{Action m_LuaOnOpen;Action m_LuaOnClose;publicoverridevoidInit(string luaName){base.Init(luaName);
  2.         m_ScriptEnv.Get("OnOpen",out m_LuaOnOpen);
  3.         m_ScriptEnv.Get("OnClose",out m_LuaOnClose);}publicvoidOnOpen(){
  4.         m_LuaOnOpen?.Invoke();}publicvoidOnClose(){
  5.         m_LuaOnClose?.Invoke();}protectedoverridevoidClear(){OnClose();base.Clear();
  6.         m_LuaOnOpen =null;
  7.         m_LuaOnClose =null;}}
复制代码
对于Package模式下,一个音乐多次播放会加载多次bundle,但是bundle不能多次加载。需要处理。
  1. //存放加载过的BundleprivateDictionary<string, AssetBundle> m_AssetBundles =newDictionary<string, AssetBundle>();AssetBundleGetBundle(string name){AssetBundle bundle =null;if(m_AssetBundles.TryGetValue(name,out bundle)){return bundle;}returnnull;}IEnumeratorLoadBundleAsync(string assetName,Action<UObject> action =null){if(assetName.EndsWith(".unity")){
  2.         action?.Invoke(null);yieldbreak;}string bundleName = m_BundleInfos[assetName].BundleName;//这个是小写的bundle.ab的路径名string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);List<string> dependences = m_BundleInfos[assetName].Dependeces;//判断是否已经加载过bundle了AssetBundle bundle =GetBundle(bundleName);if(bundle ==null){if(dependences !=null&& dependences.Count >0){//递归加载依赖bundle,因为依赖的资源目录名就是bundle资源名for(int i =0; i < dependences.Count; i++){yieldreturnLoadBundleAsync(dependences[i]);}}//创建异步加载bundle申请AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);yieldreturn request;
  3.                 bundle = request.assetBundle;
  4.         m_AssetBundles.Add(bundleName, request.assetBundle);}// if (assetName.EndsWith(".unity"))// {//     action?.Invoke(null);//     yield break;// }//从bundle申请加载指定路径名的文件,例如prefabAssetBundleRequest bundleRequest = bundle.LoadAssetAsync(assetName);yieldreturn bundleRequest;//如果回调和request都不为空,语法糖
  5.     action?.Invoke(bundleRequest?.asset);}
复制代码
12. 事件管理器

Framework-Manager添加EventManager
  1. usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;/// <summary>/// 逻辑:EventManager的m_Events相当于事件中心,其他脚本的事件都先给定一个id并注册到这个字典中,当需要运行的时候,从事件中心里查找对应id的事件执行/// </summary>publicclassEventManager:MonoBehaviour{//一个参数给lua使用,lua的多个参数可以封装成table传进来publicdelegatevoidEventHandler(object args);privateDictionary<int, EventHandler> m_Events =newDictionary<int, EventHandler>();//订阅事件publicvoidSubscribe(int id,EventHandler e){if(m_Events.ContainsKey(id))//相当于实现多播委托
  2.             m_Events[id]+= e;else
  3.             m_Events.Add(id, e);}//取消订阅事件publicvoidUnSubscribe(int id,EventHandler e){if(m_Events.ContainsKey(id)){if(m_Events[id]!=null)
  4.                 m_Events[id]-= e;}else{if(m_Events[id]==null)
  5.                 m_Events.Remove(id);}}//执行事件publicvoidFire(int id,object args =null){EventHandler handler;if(m_Events.TryGetValue(id,out handler)){handler(args);}}}
复制代码
Manager添加EventManager
  1. publicstaticEventManager Event
  2. {get{return _event;}}publicvoidAwake(){
  3.     _resource =this.gameObject.AddComponent<ResourceManager>();
  4.     _lua =this.gameObject.AddComponent<LuaManager>();
  5.     _ui =this.gameObject.AddComponent<UIManager>();
  6.     _entity =this.gameObject.AddComponent<EntityManager>();
  7.     _scene =this.gameObject.AddComponent<MySceneManager>();
  8.     _sound =this.gameObject.AddComponent<SoundManager>();
  9.     _event =this.gameObject.AddComponent<EventManager>();}
复制代码
接下来GameStart等脚本中的匿名委托都可以用EventManager进行处理
  1. publicclassGameStart:MonoBehaviour{publicGameMode GameMode;// Start is called before the first frame updatevoidStart(){//开始时订阅事件
  2.         Manager.Event.Subscribe(10000, OnLuaInit);
  3.         
  4.         AppConst.GameMode =this.GameMode;DontDestroyOnLoad(this);
  5.         Manager.Resource.ParseVersionFile();
  6.         Manager.Lua.Init();}voidOnLuaInit(object args){//初始化完成之后(lua都加载完),在执行回调
  7.         Manager.Lua.StartLua("main");//输入的文件名//输入的是函数名XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
  8.         func.Call();}publicvoidOnApplicationQuit(){
  9.         Manager.Event.UnSubscribe(10000, OnLuaInit);}}
复制代码
修改LuaManager
  1. publicclassLuaManager:MonoBehaviour{//所有的lua文件名,获取所有lua,然后进行预加载,ResourceManager查找lua文件放进来publicList<string> LuaNames =newList<string>();//缓存lua脚本内容privateDictionary<string,byte[]> m_LuaScripts;//定义一个lua虚拟机,消耗比较大,,全局只需要一个,,需要using XLua;publicLuaEnv LuaEnv;//如果是Editor模式下,直接从luapath就把所有lua读取到字典中了,然后在调用,属于同步加载后同步使用的情况//但是在其他模式下,需要从bundle异步加载lua需要等待,如果等待时start就调用了,属于异步加载同步使用的情况,需要预加载//需要创建一个回调通知publicvoidInit(){//初始化虚拟机
  2.         LuaEnv =newLuaEnv();//外部调用require时,会自动调用loader来获取文件
  3.         LuaEnv.AddLoader(Loader);
  4.         m_LuaScripts =newDictionary<string,byte[]>();#if UNITY_EDITORif(AppConst.GameMode == GameMode.EditorMode)EditorLoadLuaScript();else#endifLoadLuaScript();}voidLoadLuaScript(){foreach(var name in LuaNames){//异步的需要一个回调(=>后面那一坨,当LoadLua执行时完,执行回调并invoke把结果返回),obj就是返回的lua的对象
  5.             Manager.Resource.LoadLua(name,(UnityEngine.Object obj)=>{//LoadLua调用完会把bundle加载好的bundleRequest.asset传进来用obj接受//把这个lua根据名称添加到m_LuaScripts中AddLuaScript(name,(obj asTextAsset).bytes);//在ResourceManager中解析版本文件时加载所有lua文件到LuaNames//如果LuaNames全部都加载到m_LuaScripts集合中,就清空LuaNames,退出循环if(m_LuaScripts.Count >= LuaNames.Count){//所有lua文件加载完成了。就可以执行使用lua函数的方法了
  6.                     Manager.Event.Fire(10000);
  7.                     LuaNames.Clear();
  8.                     LuaNames =null;}});}}#if UNITY_EDITOR//编辑器模式下直接加载lua文件,并把lua名字和内容放到集合内voidEditorLoadLuaScript(){//搜索所有lua文件string[] luaFiles = Directory.GetFiles(PathUtil.LuaPath,"*.bytes", SearchOption.AllDirectories);for(int i =0; i < luaFiles.Length; i++){string fileName = PathUtil.GetStandardPath(luaFiles[i]);//读取lua文件byte[] file = File.ReadAllBytes(fileName);//把读取的lua文件添加进去AddLuaScript(PathUtil.GetUnityPath(fileName), file);}
  9.         Manager.Event.Fire(10000);}#endif}
复制代码
关于声音管理中出现的C# callback报错,是因为C#先释放了LuaEnv,但是Lua中有回调函数还没有取消订阅,在这里需要处理。
编写脚本Framework-Extension-UnityEx
  1. usingUnityEngine.UI;[XLua.LuaCallCSharp]publicstaticclassUnityEx{//对按钮的事件的监听做一个扩展//C#监听的事件变成了C#的匿名委托了,而不是lua的方法,所以lua的方法变成了临时变量publicstaticvoidOnClickSet(thisButton button,object callback){XLua.LuaFunction func = callback asXLua.LuaFunction;//监听事件前,先移除,因为不销毁这个物体,所以如果每次打开ui都监听,会监听无数个事件
  2.         button.onClick.RemoveAllListeners();
  3.         button.onClick.AddListener(()=>{
  4.                 func?.Call();});}publicstaticvoidOnValueChangedSet(thisSlider slider,object callback){XLua.LuaFunction func = callback asXLua.LuaFunction;//监听事件前,先移除,因为不销毁这个物体,所以如果每次打开ui都监听,会监听无数个事件
  5.         slider.onValueChanged.RemoveAllListeners();
  6.         slider.onValueChanged.AddListener((floatvalue)=>{
  7.                 func?.Call(value);});}}
复制代码
P11扩展方法
  1. functionOnInit()print("lua OnInit")
  2. end
  3. functionOnOpen()print("lua OnOpen")--Manager.Scene:LoadScene("Test01","scene.Scene01")
  4.     btn_play_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Play Music/Play Music On Button"):GetComponent("Button");
  5.     btn_stop_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Play Music/Play Music Off Button"):GetComponent("Button");
  6.     btn_pause_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Pause Music/Pause Music On Button"):GetComponent("Button");
  7.     btn_unpause_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Pause Music/Pause Music Off Button"):GetComponent("Button");
  8.     btn_play_sound = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Play Sound/Play Sound Button"):GetComponent("Button");
  9.     slider_music_volume = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Music Volume/Music Volume Slider"):GetComponent("Slider");
  10.     slider_sound_volume = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Sound Volume/Sound Volume Slider"):GetComponent("Slider");
  11.    
  12.     slider_music_volume.value= Manager.Sound.MusicVolume;
  13.     slider_sound_volume.value= Manager.Sound.SoundVolume;--原版btn_play_music.onClick:AddListener(btn_play_music:OnClickSet(function()
  14.             Manager.Sound:PlayMusic("main.mp3");
  15.         end
  16.     )
  17.     btn_stop_music:OnClickSet(function()
  18.             Manager.Sound:StopMusic();
  19.         end
  20.     )
  21.     btn_pause_music:OnClickSet(function()
  22.             Manager.Sound:PauseMusic();
  23.         end
  24.     )
  25.     btn_unpause_music:OnClickSet(function()
  26.             Manager.Sound:OnUnPauseMusic();
  27.         end
  28.     )
  29.     btn_play_sound:OnClickSet(function()
  30.             Manager.Sound:PlaySound("ui_select.mp3");
  31.         end
  32.     )
  33.    
  34.     slider_music_volume:OnValueChangedSet(function(volume)
  35.             Manager.Sound:SetMusicVolume(volume);
  36.         end
  37.     )
  38.     slider_sound_volume:OnValueChangedSet(function(volume)
  39.             Manager.Sound:SetSoundVolume(volume);
  40.         end
  41.     )
  42. end
  43. functionUpdate()--print("lua Update")
  44. end
  45. functionOnClose()print("lua OnClose")
  46. end
复制代码
  1. publicclassUILogic:LuaBehaviour{Action m_LuaOnOpen;Action m_LuaOnClose;publicoverridevoidInit(string luaName){base.Init(luaName);
  2.         m_ScriptEnv.Get("OnOpen",out m_LuaOnOpen);
  3.         m_ScriptEnv.Get("OnClose",out m_LuaOnClose);}publicvoidOnOpen(){
  4.         m_LuaOnOpen?.Invoke();}publicvoidOnClose(){
  5.         m_LuaOnClose?.Invoke();}protectedoverridevoidClear(){//OnClose();base.Clear();
  6.         m_LuaOnOpen =null;
  7.         m_LuaOnClose =null;}}
复制代码
把前面那些OnClose的又改回去了。

XLua.ReflectionUse

一个C#类型类型加了这个配置,xLua会生成link.xml阻止il2cpp的代码剪裁。
对于扩展方法,必须加上LuaCallCSharp或者ReflectionUse才可以被访问到。
建议所有要在Lua访问的类型,要么加LuaCallCSharp,要么加上ReflectionUse,这才能够保证在各平台都能正常运行。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-5 06:32 , Processed in 0.773637 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表