326 lines
10 KiB
C#
326 lines
10 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description:
|
||
* @Date: 2025年05月22日 星期四 14:05:32
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Logic.CrashSight;
|
||
using RuntimeData;
|
||
using TH1_Logic.Core;
|
||
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;
|
||
}
|
||
}
|
||
} |