feat: 音频参数保存功能
This commit is contained in:
@@ -42,6 +42,7 @@ namespace AibisDream.Kit
|
||||
private EventInstance _voiceInstance;
|
||||
private readonly Dictionary<string, EventInstance> _musicDict = new();
|
||||
private readonly Dictionary<string, EventInstance> _sfxDict = new();
|
||||
private YarnGlobalParameterRegistry _yarnGlobalParameters;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -52,6 +53,9 @@ namespace AibisDream.Kit
|
||||
private AmbState? _ambStateBeforePauseMenu;
|
||||
|
||||
public AmbState CurrentAmbState => _currentAmbState;
|
||||
private YarnGlobalParameterRegistry YarnGlobalParameters =>
|
||||
_yarnGlobalParameters ??= new YarnGlobalParameterRegistry(
|
||||
new RuntimeFmodGlobalParameterApi());
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -124,7 +128,8 @@ namespace AibisDream.Kit
|
||||
|
||||
var dto = new AudioSnapshotDto
|
||||
{
|
||||
ambState = ambState.ToString()
|
||||
ambState = ambState.ToString(),
|
||||
yarnGlobalParameters = YarnGlobalParameters.CaptureFinalValues(Debug.LogWarning)
|
||||
};
|
||||
|
||||
foreach (var pair in _musicDict)
|
||||
@@ -142,21 +147,34 @@ namespace AibisDream.Kit
|
||||
return dto;
|
||||
}
|
||||
|
||||
public void RestoreSnapshot(AudioSnapshotDto data)
|
||||
public void RestoreSnapshot(AudioSnapshotDto data, Action<string> warn = null)
|
||||
{
|
||||
warn ??= Debug.LogWarning;
|
||||
YarnGlobalParameters.ResetToDefaults(warn);
|
||||
|
||||
foreach (var musicEvent in _musicDict.Values)
|
||||
{
|
||||
musicEvent.stop(STOP_MODE.IMMEDIATE);
|
||||
musicEvent.release();
|
||||
}
|
||||
|
||||
foreach (var sfxEvent in _sfxDict.Values)
|
||||
{
|
||||
sfxEvent.stop(STOP_MODE.IMMEDIATE);
|
||||
sfxEvent.release();
|
||||
}
|
||||
|
||||
_musicDict.Clear();
|
||||
_sfxDict.Clear();
|
||||
|
||||
if (!string.IsNullOrEmpty(data.ambState)
|
||||
&& Enum.TryParse<AmbState>(data.ambState, out var ambState))
|
||||
{
|
||||
ChangeAmb(ambState);
|
||||
}
|
||||
|
||||
YarnGlobalParameters.RestoreFinalValues(data.yarnGlobalParameters, warn);
|
||||
|
||||
if (data.musicPaths != null)
|
||||
{
|
||||
foreach (var music in data.musicPaths)
|
||||
@@ -176,13 +194,6 @@ namespace AibisDream.Kit
|
||||
_sfxDict[sfx.Key] = instance;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(data.ambState)
|
||||
&& Enum.TryParse<AmbState>(data.ambState, out var ambState))
|
||||
{
|
||||
ChangeAmb(ambState);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void PauseAll()
|
||||
@@ -250,6 +261,16 @@ namespace AibisDream.Kit
|
||||
RuntimeManager.StudioSystem.setParameterByName(paramName, value);
|
||||
}
|
||||
|
||||
public void SetYarnGlobalParam(string paramName, float value)
|
||||
{
|
||||
var result = YarnGlobalParameters.SetFromYarn(paramName, value);
|
||||
if (result != RESULT.OK)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[AudioManager] Yarn 全局参数 {paramName ?? "(null)"} 设置失败:{result}。");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMainMenuStage(int stage)
|
||||
{
|
||||
SetGlobalParam(MainMenuStageParamName, stage);
|
||||
@@ -267,6 +288,7 @@ namespace AibisDream.Kit
|
||||
|
||||
private void OnGameStart()
|
||||
{
|
||||
YarnGlobalParameters.ResetToDefaults(Debug.LogWarning);
|
||||
SetMainMenuStage(3);
|
||||
}
|
||||
|
||||
@@ -300,6 +322,7 @@ namespace AibisDream.Kit
|
||||
|
||||
_musicDict.Clear();
|
||||
_sfxDict.Clear();
|
||||
YarnGlobalParameters.ResetToDefaults(Debug.LogWarning);
|
||||
ChangeAmb(menuAmbState);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace AibisDream.Kit
|
||||
[YarnCommand("set_global_param")]
|
||||
public static void SetGlobalParam(string paramName, float value)
|
||||
{
|
||||
AudioManager.Instance.SetGlobalParam(paramName, value);
|
||||
AudioManager.Instance.SetYarnGlobalParam(paramName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FMOD;
|
||||
using FMOD.Studio;
|
||||
using FMODUnity;
|
||||
|
||||
namespace AibisDream.Kit
|
||||
{
|
||||
internal interface IFmodGlobalParameterApi
|
||||
{
|
||||
RESULT SetParameterByName(string name, float value, bool ignoreSeekSpeed);
|
||||
RESULT GetParameterByName(string name, out float value, out float finalValue);
|
||||
RESULT GetParameterDescriptionByName(string name, out PARAMETER_DESCRIPTION description);
|
||||
RESULT SetParameterById(PARAMETER_ID id, float value, bool ignoreSeekSpeed);
|
||||
}
|
||||
|
||||
internal sealed class RuntimeFmodGlobalParameterApi : IFmodGlobalParameterApi
|
||||
{
|
||||
public RESULT SetParameterByName(string name, float value, bool ignoreSeekSpeed)
|
||||
{
|
||||
return RuntimeManager.StudioSystem.setParameterByName(name, value, ignoreSeekSpeed);
|
||||
}
|
||||
|
||||
public RESULT GetParameterByName(string name, out float value, out float finalValue)
|
||||
{
|
||||
return RuntimeManager.StudioSystem.getParameterByName(name, out value, out finalValue);
|
||||
}
|
||||
|
||||
public RESULT GetParameterDescriptionByName(
|
||||
string name,
|
||||
out PARAMETER_DESCRIPTION description)
|
||||
{
|
||||
return RuntimeManager.StudioSystem.getParameterDescriptionByName(name, out description);
|
||||
}
|
||||
|
||||
public RESULT SetParameterById(PARAMETER_ID id, float value, bool ignoreSeekSpeed)
|
||||
{
|
||||
return RuntimeManager.StudioSystem.setParameterByID(id, value, ignoreSeekSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只记录曾被 Yarn set_global_param 成功修改的参数名。
|
||||
/// 参数值与默认值始终以 FMOD Studio System 为唯一来源。
|
||||
/// </summary>
|
||||
internal sealed class YarnGlobalParameterRegistry
|
||||
{
|
||||
private readonly IFmodGlobalParameterApi _api;
|
||||
private readonly HashSet<string> _trackedNames =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
internal YarnGlobalParameterRegistry(IFmodGlobalParameterApi api)
|
||||
{
|
||||
_api = api ?? throw new ArgumentNullException(nameof(api));
|
||||
}
|
||||
|
||||
internal IReadOnlyCollection<string> TrackedNames => _trackedNames;
|
||||
|
||||
internal RESULT SetFromYarn(string name, float value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return RESULT.ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
var result = _api.SetParameterByName(name, value, ignoreSeekSpeed: false);
|
||||
if (result == RESULT.OK)
|
||||
{
|
||||
_trackedNames.Add(name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal Dictionary<string, float> CaptureFinalValues(Action<string> warn)
|
||||
{
|
||||
var values = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var name in _trackedNames)
|
||||
{
|
||||
var result = _api.GetParameterByName(name, out _, out var finalValue);
|
||||
if (result == RESULT.OK)
|
||||
{
|
||||
values[name] = finalValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
warn?.Invoke(
|
||||
$"[AudioManager] 捕获 Yarn 全局参数 {name} 失败:{result}。本次存档跳过该参数。");
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
internal void ResetToDefaults(Action<string> warn)
|
||||
{
|
||||
var names = new List<string>(_trackedNames);
|
||||
foreach (var name in names)
|
||||
{
|
||||
var descriptionResult =
|
||||
_api.GetParameterDescriptionByName(name, out var description);
|
||||
if (descriptionResult != RESULT.OK)
|
||||
{
|
||||
warn?.Invoke(
|
||||
$"[AudioManager] 查询 Yarn 全局参数 {name} 默认值失败:{descriptionResult}。保留追踪以便稍后重试。");
|
||||
continue;
|
||||
}
|
||||
|
||||
var resetResult = _api.SetParameterById(
|
||||
description.id,
|
||||
description.defaultvalue,
|
||||
ignoreSeekSpeed: true);
|
||||
if (resetResult != RESULT.OK)
|
||||
{
|
||||
warn?.Invoke(
|
||||
$"[AudioManager] 重置 Yarn 全局参数 {name} 失败:{resetResult}。保留追踪以便稍后重试。");
|
||||
continue;
|
||||
}
|
||||
|
||||
_trackedNames.Remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RestoreFinalValues(
|
||||
IReadOnlyDictionary<string, float> values,
|
||||
Action<string> warn)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pair in values)
|
||||
{
|
||||
var result = _api.SetParameterByName(
|
||||
pair.Key,
|
||||
pair.Value,
|
||||
ignoreSeekSpeed: true);
|
||||
if (result == RESULT.OK)
|
||||
{
|
||||
_trackedNames.Add(pair.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
warn?.Invoke(
|
||||
$"[AudioManager] 恢复 Yarn 全局参数 {pair.Key} 失败:{result}。跳过该参数。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe8472981b434b1d94e494cca8e3801d
|
||||
@@ -37,7 +37,7 @@ namespace AibisDream.SaveSystem
|
||||
return;
|
||||
}
|
||||
|
||||
AudioManager.Instance.RestoreSnapshot(audio);
|
||||
AudioManager.Instance.RestoreSnapshot(audio, context.Warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,10 @@ namespace AibisDream.SaveSystem
|
||||
public bool isTalking;
|
||||
}
|
||||
|
||||
/// <summary>sections["audio"]:FMOD 音乐/SFX 终态与 AMB 状态(不记播放进度)。</summary>
|
||||
/// <summary>
|
||||
/// sections["audio"]:FMOD 音乐/SFX 终态、AMB 状态与 Yarn 全局参数 finalValue
|
||||
/// (不记 Event 播放进度)。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class AudioSnapshotDto
|
||||
{
|
||||
@@ -134,6 +137,9 @@ namespace AibisDream.SaveSystem
|
||||
|
||||
/// <summary><see cref="AmbState"/> 枚举名。</summary>
|
||||
public string ambState;
|
||||
|
||||
/// <summary>由 Yarn set_global_param 修改过的参数名 → 捕获时的 FMOD finalValue。</summary>
|
||||
public Dictionary<string, float> yarnGlobalParameters = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user