简单处理 显示设置(Display Settings) 之后就是另外 音频设置(Audio Settings) 相关
音频部分就相对来说比较简单, 游戏内部对音乐管理需求不高的话, 直接采用如下配置:
主音量(Master Volume): 全局整体游戏总音量
背景音乐(BGM Volume): 游戏当中涉及到场景/剧情等背景音乐
游戏音效(SFX Volume): 游戏当中释放技能/触发交互的弹出音乐
如果是偏剧情向(GalGame)之类, 那么需要追加人物音量(Voice Volume), 甚至要单独另开配置页面来为每个角色添加音量
音量部分都是采用 0.0~1.0 的浮点数代表百分比处理(0代表静音), 所以不会专门单独定义枚举处理, 处理也就相对显示设置简单
而 Godot 内部会用到的 API 如下所示
API 全路径
方法/属性用途
参数/返回值
核心说明
AudioServer.GetBusIndex(string busName)
通过总线名获取音频总线索引
参数:string(音频总线名,如Master/BGM/SFX) 返回值:int(总线索引,-1表示未找到)
音频总线操作的基础,避免硬编码索引,通过名称匹配更灵活
AudioServer.SetBusVolumeLinear(int busIndex, float volumeDb)
设置音频总线音量
参数:int(总线索引)、float(音量浮点,如0.0~1.0) 返回值:void
Godot原生音量 0~1 浮点值修改方式
AudioServer.SetBusVolumeLinear(int busIndex)
获取音频总线当前音量
参数:int(总线索引) 返回值:float(分贝音量)
获取 0~1 浮点值音量,方便上层业务使用
Mathf.LinearToDb(float linear)
线性音量(0~1)转分贝音量(Db)
参数:float(0~1线性值,0=静音,1=最大) 返回值:float(分贝值,0对应最大,-80对应静音)
音频总线音量转换
Mathf.DbToLinear(float db)
分贝音量(Db)转线性音量(0~1)
参数:float(分贝值) 返回值:float(0~1线性值)
音频总线杜比转换
Mathf.IsEqualApprox(float a, float b)
浮点值近似相等判断
参数:float a/float b(待比较的两个浮点值) 返回值:bool(true=近似相等)
浮点型音量的重复判断核心,避免浮点数精度问题导致的重复设置
Mathf.Clamp(float value, float min, float max)
数值范围裁剪
参数:float(待裁剪值)、float(最小值)、float(最大值) 返回值:float(裁剪后的值)
保证音量始终在0~1之间,避免传入无效值导致音频异常
这里需要知道什么是 音频总线(AudioBus), 一般来说音频播放都会设置多条通道, 每条通道专门对应去播放不同音源线路
而 Godot 创建播放线路就需要在编辑器面板操作创建:
这里我创建了 BGM 和 SFX 两个音源通道并将其划分到 Master 之下, 这里对应的 Master/BGM/SFX 就是主要音量总线名称
代码处理
在创建完音频管线之后, 现在就需要定义 Data 数据本地落地容器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 namespace P21Game.Manager.Settings.Data ;public class GodotAudioSettings { public string MasterVolumeName { get ; set ; } = "Master" ; public float MasterVolume { get ; set ; } = 0.5f ; public string BgmVolumeName { get ; set ; } = "BGM" ; public float BgmVolume { get ; set ; } = 0.5f ; public string SfxVolumeName { get ; set ; } = "SFX" ; public float SfxVolume { get ; set ; } = 0.5f ; public override string ToString () { return $"{nameof (MasterVolumeName)} : {MasterVolume} , {nameof (BgmVolumeName)} : {BgmVolume} , {nameof (SfxVolumeName)} : {SfxVolume} " ; } }
之后在 ISettingsManager 声明通用修改方法即可, 这里我直接把之前整个设置接口搬运过来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 using System;using P21Utility.CDI;using P21Utility.Definitions.Settings.Display;namespace P21Game.Manager.Settings ;public interface ISettingsManager : IManager { int IManager.Priority => -1 ; public void LoadSettings () ; public void SaveSettings () ; #region 显示设置 public event Action OnDisplayChanged; public Mode DisplayMode { set ; get ; } public Resolution DisplayResolution { set ; get ; } public bool DisplayVSync { set ; get ; } public Fps DisplayFps { set ; get ; } #endregion #region 音频设置 public event Action OnAudioChanged; public float MasterVolume { set ; get ; } public float BgmVolume { set ; get ; } public float SfxVolume { set ; get ; } #endregion #region 游戏设置 #endregion #region 操作设置 #endregion }
最后就是配置 GodotSettingsManager 具体管理器功能实现, 这里篇幅太多只挑选之前显示设置功能新增的部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 using System;using System.Collections.Generic;using System.IO;using System.Text;using System.Text.Json;using Godot;using P21Game.Manager.Settings.Data;using P21Game.Manager.Settings.Extensions;using P21Utility.Definitions.Settings.Display;using Serilog;namespace P21Game.Manager.Settings.Impl ;public class GodotSettingsManager (string filename ) : ISettingsManager{ public void LoadSettings () { if (!File.Exists(filename)) { InitSettings(); return ; } var json = File.ReadAllText(filename, new UTF8Encoding(false )); var settings = JsonSerializer.Deserialize<Dictionary<string , JsonDocument>>(json); try { LoadDisplaySettings(settings.TryGetValue(nameof (GodotDisplaySettings), out var displaySettings) ? displaySettings.Deserialize<GodotDisplaySettings>() : new GodotDisplaySettings()); Log.Information("Loaded Godot Display Settings( {DisplaySettings} )" , _displaySettings.ToString()); } catch (Exception exception) { Log.Error(exception, "Failed to load Godot Display Settings" ); LoadDisplaySettings(new GodotDisplaySettings()); } try { LoadAudioSettings(settings.TryGetValue(nameof (GodotAudioSettings), out var audioSettings) ? audioSettings.Deserialize<GodotAudioSettings>() : new GodotAudioSettings()); Log.Information("Loaded Godot Audio Settings( {AudioSettings} )" , _audioSettings.ToString()); } catch (Exception exception) { Log.Error(exception, "Failed to load Godot Audio Settings" ); LoadAudioSettings(new GodotAudioSettings()); } } private void LoadAudioSettings (GodotAudioSettings audioSettings ) { _audioSettings = audioSettings; var masterIdx = AudioServer.GetBusIndex(_audioSettings.MasterVolumeName); if (masterIdx > -1 ) AudioServer.SetBusVolumeLinear(masterIdx, Mathf.Clamp(audioSettings.MasterVolume, 0 , 1 )); var bgmIdx = AudioServer.GetBusIndex(_audioSettings.BgmVolumeName); if (bgmIdx > -1 ) AudioServer.SetBusVolumeLinear(bgmIdx, Mathf.Clamp(audioSettings.BgmVolume, 0 , 1 )); var sfxIdx = AudioServer.GetBusIndex(_audioSettings.SfxVolumeName); if (sfxIdx > -1 ) AudioServer.SetBusVolumeLinear(sfxIdx, Mathf.Clamp(audioSettings.SfxVolume, 0 , 1 )); } private void InitSettings () { LoadDisplaySettings(new GodotDisplaySettings()); LoadAudioSettings(new GodotAudioSettings()); SaveSettings(); } public void SaveSettings () { try { Dictionary<string , object > settings = new () { [nameof(GodotDisplaySettings) ] = _displaySettings ?? new GodotDisplaySettings(), [nameof(GodotAudioSettings) ] = _audioSettings ?? new GodotAudioSettings(), }; var jsonSettings = JsonSerializer.Serialize(settings); Log.Information("Saving Godot Settings: {JsonSettings}" , jsonSettings); var dirname = Path.GetDirectoryName(filename); if (!string .IsNullOrEmpty(dirname) && !Directory.Exists(dirname)) { Directory.CreateDirectory(dirname); } File.WriteAllText(filename, jsonSettings, new UTF8Encoding(false )); } catch (Exception exception) { Log.Error(exception, "Failed to save Godot Settings" ); } } #region 音量设置 private GodotAudioSettings _audioSettings; public event Action OnAudioChanged; public float MasterVolume { get => _audioSettings.MasterVolume; set { var volume = Mathf.Clamp(value , 0 , 1 ); if (Mathf.IsEqualApprox(volume, _audioSettings.MasterVolume)) return ; var busIdx = AudioServer.GetBusIndex(_audioSettings.MasterVolumeName); if (busIdx == -1 ) return ; AudioServer.SetBusVolumeLinear(busIdx, volume); _audioSettings.MasterVolume = volume; OnAudioChanged?.Invoke(); SaveSettings(); } } public float BgmVolume { get => _audioSettings.BgmVolume; set { var volume = Mathf.Clamp(value , 0 , 1 ); if (Mathf.IsEqualApprox(volume, _audioSettings.BgmVolume)) return ; var busIdx = AudioServer.GetBusIndex(_audioSettings.BgmVolumeName); if (busIdx == -1 ) return ; AudioServer.SetBusVolumeLinear(busIdx, volume); _audioSettings.BgmVolume = volume; OnAudioChanged?.Invoke(); SaveSettings(); } } public float SfxVolume { get => _audioSettings.SfxVolume; set { var volume = Mathf.Clamp(value , 0 , 1 ); if (Mathf.IsEqualApprox(volume, _audioSettings.SfxVolume)) return ; var busIdx = AudioServer.GetBusIndex(_audioSettings.SfxVolumeName); if (busIdx == -1 ) return ; AudioServer.SetBusVolumeLinear(busIdx, volume); _audioSettings.SfxVolume = volume; OnAudioChanged?.Invoke(); SaveSettings(); } } #endregion }
这里代码添加完成就是准备游戏 UI 方面的调整功能, 在编辑器上添加滑动节点即可( Slider 相关节点):
最后就是在 Main 脚本之后添加这部分监听代码即可, 一般没有什么太大难度:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 using Godot;using P21Game.Manager.Settings;using P21Game.Manager.Settings.Extensions;using P21Utility.CDI;using P21Utility.Definitions.Settings.Display;using Serilog;namespace P21Game ;public partial class Main : Node { [Export ] public OptionButton ModeSelect; [Export ] public OptionButton ResolutionSelect; [Export ] public Button UpdateButton; [Export ] public HSlider MasterVolumeSlider; [Export ] public HSlider BgmVolumeSlider; [Export ] public HSlider SfxVolumeSlider; private ISettingsManager _settingsManager; public override void _Ready() { _settingsManager = ManagerRegistry.Get<ISettingsManager>(); if (ModeSelect != null ) { ModeSelect.Clear(); var active = -1 ; foreach (var mode in GodotDisplayModeExtensions.GetModes()) { var modeId = (int )mode; ModeSelect.AddItem($"{mode} - {modeId} " , modeId); if (_settingsManager.DisplayMode.Equals(mode)) active = modeId; } if (active > -1 ) ModeSelect.Selected = ModeSelect.GetItemIndex(active); Log.Information("Selected Mode: {active}" , active); } if (ResolutionSelect != null ) { ResolutionSelect.Clear(); var active = -1 ; foreach (var resolution in GodotDisplayResolutionExtensions.GetResolutions()) { var resId = (int )resolution; var (w, h) = resolution.GetSize(); ResolutionSelect.AddItem($"{w} x {h} ({resolution.ToString()} )" , resId); if (_settingsManager.DisplayResolution.Equals(resolution)) active = resId; } if (active > -1 ) ResolutionSelect.Selected = ResolutionSelect.GetItemIndex(active); Log.Information("Selected Resolution: {active}" , active); } if (UpdateButton != null ) { UpdateButton.Pressed += UpdateDisplayResolution; } _settingsManager.OnDisplayChanged += () => { Log.Information("Display Settings Updated" ); }; if (MasterVolumeSlider != null ) { MasterVolumeSlider.MinValue = 0.0f ; MasterVolumeSlider.MaxValue = 100.0f ; MasterVolumeSlider.Value = _settingsManager.MasterVolume * 100 ; Log.Information("Master Volume Slider: {masterVolume}" , MasterVolumeSlider.Value); MasterVolumeSlider.ValueChanged += volume => _settingsManager.MasterVolume = (float )volume/100 ; } if (BgmVolumeSlider != null ) { BgmVolumeSlider.MinValue = 0.0f ; BgmVolumeSlider.MaxValue = 100.0f ; BgmVolumeSlider.Value = _settingsManager.BgmVolume * 100 ; Log.Information("Bgm Volume Slider: {bgmVolume}" , BgmVolumeSlider.Value); BgmVolumeSlider.ValueChanged += volume => _settingsManager.BgmVolume = (float )volume/100 ; } if (SfxVolumeSlider != null ) { SfxVolumeSlider.MinValue = 0.0f ; SfxVolumeSlider.MaxValue = 100.0f ; SfxVolumeSlider.Value = _settingsManager.SfxVolume * 100 ; Log.Information("Sfx Volume Slider: {sfxVolume}" , SfxVolumeSlider.Value); SfxVolumeSlider.ValueChanged += volume => _settingsManager.SfxVolume = (float )volume/100 ; } _settingsManager.OnAudioChanged += () => { Log.Information("Audio Settings Updated" ); }; } private void UpdateDisplayResolution () { var modeId = ModeSelect.GetSelectedId(); var mode = (Mode)modeId; _settingsManager.DisplayMode = mode; var id = ResolutionSelect.GetSelectedId(); var resolution = (Resolution)id; _settingsManager.DisplayResolution = resolution; } }
这里没有涉及到 UI 等界面操作, 尽可能保证不会牵涉到太多知识点将人绕晕, 最后制作出来的效果如下所示:
播放音乐
配置好音量控制之后就是将音乐挂载到对应 Bus 之中播放, Godot 只需要在节点当中配置 Bus 即可(使用 AudioStreamPlayer 节点):
这样就完成游戏音乐的设置, 但是这里主要针对的2D游戏, 对于 3d 游戏的音效复杂程度更高(场景/人物之类的不同音效处理)