feat: implement volume step feature

- Add volume step functionality with VolumeStepResolver class
- Add VolumeStepThreshold, Volume, and VolumeStep properties to AudioObject
- Integrate volume step resolution in SfxSystem for dynamic audio volume control
This commit is contained in:
2026-04-13 16:56:34 +08:00
parent 0f204aa794
commit 31d117851e
15 changed files with 192 additions and 11 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3cc8ce9203c4a4d03b07e0ae9a3cf219
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 3f3b3e40c5ec34183af765f15c1ce362
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: true
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: e640dc3c900064126804112a521170ea
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: be8f215dba3904ddf9f72fc064c97e71
AudioImporter:
externalObjects: {}
serializedVersion: 7
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
+15 -3
View File
@@ -1012,7 +1012,7 @@ GameObject:
- component: {fileID: 1394234350}
- component: {fileID: 1394234349}
m_Layer: 5
m_Name: Button (Legacy)
m_Name: PlaySound
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -1085,15 +1085,27 @@ MonoBehaviour:
- m_Target: {fileID: 2093584670}
m_TargetAssemblyTypeName: OCES.Audio.AudioSystem, Assembly-CSharp
m_MethodName: Play
m_Mode: 5
m_Mode: 3
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 11
m_IntArgument: 57
m_FloatArgument: 0
m_StringArgument: rain
m_BoolArgument: 0
m_CallState: 2
- m_Target: {fileID: 2093584670}
m_TargetAssemblyTypeName: OCES.Audio.AudioSystem, Assembly-CSharp
m_MethodName: Play
m_Mode: 3
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 58
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &1394234350
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -130,6 +130,23 @@ public partial class AudioObject : IBinarySerializable
/// </summary>
public bool RandomType { get; set; }
/// <summary>
/// Volume Step阈值 ms
/// </summary>
public uint VolumeStepThreshold { get; set; }
/// <summary>
/// 起始音量
/// dB
/// </summary>
public int Volume { get; set; }
/// <summary>
/// 音量变化幅度
/// dB
/// </summary>
public int VolumeStep { get; set; }
public void DeSerialize(BinaryReader reader)
{
@@ -166,6 +183,9 @@ public partial class AudioObject : IBinarySerializable
BlendCrossFadeType = (BlendCrossFadeType)reader.ReadByte();
LimitRepetition = reader.ReadByte();
RandomType = reader.ReadBoolean();
VolumeStepThreshold = reader.ReadUInt32();
Volume = reader.ReadInt32();
VolumeStep = reader.ReadInt32();
}
public void Serialize(BinaryWriter writer)
@@ -202,6 +222,9 @@ public partial class AudioObject : IBinarySerializable
writer.Write((byte)BlendCrossFadeType);
writer.Write(LimitRepetition);
writer.Write(RandomType);
writer.Write(VolumeStepThreshold);
writer.Write(Volume);
writer.Write(VolumeStep);
}
}
@@ -66,6 +66,13 @@ public static class AudioObjectDefinitions
{ "Chinese Number 08", 49 },
{ "Chinese Number 09", 49 },
{ "Chinese Number 10", 49 },
{ "Bar", 52 },
{ "Beat", 53 },
{ "Grid", 54 },
{ "NVDice", 55 },
{ "NVHeartbeats", 56 },
{ "au_sfx_notice_level_countDown_edge", 57 },
{ "au_sfx_notice_level_countDown_time", 58 },
{ "sfx_amb_desert", 2000 },
{ "sfx_amb_forest", 2001 },
{ "sfx_anim_common_item_fly", 3000 },
@@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using OCES.Audio.HandWritten;
using UnityEngine;
using UnityEngine.Audio;
@@ -33,6 +34,7 @@ namespace OCES.Audio
AudioSourcePool m_pool;
AudioContainerSelector m_containerSelector;
PitchStepResolver m_pitchStepResolver;
VolumeStepResolver m_volumeStepResolver;
#if UNITY_EDITOR
void Update()
@@ -74,6 +76,7 @@ namespace OCES.Audio
this.m_pool = pool;
this.m_containerSelector = new AudioContainerSelector();
this.m_pitchStepResolver = new PitchStepResolver();
this.m_volumeStepResolver = new VolumeStepResolver();
AudioMixerGroup[] sfx = Find("Master/Regular/SFX");
if (sfx.Length > 0) this.m_sfxGroup = sfx[0];
@@ -173,7 +176,8 @@ namespace OCES.Audio
// 执行播放
float pitch = this.m_pitchStepResolver.ResolvePitch(audioObject, now); //算一下用不用变调
PlayNewSound(audioObject, pitch);
float volume = this.m_volumeStepResolver.ResolveVolume(audioObject, now);
PlayNewSound(audioObject, pitch, volume);
this.m_lastPlayTime[audioObject.Id] = now;
onPlay?.Invoke();
}
@@ -182,12 +186,13 @@ namespace OCES.Audio
// 播放逻辑
// ─────────────────────────────────────────────
void PlayNewSound(AudioObject audioObject, float pitch)
void PlayNewSound(AudioObject audioObject, float pitch, float volume)
{
ActiveSound active = new()
{
AudioObject = audioObject,
Pitch = pitch,
Volume = volume,
State = ActiveSoundState.Pending,
StartTime = Time.realtimeSinceStartupAsDouble,
};
@@ -207,7 +212,7 @@ namespace OCES.Audio
/// <summary>
/// 连续容器播放协程(Random / Sequence 持续模式)
/// </summary>
IEnumerator PlayContainerContinuous(AudioSource source, AudioObject audioObject, ActiveSound chainActive, int startIndex, float pitch)
IEnumerator PlayContainerContinuous(AudioSource source, AudioObject audioObject, ActiveSound chainActive, int startIndex, float pitch, float volume)
{
bool isRandom = audioObject.ContainerType == ContainerType.Random;
@@ -229,7 +234,7 @@ namespace OCES.Audio
limitRepetition);
// 配置并播放
if (!SetupSource(source, audioObject, pitch, index))
if (!SetupSource(source, audioObject, pitch, volume,index))
{
Debug.LogError($"音频文件未找到:{audioObject.Name[index]}");
yield break;
@@ -334,7 +339,7 @@ namespace OCES.Audio
return this.m_sfxGroup;
}
bool SetupSource(AudioSource source, AudioObject audioObject, float pitch, int clipIndex = 0)
bool SetupSource(AudioSource source, AudioObject audioObject, float pitch, float volume, int clipIndex = 0)
{
AudioClip clip = Resources.Load<AudioClip>($"Audios/{audioObject.Name[clipIndex]}"); // TODO 抽象同一资源加载接口
if (!clip)
@@ -348,6 +353,7 @@ namespace OCES.Audio
source.priority = audioObject.Priority;
source.outputAudioMixerGroup = GetMixerGroup(audioObject.MixingType);
source.pitch = pitch;
source.volume = volume;
return true;
}
@@ -371,6 +377,7 @@ namespace OCES.Audio
{
AudioObject audioObject = active.AudioObject;
float pitch = active.Pitch;
float volume = active.Volume;
// =======================
// Blend(每个clip一个ActiveSound
@@ -385,10 +392,11 @@ namespace OCES.Audio
{
AudioObject = audioObject,
Pitch = pitch,
Volume = volume,
State = ActiveSoundState.Playing
};
if (!SetupSource(source, audioObject, pitch, i))
if (!SetupSource(source, audioObject, pitch, volume, i))
{
this.m_pool.ReturnToPool(source.gameObject);
continue;
@@ -424,7 +432,7 @@ namespace OCES.Audio
int start = audioObject.ContainerType == ContainerType.Random ? -1 : 0;
active.Coroutine = StartCoroutine(
PlayContainerContinuous(sourceSingle, audioObject, active, start, pitch)
PlayContainerContinuous(sourceSingle, audioObject, active, start, pitch, volume)
);
return;
@@ -440,7 +448,7 @@ namespace OCES.Audio
_ => 0
};
if (!SetupSource(sourceSingle, audioObject, pitch, index))
if (!SetupSource(sourceSingle, audioObject, pitch, volume, index))
{
m_pool.ReturnToPool(sourceSingle.gameObject);
return;
@@ -506,6 +514,7 @@ namespace OCES.Audio
public int CurrentLoopCount;
public Coroutine Coroutine;
public float Pitch;
public float Volume;
public ActiveSoundState State;
}
}
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using UnityEngine;
namespace OCES.Audio.HandWritten
{
public class VolumeStepResolver
{
readonly Dictionary<uint, int> m_volumeStepCounts = new();
readonly Dictionary<uint, double> m_volumeStepLastTime = new();
internal float ResolveVolume(AudioObject audioObject, double time)
{
// 计算一下配置的音量是多少
float baseVolume = Mathf.Pow(10,audioObject.Volume / 20f);
// 优化版。看看表现,要是确实Mathf.Pow造成性能卡点了就用这个。
//float baseVolume = audioObject.Volume == 0 ? 1f : Mathf.Pow(10,audioObject.Volume / 20f);
if (audioObject.VolumeStepThreshold == 0) //没配置VolumeStep
{
return baseVolume;
}
// 超时了,或者没播过
if (!this.m_volumeStepLastTime.TryGetValue(audioObject.Id, out double lastVolumeStepTime)
|| time - lastVolumeStepTime > audioObject.VolumeStepThreshold)
{
this.m_volumeStepCounts[audioObject.Id] = 0;
this.m_volumeStepLastTime[audioObject.Id] = time;
return baseVolume;
}
//命中了
int volumeStepCount = this.m_volumeStepCounts[audioObject.Id];
volumeStepCount = this.m_volumeStepCounts[audioObject.Id] = volumeStepCount + 1;
this.m_volumeStepLastTime[audioObject.Id] = time;
return Mathf.Clamp(Mathf.Pow(10, (audioObject.Volume + audioObject.VolumeStep * volumeStepCount) / 20f), 0f, 1f);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0059d6fc851c474a97bc05f6385f9a48
timeCreated: 1776067889