TH1/Unity/Assets/Scripts/TH1Audio/AudioManager.cs

325 lines
10 KiB
C#
Raw 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.

/*
* @Author: 白哉
* @Description:
* @Date: 2025年05月22日 星期四 14:05:32
* @Modify:
*/
using System.Collections.Generic;
using System.Linq;
using Logic.CrashSight;
using RuntimeData;
using UnityEngine;
namespace Logic.Audio
{
public class AudioManager
{
public static AudioManager Instance = new AudioManager();
private AudioPlayer _musicPlayer;
private List<AudioPlayer> _allPlayer;
private Dictionary<string, List<AudioClip>> _clips;
private GameObject AudioRoot;
public float MusicVolume
{
get { return _musicVolume; }
set { _musicVolume = value; }
}
public float AudioVolume
{
get { return _audioVolume; }
set { _audioVolume = value; }
}
private float _musicVolume = 1;
private float _audioVolume = 1;
public void Init()
{
_allPlayer = new List<AudioPlayer>();
_clips = new Dictionary<string, List<AudioClip>>();
var path = new Dictionary<string, string>();
path["Main"] = "Audio/Main";
path["RemiliaEgyptian"] = "Audio/RemiliaEgyptian";
path["SatoriIndian"] = "Audio/SatoriIndian";
path["KanakoGermany"] = "Audio/KanakoGermany";
path["KaguyaFrench"] = "Audio/KaguyaFrench";
path["SFX/UI_buttonHover"] = "Audio/SFX/UI_buttonHover";
path["SFX/UI_buttonClick"] = "Audio/SFX/UI_buttonClick";
path["SFX/UNIT_click"] = "Audio/SFX/UNIT_click";
path["SFX/UNIT_bomb"] = "Audio/SFX/UNIT_bomb";
path["SFX/UNIT_archer"] = "Audio/SFX/UNIT_archer";
path["SFX/UNIT_hurt"] = "Audio/SFX/UNIT_hurt";
path["SFX/UNIT_move"] = "Audio/SFX/UNIT_move";
path["SFX/UNIT_attack"] = "Audio/SFX/UNIT_attack";
path["SFX/ENV_sea"] = "Audio/SFX/ENV_sea";
path["SFX/ENV_forest"] = "Audio/SFX/ENV_forest";
foreach (var kv in path)
{
_clips[kv.Key] = new List<AudioClip>();
_clips[kv.Key].Add(Resources.Load<AudioClip>(kv.Value));
}
if (!AudioRoot) AudioRoot = new GameObject();
AudioRoot.name = "AudioRoot";
var cfg = Object.FindObjectOfType<AudioClipConfig>();
if (cfg != null)
{
foreach (var clip in cfg.Clips)
{
_clips[clip.name] = new List<AudioClip>();
_clips[clip.name].Add(clip);
}
}
}
public void Update()
{
foreach (var player in _allPlayer)
{
if (player == _musicPlayer) player.Update(MusicVolume);
else player.Update(AudioVolume);
if (player.State != PlayerState.Finished) continue;
if (!player.Clip) continue;
if (!_clips.ContainsKey(player.MusicName)) _clips[player.MusicName] = new List<AudioClip>();
_clips[player.MusicName].Add(player.Clip);
player.Clip = null;
}
}
public void PlayMusic(string musicName, float fadeIn, float fadeOut, bool isLoop)
{
if (!_clips.ContainsKey(musicName) || _clips[musicName].Count == 0) return;
if (_musicPlayer != null) _musicPlayer.Stop();
else _musicPlayer = GetPlayer();
if (_clips[musicName].Count > 1)
{
_musicPlayer.Clip = _clips[musicName][1];
_clips[musicName].RemoveAt(1);
}
else _musicPlayer.Clip = Object.Instantiate(_clips[musicName][0]);
if (!_musicPlayer.Clip)
{
LogSystem.LogError($"音乐资源 {musicName} 未找到或加载失败!");
return;
}
_musicPlayer.MusicName = musicName;
_musicPlayer.IsLoop = isLoop;
_musicPlayer.Length = _musicPlayer.Clip.length;
_musicPlayer.FadeInDuration = fadeIn;
_musicPlayer.FadeOutDuration = fadeOut;
_musicPlayer.Play();
}
public void StopMusic()
{
_musicPlayer?.Stop();
}
public void PlayAudio(string musicName, float fadeIn = 0f, float fadeOut = 0f, bool isLoop = false)
{
if (!_clips.ContainsKey(musicName)) return;
var player = GetPlayer();
if (_clips[musicName].Count > 1)
{
player.Clip = _clips[musicName][1];
_clips[musicName].RemoveAt(1);
}
else player.Clip = Object.Instantiate(_clips[musicName][0]);
if (!player.Clip)
{
LogSystem.LogError($"音频资源 {musicName} 未找到或加载失败!");
return;
}
player.MusicName = musicName;
player.IsLoop = isLoop;
player.Length = player.Clip.length;
player.FadeInDuration = fadeIn;
player.FadeOutDuration = fadeOut;
player.Play();
}
private AudioPlayer GetPlayer()
{
// 检查是否有可重用的播放器
foreach (var player in _allPlayer)
{
if (player.State == PlayerState.Finished || player.State == PlayerState.Prepare)
{
return player;
}
}
_allPlayer = _allPlayer.OrderBy(p => p.StartTime).ToList();
// 限制最大同时播放数量
if (_allPlayer.Count >= 16) // FMOD默认最大通道数通常是32这里取一半作为安全值
{
LogSystem.LogError("Too many audio players active, trying to reuse oldest one");
var oldestPlayer = _allPlayer[0];
oldestPlayer.Stop();
if (oldestPlayer.Source != null)
{
oldestPlayer.Source.Stop();
oldestPlayer.State = PlayerState.Prepare;
return oldestPlayer;
}
}
var sourceObj = new GameObject();
sourceObj.transform.SetParent(AudioRoot.transform);
var source = sourceObj.AddComponent<AudioSource>();
var newPlayer = new AudioPlayer(source);
_allPlayer.Add(newPlayer);
return newPlayer;
}
//----- InGame部分的音频 -------
private Main _main;
private MapData _map;
public void InGameAudioInit(Main main, MapData map)
{
_main = main;
_map = map;
}
public void CalculateAndPlayAmbient()
{
if (_map == null) return;
//播放环境音效。如果领土内有海洋则60%概率播放海洋。否则必播放forest
bool isForest = true;
var player = _map.PlayerMap.SelfPlayerData;
var gset = _map.GetPlayerTerritoryGridIdSet(player.Id);
foreach (var g in gset)
{
if (!_map.GridMap.GetGridDataByGid(g, out var grid)) continue;
if (grid.Terrain != TerrainType.Land)
{
isForest = Random.Range(0, 100) < 50 ? true : false;
break;
}
}
if(isForest)
PlayAudio("SFX/ENV_forest",1f);
else
PlayAudio("SFX/ENV_sea",1f);
}
public void InGameOnTurnStart()
{
CalculateAndPlayAmbient();
}
}
public enum PlayerState
{
Prepare,
FadeIn,
FadeOut,
Playing,
Finished,
}
public class AudioPlayer
{
public string MusicName;
public AudioSource Source;
public AudioClip Clip;
public bool IsLoop;
public float Length;
public float FadeInDuration;
public float FadeOutDuration;
public float StartTime;
public float EndTime;
public PlayerState State;
public AudioPlayer(AudioSource source)
{
Source = source;
State = PlayerState.Prepare;
}
public void Play()
{
StartTime = Time.time;
Source.time = 0;
Source.clip = Clip;
Source.volume = 0;
Source.loop = IsLoop;
Source.Play();
State = PlayerState.FadeIn;
}
public void Update(float volumeRatio)
{
if (Source == null) return;
volumeRatio = Mathf.Clamp(volumeRatio, 0, 1);
UpdateSourceVolume(volumeRatio);
}
public void Stop()
{
if (State == PlayerState.Finished || State == PlayerState.Prepare) return;
State = PlayerState.FadeOut;
EndTime = Time.time;
}
private void UpdateSourceVolume(float volumeRatio)
{
if (State == PlayerState.Finished || State == PlayerState.Prepare) return;
if (State == PlayerState.FadeIn)
{
if (Time.time - StartTime < FadeInDuration)
{
Source.volume = (Time.time - StartTime) / FadeInDuration * volumeRatio;
}
else
{
Source.volume = 1 * volumeRatio;
State = PlayerState.Playing;
}
}
if (State == PlayerState.Playing && !IsLoop)
{
if (Time.time - StartTime >= Length - FadeOutDuration)
{
EndTime = Time.time;
State = PlayerState.FadeOut;
}
}
if (State == PlayerState.FadeOut)
{
if (Time.time - EndTime >= FadeOutDuration)
{
Source.volume = 0;
Source.Stop();
State = PlayerState.Finished;
}
else
{
Source.volume = (FadeOutDuration - Time.time + EndTime) / FadeOutDuration * volumeRatio;
}
}
if (State == PlayerState.Playing) Source.volume = volumeRatio;
}
}
}