reckfellENGINE

Звуковой плеер: запуск, пауза и положение

Полные примеры C# без внешних звуков: интерфейс, голоса, 2D и 3D со слушателем.

На этой странице

Какой плеер нужен

ТипНазначениеОт чего зависит громкость
AudioStreamPlayerМузыка и интерфейсПлеер и шины; положение в мире не учитывается
AudioStreamPlayer2DИсточник в мире 2DПлеер, дистанция и направление относительно слушателя, затем шины
AudioStreamPlayer3DИсточник в мире 3DМодель затухания, слушатель, направление, дополнительные параметры 3D, затем шины

AudioStream — ресурс, а не уже играющий голос. Для файла проекта назначьте в Stream загруженный ресурс ResourceLoader.Load<AudioStream>(path) и проверьте null до запуска. Встроенные форматы включают WAV, Ogg Vorbis и MP3; конкретные настройки импорта определяют сжатие и цикл. Для коротких эффектов важны задержка декодирования и число одновременных голосов, для музыки — размер ресурса и стоимость декодирования. Не выбирайте формат только по расширению.

Запустить пример

Добавьте два файла ниже в C#-проект. Создайте уровень с корнем Control, прикрепите SoundDemo.cs, соберите и запустите F6. Кнопка Запустить заново воспроизводит секундный тон; нажмите паузу до его конца, затем продолжите. Остановить завершает воспроизведение без события естественного конца. Внешний WAV не нужен.

DemoTone создаёт моно PCM 16 bit, 22 050 Гц с короткими плавными краями. Data содержит сырые little-endian сэмплы без заголовка WAV. LoopBegin и LoopEnd — индексы сэмплов, не секунды; в стерео они означают кадры с парой каналов. Параметры и лимит 30 секунд относятся к этому учебному генератору.

DemoTone.cs
C#
using System;
using System.Buffers.Binary;
using Reckfell;

public static class DemoTone
{
    public static AudioStreamWav Create(float frequency = 440, double seconds = .3,
        float amplitude = .15f, bool loop = false)
    {
        const int rate = 22050;
        if (!float.IsFinite(frequency) || frequency <= 0 || frequency >= rate / 2f ||
            !double.IsFinite(seconds) || seconds < .02 || seconds > 30 ||
            !float.IsFinite(amplitude) || amplitude < 0 || amplitude > 1)
            throw new ArgumentOutOfRangeException(nameof(frequency), "Invalid teaching-tone parameters.");
        int count = (int)(seconds * rate);
        byte[] data = new byte[count * 2];
        for (int i = 0; i < count; i++)
        {
            double fade = Math.Min(1, Math.Min(i, count - 1 - i) / (rate * .005));
            short value = (short)(Math.Sin(2 * Math.PI * frequency * i / rate) * amplitude * fade * short.MaxValue);
            BinaryPrimitives.WriteInt16LittleEndian(data.AsSpan(i * 2, 2), value);
        }
        return new AudioStreamWav
        {
            Format = AudioStreamWav.FormatEnum.Format16Bits, MixRate = rate, Stereo = false,
            Data = data, LoopMode = loop ? AudioStreamWav.LoopModeEnum.Forward : AudioStreamWav.LoopModeEnum.Disabled,
            LoopBegin = 0, LoopEnd = count
        };
    }
}
SoundDemo.cs
C#
using System;
using Reckfell;

public partial class SoundDemo : Control
{
    public AudioStreamPlayer Voice { get; private set; } = null!;
    public int FinishedCount { get; private set; }
    public Label Status { get; private set; } = null!;
    private bool _ru;
    private string Text(string ru, string en) => _ru ? ru : en;

    public override void _Ready()
    {
        _ru = TranslationServer.GetLocale().StartsWith("ru", StringComparison.OrdinalIgnoreCase);
        Voice = new AudioStreamPlayer
        {
            Name = "Voice", Stream = DemoTone.Create(seconds: 1), MaxPolyphony = 1,
            PlaybackType = AudioServer.PlaybackType.Stream
        };
        AddChild(Voice);
        var layout = new VBoxContainer { Position = new Vector2(24, 24) }; AddChild(layout);
        Status = new Label { Text = Text("Готово", "Ready") }; layout.AddChild(Status);
        AddButton(layout, Text("Запустить заново", "Restart"), Restart);
        AddButton(layout, Text("Пауза", "Pause"), () => SetPaused(true));
        AddButton(layout, Text("Продолжить", "Resume"), () => SetPaused(false));
        AddButton(layout, Text("Остановить", "Stop"), StopSound);
        Voice.Finished += () => { FinishedCount++; Status.Text = Text("Завершено", "Finished"); };
    }

    private static void AddButton(VBoxContainer parent, string text, Action action)
    {
        var button = new Button { Text = text }; parent.AddChild(button); button.Pressed += action;
    }

    public void Restart()
    {
        Voice.Stop(); Voice.Play();
        Status.Text = Text("Воспроизведение", "Playing");
    }

    public void SetPaused(bool paused)
    {
        if (!Voice.IsPlaying() && !Voice.StreamPaused) return;
        Voice.StreamPaused = paused;
        Status.Text = paused ? Text("Пауза", "Paused") : Text("Воспроизведение", "Playing");
    }

    public void StopSound()
    {
        Voice.Stop(); Status.Text = Text("Остановлено", "Stopped");
    }
}

Состояния и несколько голосов

Действие / проверкаРезультат в режиме Stream
Play()Создаёт голос с начала; Play(seconds) задаёт начальную позицию
StreamPaused = trueПриостанавливает существующие голоса; IsPlaying() становится false, HasStreamPlayback() остаётся true
StreamPaused = falseВозобновляет приостановленные голоса
Stop()Останавливает все голоса этого плеера без Finished
Seek(seconds)Перезапускает воспроизведение с позиции, только если плеер сейчас играет; на остановке или паузе ничего не делает
Естественный конецВыдаёт Finished; при цикле конец не выдаётся

StreamPaused не хранит предварительную команду паузы, когда голосов ещё нет. Вызывайте её после Play(). Проверка if (!IsPlaying()) return перед возобновлением ошибочна: на паузе IsPlaying уже false. В примере учитываются оба состояния.

MaxPolyphony = 1 оставляет один голос; новый Play вытесняет самый старый. При большем лимите Play добавляет перекрывающийся звук. Finished означает завершение одного или нескольких голосов за обновление: остальные могут ещё играть. Это не гарантированный сигнал «весь плеер пуст». После события проверьте HasStreamPlayback(). Для независимо управляемых источников используйте отдельные плееры или предназначенный для этого полифонический поток.

GetStreamPlayback() возвращает объект последнего созданного воспроизведения. Сохранённая ссылка на него не заменяет проверку текущего состояния плеера. Удаление плеера прекращает его звук: временный объект взрыва должен жить до конца звука либо передать воспроизведение более долгоживущему владельцу.

Источник 2D

Оставьте DemoTone в проекте. В отдельном уровне прикрепите SpatialSound2D.cs к корню Entity2D; задайте viewport 512 × 288, запустите F6. Белая точка — слушатель, синяя — источник. Стрелки перемещают источник: слева сильнее левый канал, справа — правый, дальше звук затихает. Используются стандартные действия ui_left/right/up/down.

SpatialSound2D.cs
C#
using Reckfell;

public partial class SpatialSound2D : Entity2D
{
    public AudioListener2D Listener { get; private set; } = null!;
    public AudioStreamPlayer2D Source { get; private set; } = null!;

    public override void _Ready()
    {
        GetViewport().AudioListenerEnable2D = true;
        Listener = new AudioListener2D { Name = "Listener", Position = new Vector2(256, 144) };
        AddChild(Listener); Listener.MakeCurrent();
        Source = new AudioStreamPlayer2D
        {
            Name = "Source", Position = new Vector2(100, 144), Stream = DemoTone.Create(loop: true),
            MaxDistance = 600, Attenuation = 1, PanningStrength = 2,
            PlaybackType = AudioServer.PlaybackType.Stream
        };
        AddChild(Source); Source.Play();
    }

    public override void _Process(double delta)
    {
        Source.Position += Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down") * 160 * (float)delta;
        QueueRedraw();
    }

    public override void _Draw()
    {
        if (Source is null || Listener is null) return;
        DrawCircle(Listener.Position, 8, Colors.White);
        DrawCircle(Source.Position, 8, new Color(.15f, .35f, 1));
        DrawLine(Listener.Position, Source.Position, new Color(.4f, .4f, .4f), 1);
    }
}

Без явного AudioListener2D слушатель находится в центре видимой области соответствующего viewport. Камера меняет преобразование этой области. Явный слушатель позволяет привязать слух к персонажу. MaxDistance в 2D измеряется в единицах мира 2D, обычно пикселях; панорама также зависит от размера и масштаба viewport. Не копируйте туда метры из 3D.

Источник 3D

В другом уровне прикрепите SpatialSound3D.cs к корню Entity3D. DemoTone остаётся в проекте. Код создаёт слушателя в начале координат, камеру и синюю сферу-источник; стрелки перемещают её по XZ. Камера показывает мир, отдельный AudioListener3D определяет точку слуха.

SpatialSound3D.cs
C#
using Reckfell;

public partial class SpatialSound3D : Entity3D
{
    public AudioListener3D Listener { get; private set; } = null!;
    public AudioStreamPlayer3D Source { get; private set; } = null!;

    public override void _Ready()
    {
        GetViewport().AudioListenerEnable3D = true;
        Listener = new AudioListener3D { Name = "Listener" }; AddChild(Listener); Listener.MakeCurrent();
        Source = new AudioStreamPlayer3D
        {
            Name = "Source", Position = new Vector3(3, 0, -3), Stream = DemoTone.Create(loop: true),
            UnitSize = 1, MaxDistance = 30, PanningStrength = 1,
            AttenuationFilterDb = 0, PlaybackType = AudioServer.PlaybackType.Stream
        };
        AddChild(Source);
        var material = new StandardMaterial3D
        {
            ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
            AlbedoColor = new Color(.15f, .35f, 1)
        };
        Source.AddChild(new MeshInstance3D { Mesh = new SphereMesh { Radius = .2f, Height = .4f }, MaterialOverride = material });
        var camera = new Camera3D { Position = new Vector3(0, 6, 10) }; AddChild(camera);
        camera.LookAt(Vector3.Zero); camera.MakeCurrent();
        Source.Play();
    }

    public override void _Process(double delta)
    {
        Vector2 move = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
        Source.Position += new Vector3(move.X, 0, move.Y) * 4 * (float)delta;
    }
}

Здесь единицы мира трактуются как метры: UnitSize = 1, MaxDistance = 30. UnitSize влияет на модель затухания и не меняет размер объектов. Стена сама по себе не заглушает источник: коллизии не создают автоматическую акустическую окклюзию. Для неё нужна отдельная логика, например проверка препятствия и изменение фильтра/громкости. Области могут переопределять аудиошину; это отдельная настройка Area2D/Area3D.

В позиционных плеерах запуск и расчёт панорамы обслуживаются физическим обновлением. Не проверяйте итоговую панораму в той же строке сразу после Play. Примеры используют PlaybackType.Stream, чтобы шины и эффекты обрабатывались микшером движка. Поведение Sample, особенно в Web, проверяйте отдельно; эти примеры его не тестируют.

Диагностика

СимптомПроверка
Продолжение не работаетНе отсекает ли код паузу через !IsPlaying()
Звук обрывается при повтореMaxPolyphony, удаление плеера, Stop перед Play
Обработчик конца сработал, а звук осталсяДругие голоса того же плеера или другие плееры
Позиционный источник молчитТот же мир и viewport, слушатель включён и current, расстояние и громкость
Цикл изменился у нескольких источниковОни разделяют один AudioStream; создайте отдельный ресурс для других параметров
Звук стал выше и корочеPitchScale меняет высоту вместе со скоростью; это не независимый time stretch

Категории громкости · Синхронизация времени

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.