当前位置: 首页 > news >正文

自定义Unity组件——AudioManager(音频管理器)

需求描述

        在游戏开发中,音频资源是不可或缺的,通常情况下音频资源随机分布,各个音频的操作和管理都是各自负责,同时对于音频的很多操作逻辑都是大同小异的,这就造成了许多冗余代码的堆叠,除此之外在获取各类音频资源的时候也会造成不必要的开销。所以解决资源分散的问题最直接的方式就是集中管理和分配,通过统一的渠道和特有标识即可获取或操作对应的音频资源。所以本篇文章将围绕这个方案进行尝试。

功能描述

        在Unity中我们导入的音频资源都会转换为AudioClip,音频的设置和管理则由AudioSource负责,AudioListener负责监听音频。我们可以在此基础上去封装,从而打造一个音频管理器。

        音频管理器负责管理音频信息以及操作音频,比如音频信息的增加和删除,音频的播放和暂停等;

        音频信息可以使用一个单独的实体类来记录,用于记录AudioSource组件中的信息,之所以单独用一个实体类来记录音频信息而不直接采用AudioSource,主要是可以通过业务需求去动态调整所要记录的音频信息,我们的实际开发中并非需要AudioSource中所有的信息,有时候仅仅需要其中的一部分,同时音频信息可能涉及存储,直接采用AudioSource可能无法与已经开发好的存储系统相互兼容,而实体类可以为其添加接口或继承来兼容存储系统;

        AudioSource组件的管理可以通过一个组件池进行管理,我们知道AudioSource也是等同于一个音频实体类,但是其同时也是一种组件,组件同样作为一种资源,通常情况下相比简单的实体类而言会带来更大的开销,例如一个场景中有十个游戏对象有播放音频的需求,那么按照传统情况就需要每个游戏对象挂载一个AudioSource组件,但是实际运行中十个游戏对象并不一定需要同时播放音频,它们或许都存在各自触发音频播放的条件,我们只需要在游戏对象需要播放音频时为其分配一个AudioSource组件即可,组件池则负责维护AudioSource组件的生产、获取和归还,进而减少资源开销。

        本质上,AudioSource组件承担的工作就是记录音频信息和操作音频,我们现在将记录工作分担给音频信息实体类,而操作音频的工作则分担给音频管理器,例如音频管理器的播放依旧是调用AudioSource的播放方法,在播放之前由音频管理器去获取AudioSource组件并且为之配置音频信息,其它的音频操作逻辑同理。

代码展示(C#)

AudioManager.cs

using System.Linq;
using System;
using System.Collections.Generic;
using UnityEngine;namespace Tools.AudioManagerAPI
{/// <summary>/// 音频管理器/// </summary>[DisallowMultipleComponent]public class AudioManager : MonoBehaviour{[Header("必要属性")][Tooltip("音频总音量,默认为1(值属于[0,1]),该值将影响所有音频的音量,例如该值为0则所有音频音量变为原有音量的0%,若为1则所有音频音量保持不变,即该值将基于所有音频的当前音量进行影响,而不是直接统一所有音频的音量为该值")][Range(0, 1), SerializeField] private float TotalVolume = 1;/// <summary>/// 音频总音量,默认为1(值属于[0,1])/// <para>声明:该值将影响所有音频的音量,例如该值为0则所有音频音量变为原有音量的0%,若为1则所有音频音量保持不变,即该值将基于所有音频的当前音量进行影响,而不是直接统一所有音频的音量为该值</para>/// </summary>public float mTotalVolume{get { return TotalVolume; }set{if (value >= 0 && value <= 1 && TotalVolume != value){TotalVolume = value;mTotalVolumeChangedEvents?.Invoke(value);}}}/// <summary>/// 是否启用音频信息覆盖,默认为true/// </summary>public bool mIsOverWrite { get => isOverWrite; set => isOverWrite = value; }/// <summary>/// 音频管理器中所存储的音频数量/// </summary>public int mCount { get => audioInfos.Count; }/// <summary>/// 总音量更改事件回调/// </summary>public event Action<float> mTotalVolumeChangedEvents;/// <summary>/// 音频信息名称合集/// </summary>public string[] mAudioInfoNames { get => audioInfos.Keys.ToArray(); }private Dictionary<string, AudioInfo> audioInfos;//音频信息集合private bool isInit;//是否完成了初始化private bool isOverWrite;//是否启用音频信息覆盖private static AudioSourcePool audioSourcePool = AudioSourcePool.GetInstance();//AudioSource组件池/// <summary>/// 播放指定名称的音频/// <para>p_audioName:音频名称</para>/// </summary>public void Play(string p_audioName){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];AudioSource v_audioSource = audioSourcePool.Get(ai);ai.mAudioSource = v_audioSource;ai.Play();}}}/// <summary>/// 播放指定名称的音频并开启立体声过渡/// <para>p_audioName:音频名称</para>/// <para>声明:该方法要求已启用立体声过渡且已设置好立体声过渡的相关属性</para>/// </summary>public void PlayWithStereoTransition(string p_audioName){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];AudioSource v_audioSource = audioSourcePool.Get(ai);ai.mAudioSource = v_audioSource;StartCoroutine(ai.mStereoPanTransitionCoroutine);ai.Play();}}}/// <summary>/// 播放指定名称的音频并开启立体声过渡/// <para>p_audioName:音频名称</para>/// <para>p_stereoTransitionValues:立体声过渡值集合</para>/// <para>p_stereoTimeSpan:立体声过渡每帧时间间隔</para>/// </summary>public void PlayWithStereoTransition(string p_audioName, float[] p_stereoTransitionValues, float p_stereoTimeSpan){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];AudioSource v_audioSource = audioSourcePool.Get(ai);ai.mAudioSource = v_audioSource;ai.mStereoTransition = true;ai.mStereoTransitionValues = p_stereoTransitionValues;ai.mStereoTransitionTimeSpan = p_stereoTimeSpan;StartCoroutine(ai.mStereoPanTransitionCoroutine);ai.Play();}}}/// <summary>/// 暂停播放指定名称的音频/// <para>p_audioName:音频名称</para>/// </summary>public void Pause(string p_audioName){if (isInit){if (audioInfos.ContainsKey(p_audioName)){AudioInfo ai = audioInfos[p_audioName];StopCoroutine(ai.mStereoPanTransitionCoroutine);ai.Pause();audioSourcePool.Return(ai.mAudioSource);}}}/// <summary>/// 添加音频信息/// <para>p_audioInfo:音频信息</para>/// <para>声明1:若启用了音频信息覆盖,当存在相同名称的音频时,新的音频信息将覆盖旧的音频信息</para>/// <para>声明2:默认启用了音频信息覆盖,可通过mIsOverWrite属性设置禁用</para>/// </summary>public void AddAudioInfo(AudioInfo p_audioInfo){if (isInit) DoAddAudioInfo(p_audioInfo);}/// <summary>/// 删除音频信息/// <para>p_audioName:音频名称</para>/// <para>返回值:若删除成功则返回true,否则返回false</para>/// </summary>public bool DeleteAudioInfo(string p_audioName){if (isInit) return DoDeleteAudioInfo(p_audioName);return false;}private void Awake(){isInit = false;if (InitParameters()) isInit = true;}//添加音频信息的执行逻辑private void DoAddAudioInfo(AudioInfo p_audioInfo){if (p_audioInfo != null && !String.IsNullOrEmpty(p_audioInfo.mAudioName)){string v_audioName = p_audioInfo.mAudioName;p_audioInfo.BindAudioManager(this);if (isOverWrite) audioInfos[v_audioName] = p_audioInfo;else if (!audioInfos.ContainsKey(v_audioName)) audioInfos.Add(v_audioName, p_audioInfo);}}//删除音频信息的执行逻辑private bool DoDeleteAudioInfo(string p_audioName){if (!String.IsNullOrEmpty(p_audioName) && audioInfos.ContainsKey(p_audioName)){audioInfos[p_audioName].BindAudioManager(null);return audioInfos.Remove(p_audioName);}return false;}//对音频管理器相关参数进行初始化private bool InitParameters(){audioInfos = new Dictionary<string, AudioInfo>();isOverWrite = true;audioSourcePool.BindAudioManager(this);
#if UNITY_EDITORforeach (AudioInfo ai in AudioInfos)DoAddAudioInfo(ai);
#endifreturn true;}#if UNITY_EDITOR/// <summary>/// 在当前Inspector面板中的AudioInfos中的元素数量/// </summary>public int mAudioInfoCount { get => AudioInfos?.Length > 0 ? AudioInfos.Length : 0; }[SerializeField] private AudioInfo[] AudioInfos;//存储Inspector面板中/// <summary>/// 在当前Inspector面板中的AudioInfos中添加一个元素/// </summary>public void Add(){AudioInfo v_audioInfo = new AudioInfo();if (AudioInfos?.Length > 0){AudioInfo[] v_audioInfos = new AudioInfo[AudioInfos.Length + 1];AudioInfos.CopyTo(v_audioInfos, 0);v_audioInfos[v_audioInfos.Length - 1] = v_audioInfo;AudioInfos = new AudioInfo[v_audioInfos.Length];v_audioInfos.CopyTo(AudioInfos, 0);}else AudioInfos = new AudioInfo[] { v_audioInfo };v_audioInfo.ValidateCheck();}/// <summary>/// 在当前Inspector面板中的AudioInfos中删除一个元素/// </summary>public void Delete(int p_index){if (AudioInfos?.Length == 1) AudioInfos = Array.Empty<AudioInfo>();else if (AudioInfos?.Length > 1){AudioInfo[] v_audioInfos = new AudioInfo[AudioInfos.Length - 1];int v_index = 0;for (int i = 0; i < AudioInfos.Length; i++){if (i != p_index) v_audioInfos[v_index++] = AudioInfos[i];}AudioInfos = new AudioInfo[v_audioInfos.Length];v_audioInfos.CopyTo(AudioInfos, 0);}}private void OnValidate(){foreach (AudioInfo audioInfo in AudioInfos){audioInfo?.ValidateCheck();}}
#endif}
}

AudioInfo.cs

using UnityEngine;
using System.Collections;
using System.Linq;
using System;namespace Tools.AudioManagerAPI
{/// <summary>/// 音频信息/// </summary>[System.Serializable]public class AudioInfo{[Header("必要组件")][Tooltip("AudioClip组件"), SerializeField] private AudioClip TheAudioClip;[Header("必要属性")][Tooltip("音频名称"), SerializeField] private string AudioName;[Tooltip("音频音量,默认为1(值属于[0,1])"), Range(0, 1), SerializeField] private float Volume = 1;[Tooltip("音频播放速度,默认为1(值属于[-3,3])"), Range(-3, 3), SerializeField] private float Pitch = 1;[Tooltip("立体声位置,默认为0(值属于[-1,1]),若为-1则完全为左声道,若为1则完全为右声道"), Range(-1, 1), SerializeField] private float StereoPan = 0;[Tooltip("音频优先级,默认为128(值属于[0,256])"), Range(0, 256), SerializeField] private int Priority = 128;[Tooltip("是否在场景启动时进行播放,默认为true"), SerializeField] private bool PlayOnAwake = true;[Tooltip("是否循环播放,默认为false"), SerializeField] private bool Loop;[Tooltip("是否忽略总音量影响,默认为false"), SerializeField] private bool IgnoreTotalVolume;[Header("立体声过渡属性")][Tooltip("是否启用立体声过渡,默认为false"), SerializeField] private bool StereoTransition;[Tooltip("立体声过渡的每帧时间间隔,默认为0.5(值属于[0.1,5])"), Range(.1f, 5), SerializeField] private float StereoTransitionTimeSpan = 0.5f;[Tooltip("立体声过渡值集合"), SerializeField] private float[] StereoTransitionValues;/// <summary>/// 音频名称/// </summary>public string mAudioName { get => AudioName; set => AudioName = value; }/// <summary>/// 音频音量,默认为1(值属于[0,1])/// </summary>public float mVolume{get { return Volume; }set { if (value >= 0 && value <= 1) Volume = value; }}/// <summary>/// 音频播放速度,默认为1(值属于[-3,3])/// </summary>public float mPitch{get { return Pitch; }set { if (value >= -3 && value <= 3) Pitch = value; }}/// <summary>/// 立体声位置,默认为0(值属于[-1,1]),若为-1则完全为左声道,若为1则完全为右声道/// </summary>public float mStereoPan{get { return StereoPan; }set { if (value >= -1 && value <= 1) StereoPan = value; }}/// <summary>/// 音频优先级,默认为128(值属于[0,256])/// </summary>public int mPriority{get { return Priority; }set { if (value >= 0 && value <= 256) Priority = value; }}/// <summary>/// 是否在场景启动时进行播放,默认为true/// </summary>public bool mPlayOnAwake { get => PlayOnAwake; set => PlayOnAwake = value; }/// <summary>/// 是否循环播放,默认为false/// </summary>public bool mLoop { get => Loop; set => Loop = value; }/// <summary>/// 是否启用立体声过渡,默认为false/// </summary>public bool mStereoTransition { get => StereoTransition; set => StereoTransition = value; }/// <summary>/// 立体声过渡的每帧时间间隔,默认为0.5(值属于[0.1,5])/// </summary>public float mStereoTransitionTimeSpan{get { return StereoTransitionTimeSpan; }set { if (value >= 0.1f && value <= 5) StereoTransitionTimeSpan = value; }}/// <summary>/// 立体声过渡值集合/// </summary>public float[] mStereoTransitionValues{get => StereoTransitionValues;set => StereoTransitionValues = value;}/// <summary>/// AudioSource组件/// </summary>public AudioSource mAudioSource { get => audioSource; set => audioSource = value; }/// <summary>/// 立体声过渡协程/// </summary>public IEnumerator mStereoPanTransitionCoroutine { get => stereoPanTransitionCoroutine; }/// <summary>/// 是否忽略总音量影响,默认为false/// </summary>public bool mIgnoreTotalVolume { get => IgnoreTotalVolume; set => IgnoreTotalVolume = value; }private AudioSource audioSource;//AudioSource组件private AudioManager audioManager;//音频管理器private bool isInit;//是否完成初始化private IEnumerator stereoPanTransitionCoroutine;//立体声过渡协程private float actualVolume;//实际音量private Action<float> totalVolumeChangedEvent;//总音量更改事件对象/// <summary>/// 将指定的AudioSource组件信息记录在新的AudioInfo实例中并返回它/// <para>p_audioSource:指定的AudioSource组件</para>/// <para>返回值:新的AudioInfo实例</para>/// </summary>public static AudioInfo Record(AudioSource p_audioSource){AudioInfo v_audioInfo = new AudioInfo();if (p_audioSource != null){v_audioInfo.TheAudioClip = p_audioSource.clip;v_audioInfo.Volume = p_audioSource.volume;v_audioInfo.Pitch = p_audioSource.pitch;v_audioInfo.StereoPan = p_audioSource.panStereo;v_audioInfo.Priority = p_audioSource.priority;v_audioInfo.PlayOnAwake = p_audioSource.playOnAwake;v_audioInfo.Loop = p_audioSource.loop;v_audioInfo.audioSource = p_audioSource;}return v_audioInfo;}public AudioInfo(){isInit = false;InitToDefault();}/// <summary>/// 播放音频/// </summary>public void Play(){if (audioSource != null && !audioSource.isPlaying){if (!IgnoreTotalVolume){if (actualVolume < 0 || actualVolume > 1) actualVolume = Volume;audioSource.volume = actualVolume;}else actualVolume = -1;audioSource.Play();}}/// <summary>/// 暂停音频播放/// </summary>public void Pause(){if (audioSource != null && audioSource.isPlaying){audioSource.Pause();audioSource.volume = Volume;}}/// <summary>/// 绑定音频管理器/// <para>p_audioManager:音频管理器</para>/// <para>声明1:若有需要可通过该方法将当前的AudioInfo与指定的音频管理器进行绑定</para>/// <para>声明2:绑定后将自动向指定的音频管理器添加当前的AudioInfo</para>/// </summary>public void BindAudioManager(AudioManager p_audioManager){audioManager = p_audioManager;if (audioManager != null){audioManager.mTotalVolumeChangedEvents -= totalVolumeChangedEvent;audioManager.mTotalVolumeChangedEvents += totalVolumeChangedEvent;}}/// <summary>/// 初始化为默认值/// </summary>public void InitToDefault(){if (!isInit){TheAudioClip = null;AudioName = "Audio";Volume = 1;Pitch = 1;StereoPan = 0;StereoTransitionValues = null;StereoTransitionTimeSpan = 0.5f;Priority = 128;StereoTransition = false;PlayOnAwake = true;Loop = false;audioSource = null;stereoPanTransitionCoroutine = StereoPanTransition();totalVolumeChangedEvent = (val) => TotalVolumeChangedEvent(val);actualVolume = -1;}}/// <summary>/// 将当前AudioInfo实例中的信息配置给指定的AudioSource组件/// <para>p_audioSource:指定的AudioSource组件</para>/// </summary>public void ShareTo(AudioSource p_audioSource){if (p_audioSource != null){p_audioSource.clip = TheAudioClip;p_audioSource.volume = Volume;p_audioSource.pitch = Pitch;p_audioSource.panStereo = StereoPan;p_audioSource.priority = Priority;p_audioSource.playOnAwake = PlayOnAwake;p_audioSource.loop = Loop;}}/// <summary>/// 将指定的AudioSource组件信息存储在当前AudioInfo实例中/// <para>p_audioSource:指定的AudioSource组件</para>/// </summary>public void SelfRecord(AudioSource p_audioSource){if (p_audioSource != null){TheAudioClip = p_audioSource.clip;Volume = p_audioSource.volume;Pitch = p_audioSource.pitch;StereoPan = p_audioSource.panStereo;Priority = p_audioSource.priority;PlayOnAwake = p_audioSource.playOnAwake;Loop = p_audioSource.loop;audioSource = p_audioSource;}}// 总音量更改事件// p_totalVolume:总音量// 若不忽略总音量影响,通过调用该事件将基于总音量和当前音量换算实际音量数值// 当TotleVolume为0时,实际音量为0;// 当TotleVolume为1或不属于[0,1)时,实际音量为Volume;// 当TotleVolume属于(0,1)时,实际音量为Volume * TotalVolumeprivate void TotalVolumeChangedEvent(float p_totalVolume){if (!IgnoreTotalVolume){if (p_totalVolume == 0) actualVolume = 0;else if (p_totalVolume > 0 && p_totalVolume < 1) actualVolume = Volume * p_totalVolume;else actualVolume = Volume;//运行时修改AudioSource音量if (audioSource != null) audioSource.volume = actualVolume;}else actualVolume = -1;}//立体声过渡协程private IEnumerator StereoPanTransition(){int currentIndex = 0;while (true){if (audioSource == null || !StereoTransition || StereoTransitionValues == null || StereoTransitionValues.Length == 0)yield break;audioSource.panStereo = StereoTransitionValues[currentIndex];yield return new WaitForSeconds(StereoTransitionTimeSpan);currentIndex = (currentIndex + 1) % StereoTransitionValues.Length;if (currentIndex == 0) StereoTransitionValues = StereoTransitionValues.Reverse().ToArray<float>();}}#if UNITY_EDITOR[NonSerialized] private bool isAudioClipLog;private bool isAudioNameLog;/// <summary>/// Inspector面板的数据更改检测/// </summary>public void ValidateCheck(){AudioClipCheck();AudioNameCheck();}//AudioClip检测private void AudioClipCheck(){if (TheAudioClip == null){if (!isAudioClipLog){Debug.LogWarning("Component: <b><color=orange>TheAudioClip</color></b> is null.");isAudioClipLog = true;}}else isAudioClipLog = false;}//AudioName检测private void AudioNameCheck(){if (String.IsNullOrEmpty(AudioName)){if (!isAudioNameLog){Debug.LogWarning("Property: <b><color=orange>AudioName</color></b> is empty.");isAudioNameLog = true;}}else isAudioNameLog = false;}
#endif}
}

AudioSourcePool.cs 

using System.Collections.Generic;
using UnityEngine;namespace Tools.AudioManagerAPI
{/// <summary>/// AudioSource组件池/// </summary>public class AudioSourcePool{/// <summary>/// 空闲的AudioSource数量/// </summary>public int mFreeCount { get => audioSources.Count; }private Stack<AudioSource> audioSources;//AudioSource组件集合private AudioManager audioManager;//AudioManager组件private AudioInfo defaultAudioInfo;//默认的AudioInfo/// <summary>/// 获取实例(单例模式)/// </summary>public static AudioSourcePool GetInstance(){return Handler.instance;}/// <summary>/// 绑定音频管理器/// <para>p_audioManager:音频管理器</para>/// </summary>public void BindAudioManager(AudioManager p_audioManager){if (p_audioManager != null) audioManager = p_audioManager;}/// <summary>/// 获取AudioSource组件/// <para>返回值:AudioSource组件</para>/// </summary>public AudioSource Get(){return DoGet();}/// <summary>/// 获取AudioSource组件并按照指定的AudioInfo为之配置属性/// <para>p_audioInfo:指定的AudioInfo</para>/// <para>返回值:AudioSource组件</para>/// </summary>public AudioSource Get(AudioInfo p_audioInfo){AudioSource v_audioSource = DoGet();p_audioInfo?.ShareTo(v_audioSource);return v_audioSource;}/// <summary>/// 归还指定的AudioSource组件/// <para>p_audioSource:指定的AudioSource组件</para>/// </summary>public void Return(AudioSource p_audioSource){if (p_audioSource != null){CleanAudioSource(p_audioSource);audioSources.Push(p_audioSource);}}class Handler{public static AudioSourcePool instance = new AudioSourcePool();}private AudioSourcePool(){audioSources = new Stack<AudioSource>();defaultAudioInfo = new AudioInfo();}//获取AudioSource组件的执行逻辑private AudioSource DoGet(){if (audioManager == null) return null;AudioSource v_audioSource = null;while (v_audioSource == null){if (audioSources.Count == 0) GenerateAudioSource();v_audioSource = audioSources.Pop();}return v_audioSource;}//生成AudioSource组件private void GenerateAudioSource(){if (audioManager?.gameObject != null){AudioSource v_audioSource = audioManager.gameObject.AddComponent<AudioSource>();audioSources.Push(v_audioSource);}}//清洗AudioSource组件private void CleanAudioSource(AudioSource p_audioSource){defaultAudioInfo.ShareTo(p_audioSource);}}
}

NumberRange.cs

using System.Collections.Generic;
using System.Linq;namespace Tools.AudioManagerAPI
{/// <summary>/// 数值范围数组工具类/// </summary>public static class NumberRange{/// <summary>/// 获取指定范围内指定步长的Float数值数组/// <para>p_start:起始值</para>/// <para>p_end:终点值</para>/// <para>p_step:步长值</para>/// <para>[ContainsEnd]:是否包括终点值,默认为false</para>/// <para>返回值:Float[]</para>/// </summary>public static float[] FloatRange(float p_start, float p_end, float p_step, bool ContainsEnd = false){if (!ContainsEnd) return DoFloatRange(p_start, p_end, p_step).ToArray();else{List<float> result = DoFloatRange(p_start, p_end, p_step).ToList();result.Add(p_end);return result.ToArray();}}//获取指定范围内指定步长的Float数值数组的执行逻辑static IEnumerable<float> DoFloatRange(float p_start, float p_end, float p_step){for (float i = p_start; i <= p_end; i += p_step){yield return i;}}}
}

界面展示

演示效果

自定义Unity组件AudioManager

资源下载

GitHub_AudioManager    百度网盘

如果这篇文章对你有帮助,请给作者点个赞吧! 

相关文章:

自定义Unity组件——AudioManager(音频管理器)

需求描述 在游戏开发中&#xff0c;音频资源是不可或缺的&#xff0c;通常情况下音频资源随机分布&#xff0c;各个音频的操作和管理都是各自负责&#xff0c;同时对于音频的很多操作逻辑都是大同小异的&#xff0c;这就造成了许多冗余代码的堆叠&#xff0c;除此之外在获取各类…...

leetcode 558 设计内存文件系统

题目 Design an in-memory file system to simulate the following functions: ls: Given a path in string format. If it is a file path, return a list that only contains this files name. If it is a directory path, return the list of file and directory namesin th…...

Haproxy负载均衡群集

HAproxy搭建Web群集一、Web集群调度器1、常见的Web集群调度器2、常用集群调度器的优缺点&#xff08;LVS ,Nginx,Haproxy)2.1 Nginx2.2 LVS2.3 Haproxy 3、LVS、Nginx、HAproxy的区别 二、Haproxy1、简介2、Haproxy应用分析3、HAProxy的主要特性4、Haproxy调度算法&#xff08;…...

什么是面包屑导航?

面包屑导航(Breadcrumb Navigation)这个概念来自童话故事“汉赛尔和格莱特”&#xff0c;当汉赛尔和格莱特穿过森林时&#xff0c;不小心迷路了&#xff0c;但是他们发现沿途走过的地方都撒下了面包屑&#xff0c;让这些面包屑来帮助他们找到回家的路。 在网站应用中&#xff0…...

VS2019创建GIt仓库时剔除文件或目录

假设本地有解决方案“SomeSolution” 1、首先”团队资源管理器“-“创建Git存储库”&#xff0c;选择“仅限本地”、“创建” VS会在解决方案目录下自动生成.gitattributes、.gitignore 2、编辑gitignore&#xff0c;直接拖到VS里或者用记事本打开。添加要剔除的文件或文件夹…...

计算机等级考试—信息安全三级真题六

目录 一、单选题 二、填空题 三、综合题 一、单选题...

vue循环滚动字幕

在Vue.js中创建一个循环滚动字幕的效果通常需要使用一些CSS和JavaScript来实现。以下是一个简单的示例&#xff0c;展示如何使用Vue.js创建一个循环滚动字幕的效果&#xff1a; 首先&#xff0c;在HTML中创建一个Vue实例&#xff0c;并添加一个包含滚动字幕的容器元素&#xff…...

扩展pytest接口自动化框架-MS数据解析功能

【软件测试行业现状】2023年了你还敢学软件测试&#xff1f;未来已寄..测试人该何去何从&#xff1f;【自动化测试、测试开发、性能测试】 开篇 MeterSphere的数据源通过html页面上传后&#xff0c;需要将请求方式进行拆分。 get接口的参数&#xff0c;常以params的方式进行传…...

docker容器安装MongoDB数据库

一&#xff1a;MongoDB数据库 1.1 简介 MongoDB是一个开源、高性能、无模式的文档型数据库&#xff0c;当初的设计就是用于简化开发和方便扩展&#xff0c;是NoSQL数据库产品中的一种。是最 像关系型数据库&#xff08;MySQL&#xff09;的非关系型数据库。 它支持的数据结构…...

Python机器学习实战-特征重要性分析方法(3):迭代删除法:Leave-one-out(附源码和实现效果)

实现功能 迭代地每次删除一个特征并评估准确性 实现代码 from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score impo…...

Go的error接口

从本书的开始&#xff0c;我们就已经创建和使用过神秘的预定义error类型&#xff0c;而且没有解释它究竟是什么。实际上它就是interface类型&#xff0c;这个类型有一个返回错误信息的单一方法&#xff1a; type error interface { Error() string } 创建一个error最简单的方…...

RabbitMQ 集群 - 普通集群、镜像集群、仲裁队列

目录 一、RabbitMQ 集群 1.1、前言 1.2、普通集群 1.3、镜像集群 1.4、仲裁队列 一、RabbitMQ 集群 1.1、前言 前面我们已经解决了消息可靠性问题&#xff0c;以及延迟消息问题 和 消息堆积问题. 这最后一章&#xff0c;我们就来解决以下 mq 的可用性 和 并发能力. 1.2、…...

高项新版教程(第四版)解读+学习指导

第四版主要内容 技术部分 信息化教程、软件工程、网络技术是原来的&#xff0c;学习原来的录播。 新基建、工业互联网、车联网、农业现代化、数字化转型、元宇宙等是新增&#xff0c;以直播讲。 管理部分 变化不是太大 。 整合管理、人力变为资源管理、风险管理新增内容。 …...

【Debian】Debian10.0.0安装选项问答

debian的LXQT是什么&#xff1f; LXQT是一套轻量级的桌面环境,主要基于Qt框架开发。 LXQT在debian中的具体特点包括: - 使用Openbox作为窗口管理器,提供平铺式窗口布局。 - 文件管理器为PCManFM-Qt。 - 设置中心集成 debconf 配置界面。 - 支持GTK和Qt应用程序。 - 资源消耗较低…...

【基于React-Native做位置信息获取,并展示出来】

基于React-Native做位置信息获取 在这个里面最重要的是两个部分&#xff0c;一个是位置定位的权限获取&#xff0c;一个是实时位置的监听&#xff0c;在安卓项目中&#xff0c;在 android/app/src/main/AndroidManifest.xml该文件下&#xff0c;在< manifest > 标签内写…...

ansible安装、点对点Ad-Hoc、模块、剧本Playbook

DevOps: 官网&#xff1a;https://docs.ansible.com 自动化运维工具对比 C/S 架构:客户端/服务端 Puppet:基于 Ruby 开发,采用 C/S 架构,扩展性强,基于 SSL,远程命令执行相对较弱 SaltStack:基于 Python 开发,采用 C/S 架构,YAML使得配置脚本更简单.需要配置客户端及服务器…...

Ceph入门到精通-ceph pool 删除导致 misplaced 的原因

misplaced 的原因 Ceph中的misplaced对象是指将对象&#xff08;或对象的副本&#xff09;存储在错误的位置上&#xff0c;这可能会导致性能下降或数据不一致的问题。在删除Ceph池时&#xff0c;可能会导致misplaced的原因有以下几个&#xff1a; 删除过程中的操作失误&#x…...

计算机组成原理课程设计

操作控制和顺序控制 操作控制就是由各种微命令来构成的顺序控制就是由P测试和后续微地址构成的 这就构成了整个微指令的三个部分 访存指令就是实现对主存中的数据进行访问或存储 一、 操作控制字段是由各种微命令来构成的&#xff0c;这些微命令怎么来设计&#xff1f; 一个萝卜…...

《从菜鸟到大师之路 MySQL 篇》

《从菜鸟到大师之路 MySQL 篇》 数据库是什么 数据库管理系统&#xff0c;简称为DBMS&#xff08;Database Management System&#xff09;&#xff0c;是用来存储数据的管理系统。 DBMS 的重要性 无法多人共享数据 无法提供操作大量数据所需的格式 实现读取自动化需要编程…...

使用qt完善对话框功能

1、 完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两…...

Day 03 python学习笔记

位运算 基于二进制的运算&#xff08;计算机的底层基于位运算&#xff09; 计算机最小单位&#xff1a;bit (比特/位/二进制) 1byte&#xff08;字节&#xff09; 8bit &#xff08; 0000 0000&#xff09; &&#xff1a;与 &#xff08;全真为真&#xff0c;一假则…...

优化类问题概述

数学建模系列文章&#xff1a; 以下是个人在准备数模国赛时候的一些模型算法和代码整理&#xff0c;有空会不断更新内容&#xff1a; 评价模型&#xff08;一&#xff09;层次分析法&#xff08;AHP&#xff09;,熵权法&#xff0c;TOPSIS分析 及其对应 PYTHON 实现代码和例题…...

第一个 Go 程序“hello,world“ 与 main 函数

第一个 Go 程序"hello&#xff0c;world" 与 main 函数 文章目录 第一个 Go 程序"hello&#xff0c;world" 与 main 函数一.创建“hello&#xff0c;world”示例程序二. “hello&#xff0c;world” 程序结构拆解三、main 函数四、Go 语言中程序是怎么编译…...

MySQL缓冲池Buffer Pool

前言 ​ 在应用系统中&#xff0c;为加速数据访问&#xff0c;会把高频的数据放在「缓存」(Redis、MongoDB)里&#xff0c;减轻数据库的压力。在操作系统中&#xff0c;为了减少磁盘IO&#xff0c;同时为了快速响应&#xff0c;引入了「缓冲池」(buffer pool)机制。 ​ MySQL…...

springboot实现发送邮箱验证码

准备工作 在邮箱官网开放SMTP授权&#xff0c;获取相应密钥&#xff0c;才可以进行发送邮件 这里以网易163邮箱为例&#xff0c;登录邮箱后&#xff0c;依次点击“设置-POP3/SMTP/IMAP” &#xff0c;然后开启SMTP服务。这时候会提示一个授权码&#xff0c;例如&#xff1a;H…...

ESP8266使用记录(三)

通过udp把mpu6050数据发送到PC端 /********************************************************************** 项目名称/Project : 零基础入门学用物联网 程序名称/Program name : ESP8266WiFiUdp_12 团队/Team : 太极创客团队 / Taichi-Maker (w…...

基于微信小程序的在线视频课程学习平台设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户微信端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉…...

CloudCompare 二次开发(15)——点云添加高斯噪声

目录 一、概述二、代码集成三、结果展示一、概述 不依赖任何第三方点云相关库,使用CloudCompare编程实现点云添加高斯噪声。添加高斯噪声的算法原理见:PCL 点云添加高斯噪声并保存。 二、代码集成 1、mainwindow.h文件public中添加: void doActionAddGassNoise(); //…...

一波免费、好用的API接口分享

全国快递物流地图轨迹查询&#xff1a;【H5物流轨迹、单号识别】通过物流单号和收寄件地址&#xff0c;自动评估物流时效&#xff0c;并在地图中展示包裹运输轨迹。包括顺丰、圆通、申通等主流快递公司。自动识别快递公司及单号&#xff0c;实时查询&#xff0c;稳定高效&#…...

Android App ~ LiveData

LiveData 两种更新数据方式 setValue(T value)postValue(T value) setValue()只能在主线程中调用&#xff0c;postValue()可以在任何线程中调用。 MutableLiveData 1.首先LiveData其实与数据实体类(POJO类)是一样的东西,它负责暂存数据. 2.其次LiveData其实也是一个观察者…...

企业网站开发实训报告/百度网站的网址是什么

在本文中&#xff0c;我将介绍MySQL执行GROUP BY的四种方法。In this blog post, I’ll look into four ways MySQL executes GROUP BY.在我的上一篇文章中&#xff0c;我们知道了通过索引或者其他的方式获取数据可能不是语句执行最耗时的操作。比如&#xff0c;MySQL 的GROUP …...

有什么做图文长图的网站吗/营销型网站名词解释

九年前&#xff0c;当史蒂夫布兰克(Steve Blank)在《Wired》杂志封面上出现的时候&#xff0c;他的游戏公司&#xff0c;Rocket Science Games&#xff0c;曾有望彻底改变电子游戏行业。在当时&#xff0c;布兰克完全没有被那些持怀疑态度的评论困扰到。 “当时我觉得我就是个…...

建设导航网站费用/网页设计html代码大全

Perl命令行应用介绍Perl语言中有很多Perl命令行参数.通过它们,我们有机会写出更简单的程序&#xff0c;在这篇文章里我们来了解一些常用的参数。第一部分&#xff1a;SafetyNetOptions安全网参数在使用Perl尝试一些聪明(或stupid)的想法时,错误难免会发生.有经验的Perl程序员常…...

济南机关建设网站/国外b站浏览器

引用操纵对象 拥有一个引用&#xff0c;但是不一定需要有一个对象与他关联。&#xff08;不一定有遥控的对象&#xff09;String s;---创建的是引用&#xff0c;并不是对象。&#xff08;创建了一个遥控器&#xff0c;但是遥控器没有指向的对象&#xff09;一种安全的创建方法&…...

女性做网站/百度搜索名字排名优化

异常是一种封装了反常程序事件信息的对象。在C#中用异常来处理错误和反常情况&#xff0c;当异常抛出时&#xff0c;当前函数的执行会停止&#xff0c;堆栈展开&#xff0c;直到找到正确的异常处理代码。如果异常得到处理&#xff0c;程序会解决问题并继续执行。即使程序不能继…...

深圳ui设计师工资/搜索引擎推广和优化方案

来自:http://jishus.org/?p467#more-467很多学习C的人其实都是从Dev C开始的&#xff0c;因為VC对一般初新者而言方便是方便&#xff0c;但無法学习到標準的C語言&#xff08;ANSI C&#xff09;&#xff0c;原因就是VC裡面有太多的函式是屬於.NET的&#xff0c;如果改用其他編…...