Files

146 lines
5.7 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace OCES.Audio
{
/// <summary>
/// 维护当前激活的游戏状态,在状态变化时全量匹配 Path 表,返回应当播放的 ContainerId。
/// </summary>
public class MusicStateRouter
{
// key: StateGroup enum Typevalue: 当前激活的 enum 值
readonly Dictionary<Type, Enum> m_activeStates = new();
readonly MusicPathConfig m_musicPaths;
readonly AmbiencePathConfig m_ambiencePaths;
// 上一次匹配到的 PathId,用于 Transition 表的 FromPathId 查询
public uint LastMusicPathId { get; private set; }
public uint LastAmbiencePathId { get; private set; }
internal IReadOnlyDictionary<Type, Enum> ActiveStates { get { return this.m_activeStates; }}
public MusicStateRouter(MusicPathConfig musicPaths, AmbiencePathConfig ambiencePaths)
{
this.m_musicPaths = musicPaths;
this.m_ambiencePaths = ambiencePaths;
}
/// <summary>
/// 更新某个 StateGroup 的当前值,并重新全量匹配两张 Path 表。
/// </summary>
public void SetState<TEnum>(TEnum state, out uint musicContainerId, out uint ambienceContainerId)
where TEnum : Enum
{
// Dictionary<Type, Enum> 天然保证同一 StateGroup 只保留最新值,直接覆盖即可
this.m_activeStates[typeof(TEnum)] = state;
musicContainerId = MatchBestPath(this.m_musicPaths.MusicPathList(), out uint musicPathId);
ambienceContainerId = MatchBestPath(this.m_ambiencePaths.AmbiencePathList(), out uint ambiencePathId);
LastMusicPathId = musicPathId;
LastAmbiencePathId = ambiencePathId;
#if UNITY_EDITOR
Editor.DebugInfoCollector.Instance.ActiveStates = this.m_activeStates;
#endif
}
// ─────────────────────────────────────────────
// 内部匹配逻辑
// ─────────────────────────────────────────────
/// <summary>
/// 遍历路径列表,找到所有满足当前状态的规则,返回 priority 最小(最高优先级)的那条的 ContainerId。
/// </summary>
uint MatchBestPath<T>(List<T> paths, out uint matchedPathId) where T : IPathEntry
{
IPathEntry best = null;
foreach (T path in paths)
{
if (PathMatches(path.Path) && (best == null || path.Priority < best.Priority))
best = path;
}
if (best != null)
{
matchedPathId = best.Id;
return best.ContainerId;
}
matchedPathId = 0;
return 0;
}
/// <summary>
/// 解析 path 字符串,判断当前激活状态是否满足该条规则。
/// 格式:TypeID1,子状态|TypeID2,子状态……
/// 规则中的每一个条件都必须满足(AND 关系)。
/// </summary>
bool PathMatches(string pathStr)
{
if (string.IsNullOrEmpty(pathStr))
return true; // 空 path 视为无条件匹配(默认兜底规则)
string[] conditions = pathStr.Split('|');
foreach (string condition in conditions)
{
string[] parts = condition.Split(',');
if (parts.Length != 2)
{
Debug.LogWarning($"[MusicStateRouter] Path格式错误: '{condition}'");
return false;
}
if (!int.TryParse(parts[0].Trim(), out int typeId) ||
!int.TryParse(parts[1].Trim(), out int stateValue))
{
Debug.LogWarning($"[MusicStateRouter] Path解析失败: '{condition}'");
return false;
}
bool conditionMet = false;
foreach (KeyValuePair<Type, Enum> kv in this.m_activeStates)
{
if (StateGroupRegistry.GetTypeId(kv.Key) != typeId || Convert.ToInt32(kv.Value) != stateValue)
continue;
conditionMet = true;
break;
}
if (!conditionMet)
return false;
}
return true;
}
}
// ─────────────────────────────────────────────
// StateGroup 注册表:将 enum Type 映射为策划表中填写的 TypeId 整数
// ─────────────────────────────────────────────
/// <summary>
/// 程序员在此注册所有 StateGroup enum 与其对应 TypeId 的映射关系。
/// 策划在 MusicPath.path 字段中填写的 TypeId 数字必须与此处一致。
/// </summary>
public static class StateGroupRegistry
{
static readonly Dictionary<Type, int> s_typeIdMap = new();
public static void Register<TEnum>(int typeId) where TEnum : Enum
{
s_typeIdMap[typeof(TEnum)] = typeId;
}
public static int GetTypeId(Type enumType)
{
if (s_typeIdMap.TryGetValue(enumType, out int id))
return id;
Debug.LogWarning($"[StateGroupRegistry] 未注册的StateGroup类型: {enumType.Name},请调用StateGroupRegistry.Register<T>()");
return -1;
}
}
}