Ilingis 发表于 2023-4-10 12:21

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

11. 声音管理器


[*]背景音乐
播放、暂停、恢复、停止、音量

[*]音效
播放、音量

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



privatestaticSoundManager _sound;publicstaticSoundManager Sound
{get{return _sound;}}publicvoidAwake(){
    _resource =this.gameObject.AddComponent<ResourceManager>();
    _lua =this.gameObject.AddComponent<LuaManager>();
    _ui =this.gameObject.AddComponent<UIManager>();
    _entity =this.gameObject.AddComponent<EntityManager>();
    _scene =this.gameObject.AddComponent<MySceneManager>();
    _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,并且带参数,需要添加这个标签
:::
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.
//黑名单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就可以工作了。
functionOnInit()print("lua OnInit")
end

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");
   
    btn_play_music.onClick:AddListener(function()
                Manager.Sound:PlayMusic("main.mp3");
            end
    )

    btn_stop_music.onClick:AddListener(function()
                Manager.Sound:StopMusic();
            end
    )

    btn_pause_music.onClick:AddListener(function()
                Manager.Sound:PauseMusic();
            end
    )

    btn_unpause_music.onClick:AddListener(function()
                Manager.Sound:OnUnPauseMusic();
            end
    )

    btn_play_sound.onClick:AddListener(function()
                Manager.Sound:PlaySound("ui_select.mp3");
            end
    )
   
    slider_music_volume.onValueChanged:AddListener(function(volume)
                Manager.Sound:SetMusicVolume(volume);print(volume);
            end
    )

    slider_sound_volume.onValueChanged:AddListener(function(volume)
                Manager.Sound:SetSoundVolume(volume);print(volume);
            end
    )
   
    slider_music_volume.value=1;
    slider_sound_volume.value=1;

end

functionUpdate()--print("lua Update")
end

functionOnClose()print("lua OnClose")
end由于Action没有在退出前清理掉,会报错,虽然不影响,但还是休整一下。
functionOnInit()print("lua OnInit")
end

functionOnOpen()print("lua OnOpen")--Manager.Scene:LoadScene("Test01","scene.Scene01")
    btn_play_music = self.transform:Find("Options Panel/ButtonsAndSlider/Play Music/Play Music On Button"):GetComponent("Button");
    btn_stop_music = self.transform:Find("Options Panel/ButtonsAndSlider/Play Music/Play Music Off Button"):GetComponent("Button");
    btn_pause_music = self.transform:Find("Options Panel/ButtonsAndSlider/Pause Music/Pause Music On Button"):GetComponent("Button");
    btn_unpause_music = self.transform:Find("Options Panel/ButtonsAndSlider/Pause Music/Pause Music Off Button"):GetComponent("Button");
    btn_play_sound = self.transform:Find("Options Panel/ButtonsAndSlider/Play Sound/Play Sound Button"):GetComponent("Button");

    slider_music_volume = self.transform:Find("Options Panel/ButtonsAndSlider/Music Volume/Music Volume Slider"):GetComponent("Slider");
    slider_sound_volume = self.transform:Find("Options Panel/ButtonsAndSlider/Sound Volume/Sound Volume Slider"):GetComponent("Slider");
   
    slider_music_volume.value= Manager.Sound.MusicVolume;
    slider_music_volume.value= Manager.Sound.SoundVolume;
   
    btn_play_music.onClick:AddListener(function()
            Manager.Sound:PlayMusic("main.mp3");
      end
    )

    btn_stop_music.onClick:AddListener(function()
            Manager.Sound:StopMusic();
      end
    )

    btn_pause_music.onClick:AddListener(function()
            Manager.Sound:PauseMusic();
      end
    )

    btn_unpause_music.onClick:AddListener(function()
            Manager.Sound:OnUnPauseMusic();
      end
    )

    btn_play_sound.onClick:AddListener(function()
            Manager.Sound:PlaySound("ui_select.mp3");
      end
    )
   
    slider_music_volume.onValueChanged:AddListener(function(volume)
            Manager.Sound:SetMusicVolume(volume);
      end
    )

    slider_sound_volume.onValueChanged:AddListener(function(volume)
            Manager.Sound:SetSoundVolume(volume);
      end
    )

end

functionUpdate()--print("lua Update")
end

functionOnClose()print("lua OnClose")
    btn_play_music.onClick:RemoveAllListeners();
    btn_stop_music.onClick:RemoveAllListeners();
    btn_pause_music.onClick:RemoveAllListeners();
    btn_unpause_music.onClick:RemoveAllListeners();
    btn_play_sound.onClick:RemoveAllListeners();
    slider_music_volume.onValueChanged:RemoveAllListeners();
    slider_sound_volume.onValueChanged:RemoveAllListeners();
end由于删除了Listener,在OnClose函数中,但是OnClose函数的调用和UILogic的逻辑相关,因此队UILgoic也进行调整
//继承LuaBehaviourpublicclassUILogic:LuaBehaviour{Action m_LuaOnOpen;Action m_LuaOnClose;publicoverridevoidInit(string luaName){base.Init(luaName);
      m_ScriptEnv.Get("OnOpen",out m_LuaOnOpen);
      m_ScriptEnv.Get("OnClose",out m_LuaOnClose);}publicvoidOnOpen(){
      m_LuaOnOpen?.Invoke();}publicvoidOnClose(){
      m_LuaOnClose?.Invoke();}protectedoverridevoidClear(){OnClose();base.Clear();
      m_LuaOnOpen =null;
      m_LuaOnClose =null;}}对于Package模式下,一个音乐多次播放会加载多次bundle,但是bundle不能多次加载。需要处理。
//存放加载过的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")){
      action?.Invoke(null);yieldbreak;}string bundleName = m_BundleInfos.BundleName;//这个是小写的bundle.ab的路径名string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);List<string> dependences = m_BundleInfos.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);}}//创建异步加载bundle申请AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);yieldreturn request;
                bundle = request.assetBundle;
      m_AssetBundles.Add(bundleName, request.assetBundle);}// if (assetName.EndsWith(".unity"))// {//   action?.Invoke(null);//   yield break;// }//从bundle申请加载指定路径名的文件,例如prefabAssetBundleRequest bundleRequest = bundle.LoadAssetAsync(assetName);yieldreturn bundleRequest;//如果回调和request都不为空,语法糖
    action?.Invoke(bundleRequest?.asset);}
12. 事件管理器

Framework-Manager添加EventManager
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))//相当于实现多播委托
            m_Events+= e;else
            m_Events.Add(id, e);}//取消订阅事件publicvoidUnSubscribe(int id,EventHandler e){if(m_Events.ContainsKey(id)){if(m_Events!=null)
                m_Events-= e;}else{if(m_Events==null)
                m_Events.Remove(id);}}//执行事件publicvoidFire(int id,object args =null){EventHandler handler;if(m_Events.TryGetValue(id,out handler)){handler(args);}}}Manager添加EventManager
publicstaticEventManager Event
{get{return _event;}}publicvoidAwake(){
    _resource =this.gameObject.AddComponent<ResourceManager>();
    _lua =this.gameObject.AddComponent<LuaManager>();
    _ui =this.gameObject.AddComponent<UIManager>();
    _entity =this.gameObject.AddComponent<EntityManager>();
    _scene =this.gameObject.AddComponent<MySceneManager>();
    _sound =this.gameObject.AddComponent<SoundManager>();
    _event =this.gameObject.AddComponent<EventManager>();}接下来GameStart等脚本中的匿名委托都可以用EventManager进行处理
publicclassGameStart:MonoBehaviour{publicGameMode GameMode;// Start is called before the first frame updatevoidStart(){//开始时订阅事件
      Manager.Event.Subscribe(10000, OnLuaInit);
      
      AppConst.GameMode =this.GameMode;DontDestroyOnLoad(this);

      Manager.Resource.ParseVersionFile();
      Manager.Lua.Init();}voidOnLuaInit(object args){//初始化完成之后(lua都加载完),在执行回调
      Manager.Lua.StartLua("main");//输入的文件名//输入的是函数名XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
      func.Call();}publicvoidOnApplicationQuit(){
      Manager.Event.UnSubscribe(10000, OnLuaInit);}}修改LuaManager
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(){//初始化虚拟机
      LuaEnv =newLuaEnv();//外部调用require时,会自动调用loader来获取文件
      LuaEnv.AddLoader(Loader);

      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的对象
            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函数的方法了
                  Manager.Event.Fire(10000);
                  LuaNames.Clear();
                  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);//读取lua文件byte[] file = File.ReadAllBytes(fileName);//把读取的lua文件添加进去AddLuaScript(PathUtil.GetUnityPath(fileName), file);}
      Manager.Event.Fire(10000);}#endif}关于声音管理中出现的C# callback报错,是因为C#先释放了LuaEnv,但是Lua中有回调函数还没有取消订阅,在这里需要处理。
编写脚本Framework-Extension-UnityEx
usingUnityEngine.UI;publicstaticclassUnityEx{//对按钮的事件的监听做一个扩展//C#监听的事件变成了C#的匿名委托了,而不是lua的方法,所以lua的方法变成了临时变量publicstaticvoidOnClickSet(thisButton button,object callback){XLua.LuaFunction func = callback asXLua.LuaFunction;//监听事件前,先移除,因为不销毁这个物体,所以如果每次打开ui都监听,会监听无数个事件
      button.onClick.RemoveAllListeners();
      button.onClick.AddListener(()=>{
                func?.Call();});}publicstaticvoidOnValueChangedSet(thisSlider slider,object callback){XLua.LuaFunction func = callback asXLua.LuaFunction;//监听事件前,先移除,因为不销毁这个物体,所以如果每次打开ui都监听,会监听无数个事件
      slider.onValueChanged.RemoveAllListeners();
      slider.onValueChanged.AddListener((floatvalue)=>{
                func?.Call(value);});}}P11扩展方法
functionOnInit()print("lua OnInit")
end

functionOnOpen()print("lua OnOpen")--Manager.Scene:LoadScene("Test01","scene.Scene01")
    btn_play_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Play Music/Play Music On Button"):GetComponent("Button");
    btn_stop_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Play Music/Play Music Off Button"):GetComponent("Button");
    btn_pause_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Pause Music/Pause Music On Button"):GetComponent("Button");
    btn_unpause_music = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Pause Music/Pause Music Off Button"):GetComponent("Button");
    btn_play_sound = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Play Sound/Play Sound Button"):GetComponent("Button");

    slider_music_volume = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Music Volume/Music Volume Slider"):GetComponent("Slider");
    slider_sound_volume = self.transform:Find("Options Page/Options Panel/ButtonsAndSlider/Sound Volume/Sound Volume Slider"):GetComponent("Slider");
   
    slider_music_volume.value= Manager.Sound.MusicVolume;
    slider_sound_volume.value= Manager.Sound.SoundVolume;--原版btn_play_music.onClick:AddListener(btn_play_music:OnClickSet(function()
            Manager.Sound:PlayMusic("main.mp3");
      end
    )

    btn_stop_music:OnClickSet(function()
            Manager.Sound:StopMusic();
      end
    )

    btn_pause_music:OnClickSet(function()
            Manager.Sound:PauseMusic();
      end
    )

    btn_unpause_music:OnClickSet(function()
            Manager.Sound:OnUnPauseMusic();
      end
    )

    btn_play_sound:OnClickSet(function()
            Manager.Sound:PlaySound("ui_select.mp3");
      end
    )
   
    slider_music_volume:OnValueChangedSet(function(volume)
            Manager.Sound:SetMusicVolume(volume);
      end
    )

    slider_sound_volume:OnValueChangedSet(function(volume)
            Manager.Sound:SetSoundVolume(volume);
      end
    )

end

functionUpdate()--print("lua Update")
end

functionOnClose()print("lua OnClose")
endpublicclassUILogic:LuaBehaviour{Action m_LuaOnOpen;Action m_LuaOnClose;publicoverridevoidInit(string luaName){base.Init(luaName);
      m_ScriptEnv.Get("OnOpen",out m_LuaOnOpen);
      m_ScriptEnv.Get("OnClose",out m_LuaOnClose);}publicvoidOnOpen(){
      m_LuaOnOpen?.Invoke();}publicvoidOnClose(){
      m_LuaOnClose?.Invoke();}protectedoverridevoidClear(){//OnClose();base.Clear();
      m_LuaOnOpen =null;
      m_LuaOnClose =null;}}把前面那些OnClose的又改回去了。

XLua.ReflectionUse

一个C#类型类型加了这个配置,xLua会生成link.xml阻止il2cpp的代码剪裁。
对于扩展方法,必须加上LuaCallCSharp或者ReflectionUse才可以被访问到。
建议所有要在Lua访问的类型,要么加LuaCallCSharp,要么加上ReflectionUse,这才能够保证在各平台都能正常运行。
页: [1]
查看完整版本: Unity Xlua热更新框架(七):声音与事件管理