333 lines
12 KiB
C#
333 lines
12 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|