【unity框架开发10】从零手搓一个UI管理器/UI框架,自带一个提示界面,还有自带DOTween动画效果(2024/10/10修改补充)

最终效果

在这里插入图片描述

文章目录

  • 最终效果
  • 前言
  • UI组件和布局的基础使用
  • UI管理器
    • 1、新增UI面板层枚举
    • 2、初始化
      • 2.1、用代码创建画布
      • 2.2、用代码创建UI面板的父物体层
      • 2.3、代码添加EventSystem物体
    • 3、ShowPanel显示面板方法
    • 4、HidePanel隐藏面板的方法
    • 5、CloseUI关闭界面的方法
    • 6、UI界面基类
  • 测试调用
  • 优化绑定按钮事件
  • 新增提示框
    • UI绘制
    • 实现
  • 使用DOTween实现动画效果
    • 提示框动画
    • 打开UI面板动画
    • 关闭UI面板动画
  • 完整代码
  • 继续优化UI界面基类(2024/10/10修改补充)
  • 完结

前言

unity在4.6版本之后,引入了自己的界面显示系统,全称unity graphic user interface,即我们所熟知的ugui。
在这里插入图片描述毕竟是unity的亲儿子,这个系统一经推出,就与其灵活快速可视化,迅速抢占用户市场,逐渐成为unity ui的主流系统,但是它也并不是完美的,对于开发人员来说,使用这套系统往往需要面对如下困境,比如缺乏跨场景的u管理器,界面的上下层关系紊乱三,界面之间的通信手段贫乏等等,上述几个问题大大影响到我们的开发效率。
在这里插入图片描述
针对上述问题,我们可以选择制作一套UI管理器解决。

UI组件和布局的基础使用

参考:【Unity游戏开发教程】零基础带你从小白到超神30——UI组件和布局的使用

UI管理器

1、新增UI面板层枚举

/// <summary>
/// UI面板层枚举
/// </summary>
public enum E_UIPanelLayer
{None,Rearmost,//最后方Rear,//后方Middle,//中间Front,//前方Forefront//最前方
}

2、初始化

2.1、用代码创建画布

/// <summary>
/// 创建画布
/// </summary>
void CreateCanvas()
{//改LayergameObject.layer = LayerMask.NameToLayer("UI");//添加并设置Canvas组件Canvas canvas = gameObject.AddComponent<Canvas>();canvas.renderMode = RenderMode.ScreenSpaceOverlay;canvas.sortingOrder = 30000;//添加并设置CanvasScaler组件CanvasScaler canvasScaler = gameObject.AddComponent<CanvasScaler>();canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;canvasScaler.referenceResolution = new Vector2(Screen.width, Screen.height);//横版屏幕设置为1 竖版屏幕设置为0canvasScaler.matchWidthOrHeight = Screen.width > Screen.height ? 1 : 0;//添加Graphic Raycaster组件gameObject.AddComponent<GraphicRaycaster>();
}

效果
在这里插入图片描述

2.2、用代码创建UI面板的父物体层

// 用于记录每个层的父物体
private Dictionary<E_UIPanelLayer, Transform> layerParents;/// <summary>
/// 创建UI面板的父物体层
/// </summary>
void CreateLayer()
{//Rearmost层的父物体Transform rearmost = new GameObject(E_UIPanelLayer.Rearmost.ToString()).transform;rearmost.SetParent(transform, false);//Rear层的父物体Transform rear = new GameObject(E_UIPanelLayer.Rear.ToString()).transform;rear.SetParent(transform, false);//Middle层的父物体Transform middle = new GameObject(E_UIPanelLayer.Middle.ToString()).transform;middle.SetParent(transform, false);//Fornt层的父物体Transform front = new GameObject(E_UIPanelLayer.Front.ToString()).transform;front.SetParent(transform, false);//Frontmost层的父物体Transform foreFront = new GameObject(E_UIPanelLayer.ForeFront.ToString()).transform;foreFront.SetParent(transform, false);//记录每个层的父物体layerParents = new Dictionary<E_UIPanelLayer, Transform>{{ E_UIPanelLayer.Rearmost, rearmost },{ E_UIPanelLayer.Rear, rear },{ E_UIPanelLayer.Middle, middle },{ E_UIPanelLayer.Front, front },{ E_UIPanelLayer.ForeFront, foreFront }};
}

效果
在这里插入图片描述

2.3、代码添加EventSystem物体

/// <summary>
/// 创建EventSystem
/// </summary>
void CreateEventSystem()
{//如果场景中已经有一个EventSystem了,则直接返回。if (FindObjectOfType<EventSystem>()) return;GameObject eventSystem = new GameObject("EventSystem");DontDestroyOnLoad(eventSystem);//切换场景不销毁eventSystem.AddComponent<EventSystem>();eventSystem.AddComponent<StandaloneInputModule>();
}

效果
在这里插入图片描述

3、ShowPanel显示面板方法

//存储加载过的界面的集合
private List<UIBase> uiList = new List<UIBase>();/// <summary>
/// 显示面板
/// </summary>
/// <typeparam name="T">UI面板脚本,记得UI面板预制体名要和脚本名一样</typeparam>
/// <param name="layer">父级层</param>
/// <returns>UIBase</returns>
public UIBase ShowUI<T>(E_UIPanelLayer layer = E_UIPanelLayer.Middle) where T : UIBase
{string uiName = typeof(T).Name;//获取名称UIBase ui = Find(uiName);if (ui == null){//记录该面板要放进哪个层中来显示Transform parent = layerParents[layer];//集合中没有 需要从Resources/UI文件夹加载GameObject obj = Instantiate(Resources.Load("UI/" + uiName), parent) as GameObject;//改名字,默认实例化会加上(clone),所以得重命名obj.name = uiName;//添加需要的脚本ui = obj.AddComponent<T>();//添加到集合进行存储uiList.Add(ui);}else{//显示ui.Show();}return ui;
}

调用
在这里插入图片描述

4、HidePanel隐藏面板的方法

/// <summary>
/// 隐藏面板
/// </summary>
/// <param name="uiName">面板名</param>
public void HideUI(string uiName)
{UIBase ui = Find(uiName);if (ui != null){ui.Hide();}
}

调用
在这里插入图片描述

5、CloseUI关闭界面的方法

/// <summary>
/// 关闭某个界面
/// </summary>
/// <param name="uiName">面板名</param>
public void CloseUI(string uiName)
{UIBase ui = Find(uiName);if (ui != null){uiList.Remove(ui);Destroy(ui.gameObject);}
}

6、UI界面基类

/// <summary>
/// UI界面基类
/// </summary>
public class UIBase : MonoBehaviour
{//显示public virtual void Show(){gameObject.SetActive(true);}//隐藏public virtual void Hide(){gameObject.SetActive(false);}//关闭界面(销毁)public virtual void Close(){UIManager.Instance.CloseUI(gameObject.name);}
}

测试调用

欢迎面板
在这里插入图片描述

新增WelcomeUI.cs测试面板代码,注意记得继承UIBase基类

public class WelcomeUI : UIBase {void Awake(){//绑定按钮事件transform.Find("bg/退出按钮").GetComponent<Button>().onClick.AddListener(onCloseBtn);transform.Find("bg/确定").GetComponent<Button>().onClick.AddListener(onConfirmBtn);}void onCloseBtn(){//关闭界面Close();}void onConfirmBtn(){//隐藏Hide();}
}

新增UITest ,绘制按钮显示WelcomeUI欢迎面板

public class UITest : MonoBehaviour {private void OnGUI(){// 创建一个新的 GUIStyleGUIStyle buttonStyle = new GUIStyle(GUI.skin.button);// 设置字体大小buttonStyle.fontSize = 50; // 替换为你想要的字体大小buttonStyle.alignment = TextAnchor.MiddleCenter; // 可选择设置对齐方式if (GUI.Button(new Rect(0, 0, 500, 200), "显示欢迎面板", buttonStyle)){//显示WelcomeUI面板,创建的脚本名字记得跟预制体物体名字一致UIManager.Instance.ShowUI<WelcomeUI>("WelcomeUI");}}
}

效果
在这里插入图片描述

优化绑定按钮事件

每次绑定按钮事件都需要写这么多代码很麻烦,我们可以继续进行封装

修改UI界面基类UIBase

//封装点击事件
public void OnBtnClick(string name, UnityAction onBtnClick)
{Transform tf = transform.Find(name);Button btn = tf.GetComponent<Button>();if (btn) btn.onClick.AddListener(() => onBtnClick?.Invoke());
}//封装滑动条事件
public void OnSliderChanged(string name, UnityAction<float> onSliderChange)
{Transform tf = transform.Find(name);Slider slider = tf.GetComponent<Slider>();if (slider) slider.onValueChanged.AddListener(value => onSliderChange?.Invoke(value));
}

测试调用

public class WelcomeUI : UIBase {void Awake(){//绑定按钮事件// transform.Find("bg/退出按钮").GetComponent<Button>().onClick.AddListener(onCloseBtn);// transform.Find("bg/确定").GetComponent<Button>().onClick.AddListener(onConfirmBtn);OnBtnClick("bg/退出按钮", OnCloseBtn);OnBtnClick("bg/确定", OnConfirmBtn);}void OnCloseBtn(){//关闭界面Close();}void OnConfirmBtn(){//隐藏Hide();}
}

效果,和前面一样
在这里插入图片描述

新增提示框

UI绘制

背景图片
在这里插入图片描述
配置
在这里插入图片描述

实现

修改UIManager

/// <summary>
/// 提示界面
/// </summary>
/// <param name="msg">文本</param>
/// <param name="color">颜色</param>
/// <param name="callback">完成回调事件</param>
public void ShowTips(string msg, Color color, float showTime = 0.5f, UnityAction callback = null, E_UIPanelLayer layer = E_UIPanelLayer.ForeFront)
{UIBase ui = ShowUI<TipsUI>(layer);TextMeshProUGUI text = ui.transform.Find("bg/text").GetComponent<TextMeshProUGUI>();text.color = color;text.text = msg;
}

测试调用

UIManager.Instance.ShowTips("成功!", Color.green);

效果
在这里插入图片描述

使用DOTween实现动画效果

参考:【推荐100个unity插件之2】DoTween动画插件的安装和使用整合(最全)

提示框动画

实现面板先经过0.4sY轴缩放从0变为1,再暂停showTime秒后,经过0.4sY轴缩放从1变回0,动画播放完成调用callback事件

/// <summary>
/// 提示界面
/// </summary>
/// <param name="msg">文本</param>
/// <param name="color">颜色</param>
/// <param name="showTime">显示时间</param>
/// <param name="callback">完成回调事件</param>
public void ShowTips(string msg, Color color, float showTime = 0.5f, UnityAction callback = null, E_UIPanelLayer layer = E_UIPanelLayer.ForeFront)
{DOTween.CompleteAll(true);UIBase ui = ShowUI<TipsUI>("TipsUI", layer);TextMeshProUGUI text = ui.transform.Find("bg/text").GetComponent<TextMeshProUGUI>();text.color = color;text.text = msg;// DOTween动画 方法一// Sequence sequence = DOTween.Sequence();// sequence.Append(ui.transform.DOScaleY(1, 0.4f).From(0)) // 第一个动画,缩放到 1// .Append(DOVirtual.DelayedCall(showTime, () => { })) // 延迟// .Append(ui.transform.DOScaleY(0, 0.4f).From(1)) // 第二个动画,缩放到 0// .OnComplete(() =>// {//     ui.gameObject.SetActive(false); // 隐藏 UI//     callback?.Invoke(); // 调用回调// });// DOTween动画 方法二ui.transform.DOScaleY(1, 0.4f).From(0).OnComplete(() =>{// 延迟显示时间DOVirtual.DelayedCall(showTime, () =>{ui.transform.DOScaleY(0, 0.4f).From(1).OnComplete(() =>{ui.gameObject.SetActive(false);callback?.Invoke();});});});
}

效果
在这里插入图片描述

打开UI面板动画

修改UIManager里的ShowUI方法,新增DOTween代码即可

ui.transform.DOScale(Vector3.one, 0.5f).From(Vector3.zero);

效果
在这里插入图片描述

关闭UI面板动画

//动画
CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();
Sequence closeSequence = DOTween.Sequence();
closeSequence.Append(canvasGroup.DOFade(0, 0.8f)) // 淡出
.Join(ui.transform.DOLocalMoveX(2000f, 0.8f)) // 同时移动
.OnComplete(() =>
{ui.gameObject.SetActive(false);
});

效果
在这里插入图片描述

完整代码

UIBase.cs

/// <summary>
/// UI界面基类
/// </summary>
public class UIBase : MonoBehaviour
{//显示public virtual void Show(){transform.localPosition = Vector3.zero;gameObject.SetActive(true);}//隐藏public virtual void Hide(){UIManager.Instance.HideUI(gameObject.name);}//关闭界面(销毁)public virtual void Close(){UIManager.Instance.CloseUI(gameObject.name);}//封装点击事件public void OnBtnClick(string name, UnityAction onBtnClick){Transform tf = transform.Find(name);Button btn = tf.GetComponent<Button>();if (btn) btn.onClick.AddListener(() => onBtnClick?.Invoke());}//封装滑动条事件public void OnSliderChanged(string name, UnityAction<float> onSliderChange){Transform tf = transform.Find(name);Slider slider = tf.GetComponent<Slider>();if (slider) slider.onValueChanged.AddListener(value => onSliderChange?.Invoke(value));}
}

UIManager.cs

/// <summary>
/// UI管理器
/// </summary>
public class UIManager : SingletonMono<UIManager>
{// 用于记录每个层的父物体private Dictionary<E_UIPanelLayer, Transform> layerParents;//存储加载过的界面的集合private List<UIBase> uiList = new List<UIBase>();#region 初始化void Awake(){//创建画布CreateCanvas();//创建UI面板的父物体层CreateLayer();//创建EventSystemCreateEventSystem();}/// <summary>/// 创建画布/// </summary>void CreateCanvas(){//改LayergameObject.layer = LayerMask.NameToLayer("UI");//添加并设置Canvas组件Canvas canvas = gameObject.AddComponent<Canvas>();canvas.renderMode = RenderMode.ScreenSpaceOverlay;canvas.sortingOrder = 30000;//添加并设置CanvasScaler组件CanvasScaler canvasScaler = gameObject.AddComponent<CanvasScaler>();canvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;canvasScaler.referenceResolution = new Vector2(Screen.width, Screen.height);//横版屏幕设置为1 竖版屏幕设置为0canvasScaler.matchWidthOrHeight = Screen.width > Screen.height ? 1 : 0;//添加Graphic Raycaster组件gameObject.AddComponent<GraphicRaycaster>();}/// <summary>/// 创建UI面板的父物体层/// </summary>void CreateLayer(){//Rearmost层的父物体Transform rearmost = new GameObject(E_UIPanelLayer.Rearmost.ToString()).transform;rearmost.SetParent(transform, false);//Rear层的父物体Transform rear = new GameObject(E_UIPanelLayer.Rear.ToString()).transform;rear.SetParent(transform, false);//Middle层的父物体Transform middle = new GameObject(E_UIPanelLayer.Middle.ToString()).transform;middle.SetParent(transform, false);//Fornt层的父物体Transform front = new GameObject(E_UIPanelLayer.Front.ToString()).transform;front.SetParent(transform, false);//Frontmost层的父物体Transform foreFront = new GameObject(E_UIPanelLayer.ForeFront.ToString()).transform;foreFront.SetParent(transform, false);//记录每个层的父物体layerParents = new Dictionary<E_UIPanelLayer, Transform>{{ E_UIPanelLayer.Rearmost, rearmost },{ E_UIPanelLayer.Rear, rear },{ E_UIPanelLayer.Middle, middle },{ E_UIPanelLayer.Front, front },{ E_UIPanelLayer.ForeFront, foreFront }};}/// <summary>/// 创建EventSystem/// </summary>void CreateEventSystem(){//如果场景中已经有一个EventSystem了,则直接返回。if (FindObjectOfType<EventSystem>()) return;GameObject eventSystem = new GameObject("EventSystem");DontDestroyOnLoad(eventSystem);//切换场景不销毁eventSystem.AddComponent<EventSystem>();eventSystem.AddComponent<StandaloneInputModule>();}#endregion/// <summary>/// 显示面板/// </summary>/// <typeparam name="T">UI面板脚本,记得UI面板预制体名要和脚本名一样</typeparam>/// <param name="layer">父级层</param>/// <param name="doTween">是否使用doTween动画</param>/// <returns>UIBase</returns>public UIBase ShowUI<T>(E_UIPanelLayer layer = E_UIPanelLayer.Middle, bool doTween = true) where T : UIBase{DOTween.CompleteAll(true);string uiName = typeof(T).Name;//获取名称UIBase ui = Find(uiName);if (ui == null){//记录该面板要放进哪个层中来显示Transform parent = layerParents[layer];//集合中没有 需要从Resources/UI文件夹加载GameObject obj = Instantiate(Resources.Load("UI/" + uiName), parent) as GameObject;//改名字,默认实例化会加上(clone),所以得重命名obj.name = uiName;//添加需要的脚本ui = obj.AddComponent<T>();//添加CanvasGroup组件,用于后面渐变使用obj.AddComponent<CanvasGroup>();//添加到集合进行存储uiList.Add(ui);}else{//显示ui.Show();}//透明度设置为1CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();canvasGroup.alpha = 1f;//动画if (doTween) ui.transform.DOScale(Vector3.one, 0.5f).From(Vector3.zero);return ui;}/// <summary>/// 隐藏面板/// </summary>/// <param name="uiName">面板名</param>public void HideUI(string uiName, bool doTween = true){DOTween.CompleteAll(true);UIBase ui = Find(uiName);if (ui == null) return;if (doTween){//动画CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();Sequence closeSequence = DOTween.Sequence();closeSequence.Append(canvasGroup.DOFade(0, 0.8f)) // 淡出.Join(ui.transform.DOLocalMoveX(2000f, 0.8f)) // 同时移动.OnComplete(() =>{ui.gameObject.SetActive(false);});}else{ui.gameObject.SetActive(false);}}/// <summary>/// 关闭某个界面/// </summary>/// <param name="uiName">面板名</param>public void CloseUI(string uiName, bool doTween = true){DOTween.CompleteAll(true);UIBase ui = Find(uiName);if (ui == null) return;if (doTween){//动画CanvasGroup canvasGroup = ui.transform.GetComponent<CanvasGroup>();Sequence closeSequence = DOTween.Sequence();closeSequence.Append(canvasGroup.DOFade(0, 0.8f)) // 淡出.Join(ui.transform.DOLocalMoveX(2000f, 0.8f)) // 同时移动.OnComplete(() =>{uiList.Remove(ui);Destroy(ui.gameObject);});}else{uiList.Remove(ui);Destroy(ui.gameObject);}}//关闭所有界面public void CloseAllUI(){for (int i = uiList.Count - 1; i >= 0; i--){Destroy(uiList[i].gameObject);}uiList.Clear();//清空合集}/// <summary>/// 从集合中找到名字对应的界面脚本/// </summary>/// <param name="uiName">面板名</param>/// <returns>UIBase</returns>public UIBase Find(string uiName){for (int i = 0; i < uiList.Count; i++){if (uiList[i].name == uiName) return uiList[i];}return null;}/// <summary>/// 提示界面/// </summary>/// <param name="msg">文本</param>/// <param name="color">颜色</param>/// <param name="showTime">显示时间</param>/// <param name="callback">完成回调事件</param>public void ShowTips(string msg, Color color, float showTime = 0.5f, UnityAction callback = null, E_UIPanelLayer layer = E_UIPanelLayer.ForeFront){DOTween.CompleteAll(true);UIBase ui = ShowUI<TipsUI>(layer, false);TextMeshProUGUI text = ui.transform.Find("bg/text").GetComponent<TextMeshProUGUI>();text.color = color;text.text = msg;//动画Sequence sequence = DOTween.Sequence();sequence.Append(ui.transform.DOScaleY(1, 0.4f).From(0)) // 第一个动画,缩放到 1.Append(DOVirtual.DelayedCall(showTime, () => { })) // 延迟.Append(ui.transform.DOScaleY(0, 0.4f).From(1)) // 第二个动画,缩放到 0.OnComplete(() =>{ui.gameObject.SetActive(false); // 隐藏 UIcallback?.Invoke(); // 调用回调});}
}

继续优化UI界面基类(2024/10/10修改补充)

按前面方式,我们想修改查找某些控件还是很麻烦,比如transform.Find("bg/Volume/Master/Slide").GetComponent<Slider>().value

我们可以在UI初始化时,提前获取所有控件信息并存储在字典里,并且给特殊控件绑定好各种事件,比如按钮点击事件、单选框或者多选框值改变事件、滑动条值改变事件

具体实现代码如下

/// <summary>
/// UI界面基类
/// </summary>
public class UIBase : MonoBehaviour
{//通过里式转换原则 来存储所有的控件信息private Dictionary<string, List<UIBehaviour>> controlDic = new Dictionary<string, List<UIBehaviour>>();bool isInit;protected virtual void Awake(){Init();}#region 初始化// 初始化获取所有控件信息void Init(){if(isInit) return;FindChildrenControl<Button>();FindChildrenControl<Image>();FindChildrenControl<Text>();FindChildrenControl<TextMeshProUGUI>();FindChildrenControl<Toggle>();FindChildrenControl<Slider>();FindChildrenControl<ScrollRect>();FindChildrenControl<InputField>();isInit = true;}/// <summary>/// 找到子对象的对应控件/// </summary>/// <typeparam name="T"></typeparam>private void FindChildrenControl<T>() where T : UIBehaviour{T[] controls = this.GetComponentsInChildren<T>(true);for (int i = 0; i < controls.Length; ++i){// 获取控件的完整路径string path = GetPath(controls[i].transform);if (controlDic.ContainsKey(path)){controlDic[path].Add(controls[i]);}else{controlDic.Add(path, new List<UIBehaviour>() { controls[i] });}// 如果是按钮控件if (controls[i] is Button button){button.onClick.AddListener(() =>{OnButtonClick(path);});}// 如果是单选框或者多选框else if (controls[i] is Toggle toggle){toggle.onValueChanged.AddListener(value =>{OnToggleValueChanged(path, value);});}// 如果是滑动条else if (controls[i] is Slider slider){slider.onValueChanged.AddListener(value =>{OnSliderValueChanged(path, value);});}}}/// <summary>/// 获取控件的完整路径/// </summary>/// <param name="transform"></param>/// <returns></returns>private string GetPath(Transform transform){List<string> pathParts = new List<string>();while (transform != null){pathParts.Add(transform.name);transform = transform.parent;}pathParts.Reverse();//反转路径顺序pathParts.RemoveRange(0, 3); // 移除前三个元素return string.Join("/", pathParts);}#endregion//显示public virtual void Show(){transform.localPosition = Vector3.zero;gameObject.SetActive(true);}//隐藏public virtual void Hide(){UIManager.Instance.HideUI(gameObject.name);}//关闭界面(销毁)public virtual void Close(){UIManager.Instance.CloseUI(gameObject.name);}/// <summary>/// 获取指定控件/// </summary>/// <typeparam name="T"></typeparam>/// <param name="path">控件的完整路径</param>/// <returns></returns>protected T GetControl<T>(string path) where T : UIBehaviour{if (controlDic.ContainsKey(path)){for (int i = 0; i < controlDic[path].Count; ++i){if (controlDic[path][i] is T)return controlDic[path][i] as T;}}return null;}#region 事件/// <summary>/// 按钮点击事件/// </summary>/// <param name="path">控件路径</param>protected virtual void OnButtonClick(string path) {}/// <summary>/// 单选框或者多选框值改变事件/// </summary>/// <param name="path">控件路径</param>/// <param name="value">值</param>protected virtual void OnToggleValueChanged(string path, bool value) {}/// <summary>/// 滑动条值改变事件/// </summary>/// <param name="path"></param>/// <param name="float">值</param>protected virtual void OnSliderValueChanged(string path, float value) {}#endregion   
}

调用

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;}
}

效果
在这里插入图片描述

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35357.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

智能指针【C++11】

文章目录 智能指针std::auto_ptr std::unique_ptrstd::shared_ptrstd::shared_ptr的线程安全问题std::weak_ptr 智能指针 std::auto_ptr 管理权转移 auto_ptr是C98中引入的智能指针&#xff0c;auto_ptr通过管理权转移的方式解决智能指针的拷贝问题&#xff0c;保证一个资源…

Win11 24h2 不能正常ensp

Win11 24h2 不能正常ensp 因为Win11 24h2的内核大小更改&#xff0c;目前virtualbox在7.1.4中更新解决了。而ensp不支持5.2.44之后的virtualbox并已停止维护&#xff0c;不再进行5.2.44修复&#xff0c;virtualbox 5.2.24的ntdll文件sizeofimage问题&#xff0c;此问题导致ens…

使用GO--Swagger生成文档

概述 在前后端分离的项目中&#xff0c;后端配置swagger可以很好的帮助前端人员了解后端接口参数和数据传输。go-swagger 是一个功能全面且高性能的Go语言实现工具包&#xff0c;用于处理Swagger 2.0&#xff08;即OpenAPI 2.0&#xff09;规范。它提供了丰富的工具集&#x…

沃德云商协系统微信小程序PHP+Uniapp

“多组织”的云服务平台&#xff0c;打造总商会、总协会、总校友会、工商联等多组织无障碍沟通合作平台&#xff0c;让各大分会、各大分校友会、分组织实现轻松管理&#xff0c;线上宣传展示、商机挖掘、会员管理、会员服务、跨界交流等, 借助沃德云商协平台系统&#xff0c;让…

网站打开速度测试工具:互联网优化的得力助手

在信息飞速流转的互联网时代&#xff0c;网站如同企业与用户对话的窗口&#xff0c;其打开速度直接关乎用户体验&#xff0c;乃至业务的成败。所幸&#xff0c;一系列专业的网站打开速度测试工具应运而生&#xff0c;它们宛如幕后的技术侦探&#xff0c;精准剖析网站性能&#…

shell脚本实战案例

文章目录 实战第一坑功能说明脚本实现 实战第一坑 实战第一坑&#xff1a;在Windows系统写了一个脚本&#xff0c;比如上面&#xff0c;随后上传到服务&#xff0c;执行会报错 原因&#xff1a; 解决方案&#xff1a;在linux系统touch文件&#xff0c;并通过vim添加内容&…

Face2QR:可根据人脸图像生成二维码,还可以扫描,以后个人名片就这样用了!

今天给大家介绍的是一种专为生成个性化二维码而设计的新方法Face2QR&#xff0c;可以将美观、人脸识别和可扫描性完美地融合在一起。 下图展示为Face2QR 生成的面部图像&#xff08;第一行&#xff09;和二维码图像&#xff08;第二行&#xff09;。生成的二维码不仅忠实地保留…

数据结构---队列(Queue)

1. 简介 队列&#xff08;Queue&#xff09;是一种常用的数据结构&#xff0c;它遵循先进先出&#xff08;FIFO&#xff0c;First In First Out&#xff09;的原则。这意味着第一个进入队列的元素将是第一个被移除的元素。队列在计算机科学中有着广泛的应用&#xff0c;比如任…

玩游戏没有flash插件的解决方案(No Flash)

一、概述 在网页游戏开发领域&#xff0c;Flash和H5是两种主流的技术。Flash游戏曾经占据主导地位&#xff0c;但随着HTML5技术的发展和浏览器对Flash支持的逐渐减少&#xff0c;H5游戏逐渐成为主流。本教程将详细介绍Flash和H5的区别&#xff0c;并提供将Flash游戏转换为H5游戏…

如何查看电脑的屏幕刷新率?

1、按一下键盘的 win i 键&#xff0c;打开如下界面&#xff0c;选择【系统】&#xff1a; 2、选择【屏幕】-【高级显示设置】 如下位置&#xff0c;显示屏幕的刷新率&#xff1a;60Hz 如果可以更改&#xff0c;则选择更高的刷新率&#xff0c;有助于电脑使用起来界面更加流…

新书速览|循序渐进Node.js企业级开发实践

《循序渐进Node.js企业级开发实践》 1 本书内容 《循序渐进Node.js企业级开发实践》结合作者多年一线开发实践&#xff0c;系统地介绍了Node.js技术栈及其在企业级开发中的应用。全书共分5部分&#xff0c;第1部分基础知识&#xff08;第1&#xff5e;3章&#xff09;&#xf…

AUTOSAR AP和CP的安全要求规范(Safety Req)详细解读

一、规范的编制的背景原因 编制该规范的原因 确保系统安全性和可靠性 随着汽车电子系统日益复杂&#xff0c;功能不断增加&#xff0c;对安全性和可靠性的要求也越来越高。该规范为AUTOSAR平台在安全执行、配置、更新、信息交换、数据处理等多方面制定了明确要求&#xff0c;…

数仓技术hive与oracle对比(四)

问题处理 sqoop导入异常 将oracle数据库中的表&#xff0c;用sqoop导入hive时&#xff0c;如果表中字段值含有“&#xff0c;”&#xff0c;会导致导入hive后&#xff0c;每一行所有字段的内容都放在了第一个字段&#xff0c;其他字段均没有值。这是因为hive底层是以文件的形…

流网络等价性证明:边分解后的最大流保持不变

流网络等价性证明:边分解后的最大流保持不变 问题描述证明思路伪代码C 代码实现解释问题描述 在流网络中,证明将一条边分解为两条边所得到的是一个等价的网络。具体来说,假设流网络 $ G $ 包含边 $ (u, v) $,我们以如下方式创建一个新的流网络 $ G’ $: 创建一个新结点 $…

应用案例 | 船舶海洋: 水下无人航行器数字样机功能模型构建

水下无人航行器数字样机功能模型构建 一、项目背景 为响应水下装备系统研制数字化转型及装备系统数字样机建设的需要&#xff0c;以某型号水下无人航行器&#xff08;Underwater Unmanned Vehicle&#xff0c;UUV&#xff09;为例&#xff0c;构建UUV数字样机1.0功能模型。针对…

RabbitMQ七种工作模式之简单模式, 工作队列模式, 发布订阅模式, 路由模式, 通配符模式

文章目录 一. Simple(简单模式)公共代码:生产者:消费者: 二. Work Queue(工作队列模式)公共代码:生产者:消费者1, 消费者2(代码相同): 三. Publish/Subscribe(发布/订阅模式)公共代码:生产者:消费者: 四. Routing(路由模式)公共代码:消费者: 五. Topics(通配符模式)公共代码:生…

前端知识1html

VScode一些快捷键 Ctrl/——注释 !——生成html框架元素 *n——生成n个标签 直接书写html的名字回车生成对应的标签 常见标签 span&#xff1a; <span style"color: red;">hello</span> <span>demo</span> span实现&#xff1a; 标题…

Push an existing folder和Push an existing Git repository的区别

Push an existing folder 和 Push an existing Git repository 是在使用 Git 服务&#xff08;如 GitHub、GitLab、Bitbucket 等&#xff09;时两个常见的操作选项。它们的区别主要体现在项目的初始化和版本控制状态上&#xff1a; 1. Push an existing folder 适用场景&#…

Netty入门(快速了解以及使用netty)

二. Netty 入门 1. 概述 1.1 Netty 是什么&#xff1f; Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.Netty 是一个异步的、基于事件驱动的网络应用框架&…

Zemax 中 ZBF 文件激光传播的描述

激光传播是指激光束在空间或介质中传播的方式。激光的独特特性&#xff0c;例如相干性、单色性和准直性&#xff0c;使其行为与普通光源不同。了解激光传播的原理在光学、通信、医疗技术和科学研究等领域至关重要。 激光产生高斯光束&#xff0c;其中强度在光束横截面上服从高…