Game/GameMusic.cs
using System;
/// <summary>
/// 背景音乐(M4):主菜单 = chiptune1,战斗 = battle.music。走 .sound SoundEvent(UI 标志 = 2D 平面声,
/// 与 6 个音效同一条已验证链路;ResourceLibrary.Get<SoundFile> 直接取导入 mp3 取不到,实测)。
/// **循环 = 播完自动重播**(SoundHandle 没有循环标志,Tick 里检测曲终重开)。切曲:旧曲 1s 淡出、新曲 1s 淡入。
/// </summary>
public static class GameMusic
{
const string MenuTrack = "sounds/music/chiptune1.sound";
const string BattleTrack = "sounds/music/battle.music.sound";
static SoundHandle _handle;
static string _track;
static bool _wasPlaying; // 上一帧 handle 是否有效且在播(曲终重播的判据,见 Tick)
static Sandbox.Audio.Mixer _musicMixer; // Music 混音器缓存(找不到就留在默认 Game,不影响出声)
/// <summary> 音乐音量 0-1(控制台实时可调)。走 Music 混音器,别和 UI 音效一锅炖 </summary>
[ConVar( "cr_music_volume", Help = "Music volume 0..1 (default 0.22)" )]
public static float Volume { get; set; } = 0.22f;
/// <summary> 切主菜单曲(已在放就无事发生) </summary>
public static void PlayMenu() => Play( MenuTrack );
/// <summary> 切战斗曲(开局时调) </summary>
public static void PlayBattle() => Play( BattleTrack );
/// <summary> 每帧调(CircleroyaleGame.Tick,菜单阶段也要跑):音量跟随 convar + 曲终自动重播 </summary>
public static void Tick()
{
if ( _track is null ) return;
// 曲终重播:引擎在声音播完后会 Dispose 掉 handle(SoundHandle.PreTick: Finished→Dispose),
// 所以 IsValid 会变 false——**不能**用 `if (!_handle.IsValid()) return;` 早退,否则永远到不了重播。
// 只在"上一帧还在播、现在失效/Finished/IsStopped"时重开,避免资源缺失时每帧空转重试。
if ( _wasPlaying && ( !_handle.IsValid() || _handle.Finished || _handle.IsStopped ) )
{
var path = _track;
_handle = default;
_track = null;
_wasPlaying = false;
Play( path );
return;
}
if ( !_handle.IsValid() ) return;
_handle.Volume = Volume;
_wasPlaying = _handle.IsPlaying;
}
static void Play( string path )
{
if ( _track == path && _handle.IsValid() && _handle.IsPlaying ) return; // 已在放这首
try
{
if ( _handle.IsValid() ) _handle.Stop( 1f ); // 旧曲淡出让位
_handle = Sound.Play( path ); // 曲子 .sound 带 UI 标志 = 2D 平面声
_track = path;
if ( !_handle.IsValid() )
{
Log.Warning( $"[music] Sound.Play returned invalid handle: {path} (asset not imported?)" );
_track = null; // 别让 Tick 每帧重试
return;
}
_wasPlaying = true;
// 路由到内置 Music 混音器(Master→Music/Game/UI/Voice,引擎 ResetToDefault 建)
_musicMixer ??= Sandbox.Audio.Mixer.FindMixerByName( "Music" );
if ( _musicMixer is not null )
{
// 用户要求:Music 混音器音量减半(引擎默认 1.0 → 0.5;绝对值赋值,热重载不叠加)
_musicMixer.Volume = 0.5f;
_handle.TargetMixer = _musicMixer;
}
}
catch ( Exception e )
{
Log.Warning( $"[music] play '{path}' failed: {e.Message}" );
_track = null;
}
}
}