151 lines
5.2 KiB
C#
151 lines
5.2 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);
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|