Initial commit: Tiny Tackle Heroes Unity project
This commit is contained in:
@@ -0,0 +1,661 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using BumpCombat.Spawning;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Small scene-local audio router for the presentation pass. It keeps
|
||||
/// combat sounds bounded and leaves gameplay systems unaware of clips.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class BumpCombatAudioService : MonoBehaviour
|
||||
{
|
||||
private const string ResourceRoot = "Presentation/Audio-v1/";
|
||||
private const string MasterKey = "BumpCombat.MasterVolume";
|
||||
private const string MusicKey = "BumpCombat.MusicVolume";
|
||||
private const string SfxKey = "BumpCombat.SfxVolume";
|
||||
private const float DefaultMaster = 0.8f;
|
||||
private const float DefaultMusic = 0.55f;
|
||||
private const float DefaultSfx = 0.8f;
|
||||
private const float SfxBusGain = 0.65f;
|
||||
private const int DefaultVoiceCount = 8;
|
||||
private const string MusicSourcePrefix = "Music";
|
||||
|
||||
private sealed class Voice
|
||||
{
|
||||
public AudioSource Source;
|
||||
public string ClipId;
|
||||
public float StartedAt;
|
||||
public float Gain = 1f;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, AudioClip> clips = new();
|
||||
private readonly Dictionary<string, float> lastPlayedAt = new();
|
||||
private readonly List<Voice> voices = new();
|
||||
private readonly HashSet<RunTimedEvent> activeBossEvents = new();
|
||||
private AudioSource musicSource;
|
||||
private AudioSource activeMusic;
|
||||
private AudioClip activeMusicClip;
|
||||
private float musicWeight;
|
||||
private Coroutine musicFadeRoutine;
|
||||
private RunManager runManager;
|
||||
private PlayerHealth playerHealth;
|
||||
private ExperienceSystem experienceSystem;
|
||||
private ActiveArtifactController artifacts;
|
||||
private SpawnDirector spawnDirector;
|
||||
private bool nextPitchUp;
|
||||
private bool gaugeWasReady;
|
||||
private bool runEndSoundPlayed;
|
||||
private int lastExperience;
|
||||
private int lastLevel;
|
||||
private float musicDuck = 1f;
|
||||
private float masterVolume;
|
||||
private float musicVolume;
|
||||
private float sfxVolume;
|
||||
|
||||
public static BumpCombatAudioService Instance { get; private set; }
|
||||
public float MasterVolume => masterVolume;
|
||||
public float MusicVolume => musicVolume;
|
||||
public float SfxVolume => sfxVolume;
|
||||
public int VoiceCount => voices.Count;
|
||||
public string ActiveMusicClipId { get; private set; }
|
||||
public string LastPlayedClipId { get; private set; }
|
||||
public float CurrentMusicOutputGain =>
|
||||
musicSource != null ? musicSource.volume : 0f;
|
||||
public bool AreMusicSourcesStopped =>
|
||||
musicSource == null || !musicSource.isPlaying;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
StopAndRemoveGeneratedMusicSources();
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
StopAndRemoveGeneratedMusicSources();
|
||||
masterVolume = LoadVolume(MasterKey, DefaultMaster);
|
||||
musicVolume = LoadVolume(MusicKey, DefaultMusic);
|
||||
sfxVolume = LoadVolume(SfxKey, DefaultSfx);
|
||||
LoadClips();
|
||||
CreateSources();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
BindRuntimeObjects();
|
||||
PlayMusic("bgm_courtyard", true);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
bool ownsInstance = Instance == this;
|
||||
UnbindRuntimeObjects();
|
||||
if (musicFadeRoutine != null)
|
||||
{
|
||||
StopCoroutine(musicFadeRoutine);
|
||||
musicFadeRoutine = null;
|
||||
}
|
||||
if (ownsInstance)
|
||||
{
|
||||
StopAndRemoveGeneratedMusicSources();
|
||||
Instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopAndRemoveGeneratedMusicSources()
|
||||
{
|
||||
for (int i = transform.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
Transform child = transform.GetChild(i);
|
||||
if (!IsGeneratedMusicSourceName(child.name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AudioSource[] sources = child.GetComponentsInChildren<AudioSource>(true);
|
||||
for (int j = 0; j < sources.Length; j++)
|
||||
{
|
||||
sources[j].Stop();
|
||||
sources[j].volume = 0f;
|
||||
}
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsGeneratedMusicSourceName(string objectName)
|
||||
{
|
||||
return string.Equals(objectName, MusicSourcePrefix, StringComparison.Ordinal)
|
||||
|| string.Equals(objectName, "Music A", StringComparison.Ordinal)
|
||||
|| string.Equals(objectName, "Music B", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private void LoadClips()
|
||||
{
|
||||
string[] ids =
|
||||
{
|
||||
"ui_move", "ui_confirm", "hit_light", "hit_heavy",
|
||||
"player_hurt", "enemy_defeat", "xp_pickup", "level_up",
|
||||
"gauge_ready", "run_end", "charge_start", "artifact_dash",
|
||||
"artifact_pulse", "artifact_ray", "artifact_cyclone",
|
||||
"artifact_thunder", "artifact_arc", "bgm_courtyard", "bgm_boss",
|
||||
};
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
AudioClip clip = Resources.Load<AudioClip>(ResourceRoot + ids[i]);
|
||||
if (clip != null)
|
||||
{
|
||||
clips[ids[i]] = clip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSources()
|
||||
{
|
||||
for (int i = 0; i < DefaultVoiceCount; i++)
|
||||
{
|
||||
GameObject sourceObject = new($"SFX Voice {i + 1}");
|
||||
sourceObject.transform.SetParent(transform, false);
|
||||
AudioSource source = sourceObject.AddComponent<AudioSource>();
|
||||
source.playOnAwake = false;
|
||||
source.ignoreListenerPause = true;
|
||||
source.spatialBlend = 0f;
|
||||
voices.Add(new Voice { Source = source });
|
||||
}
|
||||
|
||||
musicSource = CreateMusicSource("Music");
|
||||
}
|
||||
|
||||
private AudioSource CreateMusicSource(string name)
|
||||
{
|
||||
GameObject sourceObject = new(name);
|
||||
sourceObject.transform.SetParent(transform, false);
|
||||
AudioSource source = sourceObject.AddComponent<AudioSource>();
|
||||
source.playOnAwake = false;
|
||||
source.loop = true;
|
||||
source.ignoreListenerPause = true;
|
||||
source.spatialBlend = 0f;
|
||||
return source;
|
||||
}
|
||||
|
||||
private void BindRuntimeObjects()
|
||||
{
|
||||
runManager = RunManager.Instance;
|
||||
playerHealth = FindAnyObjectByType<PlayerHealth>();
|
||||
experienceSystem = FindAnyObjectByType<ExperienceSystem>();
|
||||
artifacts = FindAnyObjectByType<ActiveArtifactController>();
|
||||
spawnDirector = FindAnyObjectByType<SpawnDirector>();
|
||||
|
||||
CombatEvents.OnValidHit += HandleValidHit;
|
||||
CombatEvents.OnEnemyDied += HandleEnemyDied;
|
||||
if (runManager != null)
|
||||
{
|
||||
runManager.OnSelectionChanged += HandleSelectionChanged;
|
||||
runManager.OnPauseChanged += HandlePauseChanged;
|
||||
runManager.OnTitleChanged += HandleTitleChanged;
|
||||
runManager.OnGameOver += HandleGameOver;
|
||||
}
|
||||
if (playerHealth != null)
|
||||
{
|
||||
playerHealth.OnDamaged += HandlePlayerDamaged;
|
||||
playerHealth.OnDied += HandlePlayerDied;
|
||||
}
|
||||
if (experienceSystem != null)
|
||||
{
|
||||
lastExperience = experienceSystem.CurrentExperience;
|
||||
lastLevel = experienceSystem.Level;
|
||||
experienceSystem.OnExperienceChanged += HandleExperienceChanged;
|
||||
experienceSystem.OnLevelGained += HandleLevelGained;
|
||||
}
|
||||
if (artifacts != null)
|
||||
{
|
||||
gaugeWasReady = artifacts.CurrentGauge >= artifacts.MaxGauge;
|
||||
artifacts.OnStateChanged += HandleArtifactStateChanged;
|
||||
artifacts.OnArtifactChargeStarted += HandleArtifactChargeStarted;
|
||||
artifacts.OnArtifactUseSucceeded += HandleArtifactUseSucceeded;
|
||||
}
|
||||
if (spawnDirector != null)
|
||||
{
|
||||
spawnDirector.OnEventEnemySpawned += HandleEventEnemySpawned;
|
||||
spawnDirector.OnEventEnemyDefeated += HandleEventEnemyDefeated;
|
||||
}
|
||||
UpdateMusicDuck(runManager?.IsTitleScreen == true
|
||||
|| runManager?.IsSelectionOpen == true
|
||||
|| runManager?.IsPaused == true);
|
||||
}
|
||||
|
||||
private void UnbindRuntimeObjects()
|
||||
{
|
||||
CombatEvents.OnValidHit -= HandleValidHit;
|
||||
CombatEvents.OnEnemyDied -= HandleEnemyDied;
|
||||
if (runManager != null)
|
||||
{
|
||||
runManager.OnSelectionChanged -= HandleSelectionChanged;
|
||||
runManager.OnPauseChanged -= HandlePauseChanged;
|
||||
runManager.OnTitleChanged -= HandleTitleChanged;
|
||||
runManager.OnGameOver -= HandleGameOver;
|
||||
}
|
||||
if (playerHealth != null)
|
||||
{
|
||||
playerHealth.OnDamaged -= HandlePlayerDamaged;
|
||||
playerHealth.OnDied -= HandlePlayerDied;
|
||||
}
|
||||
if (experienceSystem != null)
|
||||
{
|
||||
experienceSystem.OnExperienceChanged -= HandleExperienceChanged;
|
||||
experienceSystem.OnLevelGained -= HandleLevelGained;
|
||||
}
|
||||
if (artifacts != null)
|
||||
{
|
||||
artifacts.OnStateChanged -= HandleArtifactStateChanged;
|
||||
artifacts.OnArtifactChargeStarted -= HandleArtifactChargeStarted;
|
||||
artifacts.OnArtifactUseSucceeded -= HandleArtifactUseSucceeded;
|
||||
}
|
||||
if (spawnDirector != null)
|
||||
{
|
||||
spawnDirector.OnEventEnemySpawned -= HandleEventEnemySpawned;
|
||||
spawnDirector.OnEventEnemyDefeated -= HandleEventEnemyDefeated;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleValidHit(CombatHitResult result)
|
||||
{
|
||||
// Artifact hits already have one sound at cast start. Keep this
|
||||
// event for successful ordinary bumps only, avoiding AoE chatter.
|
||||
if (!result.IsOrdinaryBump || !result.ShowsHitFeedback)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PlaySfx(
|
||||
result.IsStrongDash || result.HasBackBonus
|
||||
? "hit_heavy"
|
||||
: "hit_light",
|
||||
1f,
|
||||
false,
|
||||
0.05f);
|
||||
}
|
||||
|
||||
private void HandleEnemyDied(GameObject target)
|
||||
{
|
||||
PlaySfx("enemy_defeat", 0.85f, false, 0.08f);
|
||||
}
|
||||
|
||||
private void HandlePlayerDamaged(Vector2 direction, float force)
|
||||
{
|
||||
PlaySfx("player_hurt", 1f, false, 0.08f);
|
||||
}
|
||||
|
||||
private void HandlePlayerDied()
|
||||
{
|
||||
if (runEndSoundPlayed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
runEndSoundPlayed = true;
|
||||
PlaySfx("run_end", 1f, false, 0f);
|
||||
FadeMusicOut(0.5f);
|
||||
}
|
||||
|
||||
private void HandleGameOver()
|
||||
{
|
||||
if (runEndSoundPlayed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
runEndSoundPlayed = true;
|
||||
PlaySfx("run_end", 1f, false, 0f);
|
||||
FadeMusicOut(0.5f);
|
||||
}
|
||||
|
||||
private void HandleExperienceChanged(int current, int required, int level)
|
||||
{
|
||||
if (current > lastExperience || level > lastLevel)
|
||||
{
|
||||
PlaySfx("xp_pickup", 0.6f, false, 0.07f);
|
||||
}
|
||||
lastExperience = current;
|
||||
lastLevel = level;
|
||||
}
|
||||
|
||||
private void HandleLevelGained(int level)
|
||||
{
|
||||
PlaySfx("level_up", 1f, false, 0.1f);
|
||||
}
|
||||
|
||||
private void HandleArtifactStateChanged()
|
||||
{
|
||||
if (artifacts == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool ready = artifacts.CurrentGauge >= artifacts.MaxGauge - 0.0001f;
|
||||
if (ready && !gaugeWasReady)
|
||||
{
|
||||
PlaySfx("gauge_ready", 0.9f, false, 0.1f);
|
||||
}
|
||||
gaugeWasReady = ready;
|
||||
}
|
||||
|
||||
private void HandleArtifactChargeStarted(ActiveArtifactDefinition definition)
|
||||
{
|
||||
PlaySfx("charge_start", 0.8f, false, 0.1f);
|
||||
}
|
||||
|
||||
private void HandleArtifactUseSucceeded(
|
||||
ActiveArtifactDefinition definition,
|
||||
bool charged)
|
||||
{
|
||||
if (definition == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string clipId = definition.Effect switch
|
||||
{
|
||||
ActiveArtifactEffect.Dash => "artifact_dash",
|
||||
ActiveArtifactEffect.Pulse => "artifact_pulse",
|
||||
ActiveArtifactEffect.Phoenix => "artifact_ray",
|
||||
ActiveArtifactEffect.Cyclone => "artifact_cyclone",
|
||||
ActiveArtifactEffect.ThunderCrash => "artifact_thunder",
|
||||
ActiveArtifactEffect.ChainLightning => "artifact_arc",
|
||||
_ => null,
|
||||
};
|
||||
if (clipId != null)
|
||||
{
|
||||
PlaySfx(clipId, charged ? 1f : 0.85f, false, 0.04f);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEventEnemySpawned(
|
||||
RunTimedEvent timedEvent,
|
||||
EnemyController enemy)
|
||||
{
|
||||
if (timedEvent == RunTimedEvent.MidBoss
|
||||
|| timedEvent == RunTimedEvent.FinalBoss)
|
||||
{
|
||||
activeBossEvents.Add(timedEvent);
|
||||
PlayMusic("bgm_boss", false);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEventEnemyDefeated(RunTimedEvent timedEvent)
|
||||
{
|
||||
if (timedEvent == RunTimedEvent.MidBoss
|
||||
|| timedEvent == RunTimedEvent.FinalBoss)
|
||||
{
|
||||
activeBossEvents.Remove(timedEvent);
|
||||
if (activeBossEvents.Count == 0)
|
||||
{
|
||||
PlayMusic("bgm_courtyard", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSelectionChanged(bool isOpen)
|
||||
{
|
||||
UpdateMusicDuck(isOpen || runManager?.IsPaused == true || runManager?.IsTitleScreen == true);
|
||||
}
|
||||
|
||||
private void HandlePauseChanged(bool isOpen)
|
||||
{
|
||||
UpdateMusicDuck(isOpen || runManager?.IsSelectionOpen == true || runManager?.IsTitleScreen == true);
|
||||
}
|
||||
|
||||
private void HandleTitleChanged(bool isOpen)
|
||||
{
|
||||
UpdateMusicDuck(isOpen || runManager?.IsSelectionOpen == true || runManager?.IsPaused == true);
|
||||
}
|
||||
|
||||
private void UpdateMusicDuck(bool duck)
|
||||
{
|
||||
musicDuck = duck ? 0.35f : 1f;
|
||||
ApplyMusicVolume();
|
||||
}
|
||||
|
||||
public bool PlayUi(string clipId, float volume = 1f)
|
||||
{
|
||||
return PlaySfx(clipId, volume, true, 0.025f);
|
||||
}
|
||||
|
||||
public bool PlaySfx(
|
||||
string clipId,
|
||||
float volume = 1f,
|
||||
bool isUi = false,
|
||||
float minimumInterval = 0f)
|
||||
{
|
||||
if (!clips.TryGetValue(clipId, out AudioClip clip)
|
||||
|| clip == null
|
||||
|| voices.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float now = Time.unscaledTime;
|
||||
if (minimumInterval > 0f
|
||||
&& lastPlayedAt.TryGetValue(clipId, out float previous)
|
||||
&& now - previous < minimumInterval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Voice voice = FindVoice(isUi);
|
||||
if (voice == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
lastPlayedAt[clipId] = now;
|
||||
LastPlayedClipId = clipId;
|
||||
voice.ClipId = clipId;
|
||||
voice.StartedAt = now;
|
||||
voice.Gain = Mathf.Clamp01(volume);
|
||||
AudioSource source = voice.Source;
|
||||
source.Stop();
|
||||
source.clip = clip;
|
||||
source.loop = false;
|
||||
source.volume = Mathf.Clamp01(masterVolume * sfxVolume * SfxBusGain * voice.Gain);
|
||||
source.pitch = isUi ? 1f : nextPitchUp ? 1.02f : 0.98f;
|
||||
nextPitchUp = !nextPitchUp;
|
||||
source.Play();
|
||||
return true;
|
||||
}
|
||||
|
||||
private Voice FindVoice(bool isUi)
|
||||
{
|
||||
int first = isUi ? 0 : 1;
|
||||
int last = isUi ? 1 : voices.Count;
|
||||
for (int i = first; i < last; i++)
|
||||
{
|
||||
if (!voices[i].Source.isPlaying)
|
||||
{
|
||||
return voices[i];
|
||||
}
|
||||
}
|
||||
|
||||
Voice oldest = null;
|
||||
for (int i = first; i < last; i++)
|
||||
{
|
||||
if (oldest == null || voices[i].StartedAt < oldest.StartedAt)
|
||||
{
|
||||
oldest = voices[i];
|
||||
}
|
||||
}
|
||||
return oldest;
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float value)
|
||||
{
|
||||
masterVolume = SaveVolume(MasterKey, value);
|
||||
ApplySfxVolume();
|
||||
ApplyMusicVolume();
|
||||
}
|
||||
|
||||
public void SetMusicVolume(float value)
|
||||
{
|
||||
musicVolume = SaveVolume(MusicKey, value);
|
||||
ApplyMusicVolume();
|
||||
}
|
||||
|
||||
public void SetSfxVolume(float value)
|
||||
{
|
||||
sfxVolume = SaveVolume(SfxKey, value);
|
||||
ApplySfxVolume();
|
||||
}
|
||||
|
||||
private void ApplySfxVolume()
|
||||
{
|
||||
for (int i = 0; i < voices.Count; i++)
|
||||
{
|
||||
if (voices[i].Source.isPlaying)
|
||||
{
|
||||
voices[i].Source.volume = masterVolume
|
||||
* sfxVolume
|
||||
* SfxBusGain
|
||||
* voices[i].Gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayMusic(string clipId, bool immediate)
|
||||
{
|
||||
if (!clips.TryGetValue(clipId, out AudioClip clip) || clip == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (activeMusicClip == clip && activeMusic != null && activeMusic.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (musicFadeRoutine != null)
|
||||
{
|
||||
StopCoroutine(musicFadeRoutine);
|
||||
musicFadeRoutine = null;
|
||||
}
|
||||
|
||||
// Music owns one loop source. Stop the previous clip before
|
||||
// assigning the new one so a boss transition can never overlap
|
||||
// ambient music, even during the transition fade.
|
||||
SetMusicWeight(immediate ? 1f : 0f);
|
||||
musicSource.Stop();
|
||||
musicSource.clip = clip;
|
||||
musicSource.loop = true;
|
||||
musicSource.volume = musicWeight * GetMusicGain();
|
||||
musicSource.Play();
|
||||
activeMusic = musicSource;
|
||||
activeMusicClip = clip;
|
||||
ActiveMusicClipId = clipId;
|
||||
ApplyMusicVolume();
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
musicFadeRoutine = StartCoroutine(FadeMusicIn(0.5f));
|
||||
}
|
||||
|
||||
private IEnumerator FadeMusicIn(float duration)
|
||||
{
|
||||
float elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
SetMusicWeight(Mathf.Lerp(0f, 1f, t));
|
||||
ApplyMusicVolume();
|
||||
yield return null;
|
||||
}
|
||||
SetMusicWeight(1f);
|
||||
ApplyMusicVolume();
|
||||
musicFadeRoutine = null;
|
||||
}
|
||||
|
||||
private void FadeMusicOut(float duration)
|
||||
{
|
||||
if (musicSource == null || !musicSource.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (musicFadeRoutine != null)
|
||||
{
|
||||
StopCoroutine(musicFadeRoutine);
|
||||
}
|
||||
musicFadeRoutine = StartCoroutine(FadeMusicOutRoutine(duration));
|
||||
}
|
||||
|
||||
private IEnumerator FadeMusicOutRoutine(float duration)
|
||||
{
|
||||
float musicStart = musicWeight;
|
||||
float elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.unscaledDeltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
SetMusicWeight(Mathf.Lerp(musicStart, 0f, t));
|
||||
ApplyMusicVolume();
|
||||
yield return null;
|
||||
}
|
||||
if (musicSource != null)
|
||||
{
|
||||
musicSource.Stop();
|
||||
}
|
||||
SetMusicWeight(0f);
|
||||
ApplyMusicVolume();
|
||||
activeMusic = null;
|
||||
activeMusicClip = null;
|
||||
musicFadeRoutine = null;
|
||||
}
|
||||
|
||||
private float GetMusicGain()
|
||||
{
|
||||
return Mathf.Clamp01(masterVolume * musicVolume * musicDuck);
|
||||
}
|
||||
|
||||
private void ApplyMusicVolume()
|
||||
{
|
||||
float gain = GetMusicGain();
|
||||
if (musicSource != null)
|
||||
{
|
||||
musicSource.volume = musicSource.isPlaying ? musicWeight * gain : 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMusicWeight(float weight)
|
||||
{
|
||||
musicWeight = Mathf.Clamp01(weight);
|
||||
}
|
||||
|
||||
private static float LoadVolume(string key, float fallback)
|
||||
{
|
||||
return Mathf.Clamp01(PlayerPrefs.GetFloat(key, fallback));
|
||||
}
|
||||
|
||||
private static float SaveVolume(string key, float value)
|
||||
{
|
||||
float clamped = Mathf.Clamp01(value);
|
||||
PlayerPrefs.SetFloat(key, clamped);
|
||||
PlayerPrefs.Save();
|
||||
return clamped;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public bool DebugHasClip(string clipId)
|
||||
{
|
||||
return clips.ContainsKey(clipId);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47b32c4ff8d74a0c9a7d1e2b3c4f5a6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user