Kursach_StealthAction/Assets/Scripts/GameAudioSynthesizer.cs
2026-10-23 15:44:09 +03:00

333 lines
12 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 UnityEngine;
/// <summary>
/// Процедурный синтезатор звуковых эффектов для интерфейса терминала, кнопок и записок.
/// Создает чистые звуки прямо в оперативной памяти (AudioClip.Create) без внешних файлов.
/// </summary>
public static class GameAudioSynthesizer
{
private static AudioSource globalAudioSource;
private static AudioSource GetAudioSource()
{
if (globalAudioSource == null)
{
var go = new GameObject("ProceduralUIAudioSource");
if (Application.isPlaying)
{
Object.DontDestroyOnLoad(go);
}
globalAudioSource = go.AddComponent<AudioSource>();
globalAudioSource.playOnAwake = false;
globalAudioSource.spatialBlend = 0f; // 2D звук интерфейса
globalAudioSource.ignoreListenerPause = true; // Работает даже при Time.timeScale = 0
}
return globalAudioSource;
}
/// <summary>
/// Короткий высокий бип нажатия кнопки терминала (1200 Hz)
/// </summary>
public static void PlayKeypadBeep()
{
AudioClip clip = CreateToneClip(1200f, 0.05f, 0.25f);
PlayOneShot(clip, 0.35f);
}
/// <summary>
/// Звук сброса/стирания цифры (700 Hz)
/// </summary>
public static void PlayKeypadClear()
{
AudioClip clip = CreateToneClip(700f, 0.08f, 0.3f);
PlayOneShot(clip, 0.3f);
}
/// <summary>
/// Звук ошибки ввода (низкий двойной зуммер 160Hz)
/// </summary>
public static void PlayErrorCode()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.25f);
float[] data = new float[samples];
for (int i = 0; i < samples; i++)
{
float t = (float)i / sampleRate;
float freq = 160f;
float pulse = Mathf.Sin(2f * Mathf.PI * 18f * t) > 0f ? 1f : 0f;
data[i] = Mathf.Sin(2f * Mathf.PI * freq * t) * pulse * (1f - (float)i / samples) * 0.4f;
}
AudioClip clip = AudioClip.Create("ErrorCodeTone", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.45f);
}
/// <summary>
/// Торжественный трезвук успеха (C-E-G мажорный перелив)
/// </summary>
public static void PlaySuccessChime()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.6f);
float[] data = new float[samples];
float[] freqs = new float[] { 523.25f, 659.25f, 783.99f, 1046.50f };
for (int i = 0; i < samples; i++)
{
float t = (float)i / sampleRate;
float sampleVal = 0f;
for (int f = 0; f < freqs.Length; f++)
{
float startTime = f * 0.1f;
if (t >= startTime)
{
float noteTime = t - startTime;
float env = Mathf.Exp(-noteTime * 4.5f);
sampleVal += Mathf.Sin(2f * Mathf.PI * freqs[f] * noteTime) * env * 0.2f;
}
}
data[i] = Mathf.Clamp(sampleVal, -0.9f, 0.9f);
}
AudioClip clip = AudioClip.Create("SuccessChime", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.5f);
}
/// <summary>
/// Звук разворачивания/чтения секретной записки (шум бумаги + легкий звон)
/// </summary>
public static void PlayPaperRustle()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.18f);
float[] data = new float[samples];
System.Random rnd = new System.Random(42);
for (int i = 0; i < samples; i++)
{
float env = 1f - (float)i / samples;
float noise = ((float)rnd.NextDouble() * 2f - 1f) * 0.15f;
float chime = Mathf.Sin(2f * Mathf.PI * 1400f * ((float)i / sampleRate)) * 0.12f;
data[i] = (noise + chime) * env;
}
AudioClip clip = AudioClip.Create("PaperRustle", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.4f);
}
/// <summary>
/// Звук сочного плевка слюны изо рта (хлёсткий выброс сжатого воздуха и мокрый хлопок)
/// </summary>
public static void PlayAcidSpitSound()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.28f);
float[] data = new float[samples];
System.Random rnd = new System.Random();
for (int i = 0; i < samples; i++)
{
float t = (float)i / samples;
// Фаза 1: резкий хлёсткий хлопок воздуха пасти
float popEnv = Mathf.Exp(-t * 22f);
float popTone = Mathf.Sin(2f * Mathf.PI * Mathf.Lerp(650f, 160f, t) * ((float)i / sampleRate));
// Фаза 2: свист и шипение вылетающей тягучей жидкости
float hissEnv = Mathf.Sin(t * Mathf.PI) * Mathf.Exp(-t * 4.5f);
float hissNoise = ((float)rnd.NextDouble() * 2f - 1f) * 0.42f;
// Фаза 3: влажное клокотание
float wetBubble = Mathf.Sin(2f * Mathf.PI * 90f * ((float)i / sampleRate)) * 0.25f;
data[i] = (popTone * 0.7f * popEnv) + (hissNoise + wetBubble) * hissEnv;
}
AudioClip clip = AudioClip.Create("SalivaSpit", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.75f);
}
/// <summary>
/// Звук смачного шлепка слюны о поверхность и шипения токсина
/// </summary>
public static void PlayAcidImpact()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.32f);
float[] data = new float[samples];
System.Random rnd = new System.Random();
for (int i = 0; i < samples; i++)
{
float t = (float)i / samples;
// Резкий мокрый шлепок в начале
float slapEnv = Mathf.Exp(-t * 18f);
float slap = ((float)rnd.NextDouble() * 2f - 1f) * 0.65f + Mathf.Sin(2f * Mathf.PI * 130f * ((float)i / sampleRate)) * 0.35f;
// Последующее шипение кислоты
float sizzleEnv = (1f - t) * Mathf.Exp(-t * 3.5f);
float sizzle = ((float)rnd.NextDouble() * 2f - 1f) * 0.35f;
data[i] = (slap * slapEnv) + (sizzle * sizzleEnv * 0.6f);
}
AudioClip clip = AudioClip.Create("SalivaImpact", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.8f);
}
/// <summary>
/// Звук шипения кислоты на полу
/// </summary>
public static void PlayAcidHiss()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.5f);
float[] data = new float[samples];
System.Random rnd = new System.Random();
for (int i = 0; i < samples; i++)
{
float t = (float)i / samples;
float env = 1f - t;
float hiss = ((float)rnd.NextDouble() * 2f - 1f) * 0.22f;
data[i] = hiss * env;
}
AudioClip clip = AudioClip.Create("AcidHiss", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.35f);
}
/// <summary>
/// Оглушительный ультразвуковой крик босса, вызывающий акустическое ослепление
/// </summary>
public static void PlayAcousticScreech()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 1.5f);
float[] data = new float[samples];
System.Random rnd = new System.Random();
for (int i = 0; i < samples; i++)
{
float t = (float)i / sampleRate;
float env = Mathf.Clamp01(t / 0.15f) * Mathf.Pow(1f - (float)i / samples, 0.8f);
float lowRumble = Mathf.Sin(2f * Mathf.PI * 65f * t) * 0.5f;
float screechFreq = Mathf.Lerp(1600f, 2800f, Mathf.PingPong(t * 3f, 1f));
float screech = Mathf.Sin(2f * Mathf.PI * screechFreq * t) * 0.35f;
float distortion = ((float)rnd.NextDouble() * 2f - 1f) * 0.2f;
data[i] = Mathf.Clamp((lowRumble + screech + distortion) * env, -1f, 1f) * 0.8f;
}
AudioClip clip = AudioClip.Create("AcousticScreech", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.85f);
}
/// <summary>
/// Глухой рев босса при пробуждении
/// </summary>
public static void PlayBossRoar()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 1.8f);
float[] data = new float[samples];
System.Random rnd = new System.Random();
for (int i = 0; i < samples; i++)
{
float t = (float)i / sampleRate;
float env = Mathf.Clamp01(t / 0.2f) * Mathf.Pow(1f - (float)i / samples, 0.7f);
float pitch = Mathf.Lerp(120f, 50f, (float)i / samples);
float roar = Mathf.Sin(2f * Mathf.PI * pitch * t) * 0.6f;
float growl = Mathf.Sin(2f * Mathf.PI * (pitch * 0.5f) * t) * 0.4f;
float noise = ((float)rnd.NextDouble() * 2f - 1f) * 0.25f;
data[i] = Mathf.Clamp((roar + growl + noise) * env, -1f, 1f) * 0.9f;
}
AudioClip clip = AudioClip.Create("BossRoar", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.9f);
}
/// <summary>
/// Сигнал тревоги и карантинной блокировки
/// </summary>
public static void PlayLockdownAlarm()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.9f);
float[] data = new float[samples];
for (int i = 0; i < samples; i++)
{
float t = (float)i / sampleRate;
float freq = Mathf.Sin(2f * Mathf.PI * 2.5f * t) > 0f ? 880f : 660f;
float env = 1f - ((float)i / samples);
data[i] = Mathf.Sin(2f * Mathf.PI * freq * t) * env * 0.5f;
}
AudioClip clip = AudioClip.Create("LockdownAlarm", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.6f);
}
/// <summary>
/// Звук гидравлических ворот / разблокировки шлюза
/// </summary>
public static void PlayDoorHydraulic()
{
int sampleRate = 44100;
int samples = (int)(sampleRate * 0.8f);
float[] data = new float[samples];
System.Random rnd = new System.Random();
for (int i = 0; i < samples; i++)
{
float t = (float)i / samples;
float env = Mathf.Sin(t * Mathf.PI);
float steam = ((float)rnd.NextDouble() * 2f - 1f) * 0.25f;
float lowClank = Mathf.Sin(2f * Mathf.PI * 90f * ((float)i / sampleRate)) * 0.35f;
data[i] = (steam + lowClank) * env;
}
AudioClip clip = AudioClip.Create("DoorHydraulic", samples, 1, sampleRate, false);
clip.SetData(data, 0);
PlayOneShot(clip, 0.65f);
}
private static AudioClip CreateToneClip(float frequency, float duration, float volume)
{
int sampleRate = 44100;
int samples = (int)(sampleRate * duration);
float[] data = new float[samples];
for (int i = 0; i < samples; i++)
{
float t = (float)i / sampleRate;
float env = 1f - ((float)i / samples);
data[i] = Mathf.Sin(2f * Mathf.PI * frequency * t) * env * volume;
}
AudioClip clip = AudioClip.Create($"Tone_{frequency}", samples, 1, sampleRate, false);
clip.SetData(data, 0);
return clip;
}
private static void PlayOneShot(AudioClip clip, float volumeScale)
{
if (clip == null) return;
var src = GetAudioSource();
if (src != null)
{
src.PlayOneShot(clip, volumeScale);
}
}
}