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:
@@ -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
|
||||
Reference in New Issue
Block a user