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

10 篇文章 6 订阅

11. 声音管理器

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

Scripts/Framework/Manager/创建SoundManager.cs,给BuildResources/Audio下面添加音频

public class SoundManager : MonoBehaviour
{
    AudioSource m_MusicAudio;//背景音乐
    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;

        //音效不需要循环,音效不一定有,不用设置playOnAwake
        m_SoundAudio = this.gameObject.AddComponent<AudioSource>();
        m_SoundAudio.loop = false;
    }

    public void PlayMusic(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 as AudioClip;
            m_MusicAudio.Play();
        });
    }
    //暂停播放背景音乐
    public void PauseMusic()
    {
        m_MusicAudio.Pause();
    }
    //继续播放背景音乐
    public void OnUnPauseMusic()
    {
        m_MusicAudio.UnPause();
    }
    //停止播放背景音乐
    public void StopMusic()
    {
        m_MusicAudio.clip = null;
        m_MusicAudio.Stop();
    }

    //设置背景音乐音量
    public void SetMusicVolume(float value)
    {
        this.MusicVolume = value;
    }
    //设置音效音量
    public void SetSoundVolume(float value)
    {
        this.SoundVolume = value;
    }
}

设计UI
image.png

private static SoundManager _sound;
public static SoundManager Sound
{
    get { return _sound; }
}
public 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<MySceneManager>();
    _sound = this.gameObject.AddComponent<SoundManager>();
}

image.png

XLua.CSharpCallLua

如果希望把一个lua函数适配到一个C# delegate(一类是C#侧各种回调:UI事件,delegate参数,比如List:ForEach;另外一类场景是通过LuaTable的Get函数指明一个lua函数绑定到一个delegate)。或者把一个lua table适配到一个C# interface,该delegate或者interface需要加上该配置。
:::info
如果lua需要添加Action,并且带参数,需要添加这个标签
:::

[CSharpCallLua]
public static List<Type> mymodule_cs_call_lua_list = new List<Type>()
{
    typeof(UnityEngine.Events.UnityAction<float>),
};

为什么需要这么作,因为Slider的Action要传float并且,lua中的Action也要传float,无参数的不会报错,有参数需要在静态列表中添加说明。添加完之后,在XLua-GenerateCode一下,就可以用了。
image.png
再次构建Bundle会报错,因为这些扩展方法是编辑器下使用的,lua不允许使用,避免编辑器方法生成code代码,需要生成黑名单列表
如果出现不播放声音的问题,是因为delegate之前出现的错误,导致直接把音量0写进playerprefs了,所以一直没有办法播放,音量为0.

//黑名单
[BlackList]
public static List<List<string>> BlackList = new List<List<string>>()  {
    new List<string>(){"UnityEngine.Light", "ShadowRadius"},
	new List<string>(){"UnityEngine.Light", "shadowRadius"},
    new List<string>(){"UnityEngine.Light", "SetLightDirty"},
    new List<string>(){"UnityEngine.Light", "shadowAngle"},
};

重新构建Bundle就可以工作了。

function OnInit()
    print("lua OnInit")
end

function OnOpen()
    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

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

function OnClose()
    print("lua OnClose")
end

由于Action没有在退出前清理掉,会报错,虽然不影响,但还是休整一下。

function OnInit()
    print("lua OnInit")
end

function OnOpen()
    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

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

function OnClose()
    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也进行调整

//继承LuaBehaviour
public class UILogic : LuaBehaviour
{
    Action m_LuaOnOpen;
    Action m_LuaOnClose;
    public override void Init(string luaName)
    {
        base.Init(luaName);
        m_ScriptEnv.Get("OnOpen", out m_LuaOnOpen);
        m_ScriptEnv.Get("OnClose", out m_LuaOnClose);
    }

    public void OnOpen()
    {
        m_LuaOnOpen?.Invoke();
    }
    public void OnClose()
    {
        m_LuaOnClose?.Invoke();
    }
    protected override void Clear()
    {
        OnClose();
        base.Clear();
        m_LuaOnOpen = null;
        m_LuaOnClose = null;
    }
}

对于Package模式下,一个音乐多次播放会加载多次bundle,但是bundle不能多次加载。需要处理。

//存放加载过的Bundle
private Dictionary<string, AssetBundle> m_AssetBundles = new Dictionary<string, AssetBundle>();

AssetBundle GetBundle(string name)
{
    AssetBundle bundle = null;
    if (m_AssetBundles.TryGetValue(name, out bundle))
    {
        return bundle;
    }
    return null;
}

IEnumerator LoadBundleAsync(string assetName,Action<UObject> action = null)
{
    if (assetName.EndsWith(".unity"))
    {
        action?.Invoke(null);
        yield break;
    }
    
    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++)
            {
                yield return LoadBundleAsync(dependences[i]);
            }
        }

        //创建异步加载bundle申请
        AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(bundlePath);
        yield return request;
		bundle = request.assetBundle;
        m_AssetBundles.Add(bundleName, request.assetBundle);
    }

    // if (assetName.EndsWith(".unity"))
    // {
    //     action?.Invoke(null);
    //     yield break;
    // }
    //从bundle申请加载指定路径名的文件,例如prefab
    AssetBundleRequest bundleRequest = bundle.LoadAssetAsync(assetName);
    yield return bundleRequest;

    //如果回调和request都不为空,语法糖
    action?.Invoke(bundleRequest?.asset);
}

12. 事件管理器

Framework-Manager添加EventManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 逻辑:EventManager的m_Events相当于事件中心,其他脚本的事件都先给定一个id并注册到这个字典中,当需要运行的时候,从事件中心里查找对应id的事件执行
/// </summary>
public class EventManager : MonoBehaviour
{
    //一个参数给lua使用,lua的多个参数可以封装成table传进来
    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
        {
            if (m_Events[id] == null)
                m_Events.Remove(id);
        }
    }
    
    //执行事件
    public void Fire(int id, object args = null)
    {
        EventHandler handler;
        if (m_Events.TryGetValue(id, out handler))
        {
            handler(args);
        }
    }
}

Manager添加EventManager

public static EventManager Event
{
    get { return _event; }
}

public 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<MySceneManager>();
    _sound = this.gameObject.AddComponent<SoundManager>();
    _event = this.gameObject.AddComponent<EventManager>();
}

接下来GameStart等脚本中的匿名委托都可以用EventManager进行处理

public class GameStart : MonoBehaviour
{
    public GameMode GameMode;

    // Start is called before the first frame update
    void Start()
    {
        //开始时订阅事件
        Manager.Event.Subscribe(10000, OnLuaInit);
        
        AppConst.GameMode = this.GameMode;
        DontDestroyOnLoad(this);

        Manager.Resource.ParseVersionFile();
        Manager.Lua.Init();
    }

    void OnLuaInit(object args)
    {
        //初始化完成之后(lua都加载完),在执行回调
        Manager.Lua.StartLua("main"); //输入的文件名
        //输入的是函数名
        XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
        func.Call();
    }

    public void OnApplicationQuit()
    {
        Manager.Event.UnSubscribe(10000, OnLuaInit);
    }
}

修改LuaManager

public class LuaManager : MonoBehaviour
{
    //所有的lua文件名,获取所有lua,然后进行预加载,ResourceManager查找lua文件放进来
    public List<string> LuaNames = new List<string>();

    //缓存lua脚本内容
    private Dictionary<string, byte[]> m_LuaScripts;

    //定义一个lua虚拟机,消耗比较大,,全局只需要一个,,需要using XLua;
    public LuaEnv LuaEnv;
    
    //如果是Editor模式下,直接从luapath就把所有lua读取到字典中了,然后在调用,属于同步加载后同步使用的情况
    //但是在其他模式下,需要从bundle异步加载lua需要等待,如果等待时start就调用了,属于异步加载同步使用的情况,需要预加载
    //需要创建一个回调通知
    public void Init()
    {
        //初始化虚拟机
        LuaEnv = new LuaEnv();
        //外部调用require时,会自动调用loader来获取文件
        LuaEnv.AddLoader(Loader);

        m_LuaScripts = new Dictionary<string, byte[]>();

#if UNITY_EDITOR
        if (AppConst.GameMode == GameMode.EditorMode)
            EditorLoadLuaScript();
        else
#endif
            LoadLuaScript();
    }

    void LoadLuaScript()
    {
        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 as TextAsset).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名字和内容放到集合内
    void EditorLoadLuaScript()
    {
        //搜索所有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);
        }
        Manager.Event.Fire(10000);
    }
#endif
}

关于声音管理中出现的C# callback报错,是因为C#先释放了LuaEnv,但是Lua中有回调函数还没有取消订阅,在这里需要处理。
编写脚本Framework-Extension-UnityEx

using UnityEngine.UI;

[XLua.LuaCallCSharp]
public static class UnityEx
{
    //对按钮的事件的监听做一个扩展
    //C#监听的事件变成了C#的匿名委托了,而不是lua的方法,所以lua的方法变成了临时变量
    public static void OnClickSet(this Button button, object callback)
    {
        XLua.LuaFunction func = callback as XLua.LuaFunction;
        //监听事件前,先移除,因为不销毁这个物体,所以如果每次打开ui都监听,会监听无数个事件
        button.onClick.RemoveAllListeners();
        button.onClick.AddListener(
            () =>
            {
                func?.Call();
            }
        );
    }
    
    public static void OnValueChangedSet(this Slider slider, object callback)
    {
        XLua.LuaFunction func = callback as XLua.LuaFunction;
        //监听事件前,先移除,因为不销毁这个物体,所以如果每次打开ui都监听,会监听无数个事件
        slider.onValueChanged.RemoveAllListeners();
        slider.onValueChanged.AddListener(
            (float value) =>
            {
                func?.Call(value);
            }
        );
    }
}

P11扩展方法

function OnInit()
    print("lua OnInit")
end

function OnOpen()
    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

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

function OnClose()
    print("lua OnClose")
end
public class UILogic : LuaBehaviour
{
    Action m_LuaOnOpen;
    Action m_LuaOnClose;
    public override void Init(string luaName)
    {
        base.Init(luaName);
        m_ScriptEnv.Get("OnOpen", out m_LuaOnOpen);
        m_ScriptEnv.Get("OnClose", out m_LuaOnClose);
    }

    public void OnOpen()
    {
        m_LuaOnOpen?.Invoke();
    }
    public void OnClose()
    {
        m_LuaOnClose?.Invoke();
    }
    protected override void Clear()
    {
        //OnClose();
        base.Clear();
        m_LuaOnOpen = null;
        m_LuaOnClose = null;
    }
}

把前面那些OnClose的又改回去了。

XLua.ReflectionUse

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值