صدا و موسیقی را به بازی Flutter خود اضافه کنید

1. قبل از شروع

بازی ها تجربیات سمعی و بصری هستند. Flutter یک ابزار عالی برای ایجاد تصاویری زیبا و رابط کاربری قوی است، بنابراین شما را از جنبه بصری چیزها دور می کند. عنصر گم شده ای که باقی مانده است صدا است. در این کد لبه یاد می گیرید که چگونه از پلاگین flutter_soloud برای معرفی صدا و موسیقی با تاخیر کم به پروژه خود استفاده کنید. شما با یک داربست اولیه شروع می کنید تا بتوانید مستقیماً به قسمت های جالب بپرید.

تصویری با دست طراحی شده از هدفون.

البته می‌توانید از آنچه در اینجا یاد می‌گیرید برای افزودن صدا به برنامه‌های خود استفاده کنید، نه فقط بازی‌ها. اما در حالی که تقریباً همه بازی‌ها به صدا و موسیقی نیاز دارند، اکثر برنامه‌ها نیازی به این کار ندارند، بنابراین این نرم‌افزار روی بازی‌ها تمرکز دارد.

پیش نیازها

  • آشنایی اولیه با فلاتر
  • آشنایی با نحوه اجرا و اشکال زدایی برنامه های Flutter.

آنچه یاد می گیرید

  • نحوه پخش صداهای تک شات.
  • نحوه پخش و سفارشی کردن حلقه های موسیقی بدون شکاف.
  • چگونه صداها را در داخل و خارج محو کنیم.
  • نحوه اعمال افکت های محیطی روی صداها
  • نحوه برخورد با استثناها
  • چگونه می توان همه این ویژگی ها را در یک کنترلر صوتی محصور کرد.

آنچه شما نیاز دارید

  • فلاتر SDK
  • یک ویرایشگر کد به انتخاب شما

2. راه اندازی کنید

  1. فایل های زیر را دانلود کنید. اگر اتصال کندی دارید، نگران نباشید. بعداً به فایل‌های واقعی نیاز دارید، بنابراین می‌توانید در حین کار به آنها اجازه دانلود کنید.
  1. یک پروژه فلاتر با نام دلخواه خود ایجاد کنید.
  1. یک فایل lib/audio/audio_controller.dart در پروژه ایجاد کنید.
  2. در فایل کد زیر را وارد کنید:

lib/audio/audio_controller.dart

import 'dart:async';

import 'package:logging/logging.dart';

class AudioController {
  static final Logger _log = Logger('AudioController');

  Future<void> initialize() async {
    // TODO
  }

  void dispose() {
    // TODO
  }

  Future<void> playSound(String assetKey) async {
    _log.warning('Not implemented yet.');
  }

  Future<void> startMusic() async {
    _log.warning('Not implemented yet.');
  }

  void fadeOutMusic() {
    _log.warning('Not implemented yet.');
  }

  void applyFilter() {
    // TODO
  }

  void removeFilter() {
    // TODO
  }
}

همانطور که می بینید، این فقط یک اسکلت برای عملکرد آینده است. ما همه آن را در طول این کد لبه اجرا خواهیم کرد.

  1. سپس فایل lib/main.dart را باز کنید و محتوای آن را با کد زیر جایگزین کنید:

lib/main.dart

import 'dart:developer' as dev;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';

import 'audio/audio_controller.dart';

void main() async {
  // The `flutter_soloud` package logs everything
  // (from severe warnings to fine debug messages)
  // using the standard `package:logging`.
  // You can listen to the logs as shown below.
  Logger.root.level = kDebugMode ? Level.FINE : Level.INFO;
  Logger.root.onRecord.listen((record) {
    dev.log(
      record.message,
      time: record.time,
      level: record.level.value,
      name: record.loggerName,
      zone: record.zone,
      error: record.error,
      stackTrace: record.stackTrace,
    );
  });

  WidgetsFlutterBinding.ensureInitialized();

  final audioController = AudioController();
  await audioController.initialize();

  runApp(
    MyApp(audioController: audioController),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({required this.audioController, super.key});

  final AudioController audioController;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter SoLoud Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
        useMaterial3: true,
      ),
      home: MyHomePage(audioController: audioController),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.audioController});

  final AudioController audioController;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const _gap = SizedBox(height: 16);

  bool filterApplied = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter SoLoud Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            OutlinedButton(
              onPressed: () {
                widget.audioController.playSound('assets/sounds/pew1.mp3');
              },
              child: const Text('Play Sound'),
            ),
            _gap,
            OutlinedButton(
              onPressed: () {
                widget.audioController.startMusic();
              },
              child: const Text('Start Music'),
            ),
            _gap,
            OutlinedButton(
              onPressed: () {
                widget.audioController.fadeOutMusic();
              },
              child: const Text('Fade Out Music'),
            ),
            _gap,
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text('Apply Filter'),
                Checkbox(
                  value: filterApplied,
                  onChanged: (value) {
                    setState(() {
                      filterApplied = value!;
                    });
                    if (filterApplied) {
                      widget.audioController.applyFilter();
                    } else {
                      widget.audioController.removeFilter();
                    }
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
  1. پس از دانلود فایل های صوتی، یک دایرکتوری در ریشه پروژه خود به نام assets ایجاد کنید.
  2. در فهرست assets ، دو زیرشاخه ایجاد کنید، یکی به نام music و دیگری به نام sounds .
  3. فایل های دانلود شده را به پروژه خود منتقل کنید تا فایل آهنگ در فایل assets/music/looped-song.ogg و صداهای pew در فایل های زیر باشد:
  • assets/sounds/pew1.mp3
  • assets/sounds/pew2.mp3
  • assets/sounds/pew3.mp3

ساختار پروژه شما اکنون باید چیزی شبیه به این باشد:

نمای درختی پروژه، با پوشه‌هایی مانند «android»، «ios»، فایل‌هایی مانند «README.md» و «analysis_options.yaml».   در این میان، ما می‌توانیم دایرکتوری «assets» را با زیر شاخه‌های «music» و «sounds»، دایرکتوری «lib» با «main.dart» و یک زیر شاخه «audio» با «audio_controller.dart» و «pubspec» ببینیم. فایل yaml.  فلش ها به دایرکتوری های جدید و فایل هایی که تا کنون لمس کرده اید اشاره می کنند.

اکنون که فایل‌ها وجود دارند، باید به Flutter در مورد آنها بگویید.

  1. فایل pubspec.yaml را باز کنید و سپس قسمت flutter: در پایین فایل را با موارد زیر جایگزین کنید:

pubspec.yaml

...

flutter:
  uses-material-design: true

  assets:
    - assets/music/
    - assets/sounds/
  1. یک وابستگی به بسته flutter_soloud و بسته logging اضافه کنید.

pubspec.yaml

...

dependencies:
  flutter:
    sdk: flutter

  flutter_soloud: ^2.0.0
  logging: ^1.2.0

...
  1. پروژه را اجرا کنید. هنوز هیچ چیز کار نمی کند زیرا شما عملکرد را در بخش های زیر اضافه می کنید.

10f0f751c9c47038.png

/flutter_soloud/src/filters/filters.cpp:21:24: warning: implicit conversion loses integer precision: 'decltype(__x.base() - __y.base())' (aka 'long') to 'int' [-Wshorten-64-to-32];

اینها از کتابخانه زیرین SoLoud C++ می آیند. آنها هیچ تاثیری بر عملکرد ندارند و می توان آنها را با خیال راحت نادیده گرفت.

3. مقداردهی اولیه و خاموش کردن

برای پخش صدا از افزونه flutter_soloud استفاده می کنید. این افزونه بر اساس پروژه SoLoud ، یک موتور صوتی C++ برای بازی‌ها است که توسط Nintendo SNES Classic استفاده می‌شود.

7ce23849b6d0d09a.png

برای مقداردهی اولیه موتور صوتی SoLoud، مراحل زیر را دنبال کنید:

  1. در فایل audio_controller.dart بسته flutter_soloud را وارد کرده و یک فیلد _soloud خصوصی به کلاس اضافه کنید.

lib/audio/audio_controller.dart

import 'dart:ui';

import 'package:flutter_soloud/flutter_soloud.dart';  // ← Add this...
import 'package:logging/logging.dart';

class AudioController {
  static final Logger _log = Logger('AudioController');

  SoLoud? _soloud;                                    // ← ... and this.

  Future<void> initialize() async {
    // TODO
  }

  ...

کنترل کننده صوتی موتور SoLoud زیرین را از طریق این فیلد مدیریت می کند و همه تماس ها را به آن فوروارد می کند.

  1. در متد initialize() کد زیر را وارد کنید:

lib/audio/audio_controller.dart

...

  Future<void> initialize() async {
    _soloud = SoLoud.instance;
    await _soloud!.init();
  }

...

این فیلد _soloud را پر می کند و منتظر مقداردهی اولیه است. به موارد زیر توجه کنید:

  • SoLoud یک فیلد instance تکی را فراهم می کند. هیچ راهی برای نمونه سازی چندین نمونه SoLoud وجود ندارد. این چیزی است که موتور C++ اجازه نمی دهد، بنابراین پلاگین Dart نیز اجازه آن را نمی دهد.
  • راه اندازی پلاگین ناهمزمان است و تا زمانی که متد init() برنگردد تمام نمی شود.
  • برای اختصار در این مثال، شما خطاهای یک بلوک try/catch را دریافت نمی کنید. در کد تولید، شما می خواهید این کار را انجام دهید و هر گونه خطا را به کاربر گزارش دهید.
  1. در متد dispose() کد زیر را وارد کنید:

lib/audio/audio_controller.dart

...

  void dispose() {
    _soloud?.deinit();
  }

...

خاموش کردن SoLoud هنگام خروج از برنامه تمرین خوبی است، اگرچه همه چیز باید خوب کار کند حتی اگر از انجام آن غفلت کنید.

  1. توجه داشته باشید که متد AudioController.initialize() قبلاً از تابع main() فراخوانی شده است. این بدان معناست که با راه‌اندازی مجدد پروژه، SoLoud در پس‌زمینه مقداردهی اولیه می‌شود، اما قبل از اینکه برخی صداها را پخش کنید، هیچ فایده‌ای برای شما نخواهد داشت.

4. پخش صداهای تک شات

یک دارایی را بارگیری کنید و آن را بازی کنید

اکنون که می دانید SoLoud در هنگام راه اندازی مقداردهی اولیه شده است، می توانید از آن بخواهید صداها را پخش کند.

SoLoud بین منبع صوتی، که داده‌ها و ابرداده‌های مورد استفاده برای توصیف یک صدا است، و «نمونه‌های صوتی» آن، که صداهایی هستند که در واقع پخش می‌شوند، تفاوت قائل می‌شود. نمونه ای از منبع صوتی می تواند یک فایل mp3 باشد که در حافظه بارگذاری شده، آماده پخش است و با نمونه ای از کلاس AudioSource نمایش داده می شود. هر بار که این منبع صوتی را پخش می کنید، SoLoud یک "نمونه صدا" ایجاد می کند که با نوع SoundHandle نشان داده می شود.

شما با بارگذاری یک نمونه AudioSource آن را دریافت می کنید. به عنوان مثال، اگر یک فایل mp3 در دارایی های خود دارید، می توانید آن را برای دریافت AudioSource بارگیری کنید. سپس، به SoLoud می‌گویید که این AudioSource را پخش کند. شما می توانید آن را چندین بار، حتی به طور همزمان بازی کنید.

وقتی کارتان با یک منبع صوتی تمام شد، آن را با روش SoLoud.disposeSource() از بین می برید.

برای بارگیری یک دارایی و پخش آن، این مراحل را دنبال کنید:

  1. در متد playSound() کلاس AudioController کد زیر را وارد کنید:

lib/audio/audio_controller.dart

  ...

  Future<void> playSound(String assetKey) async {
    final source = await _soloud!.loadAsset(assetKey);
    await _soloud!.play(source);
  }

  ...
  1. فایل را ذخیره کنید، دوباره بارگیری کنید و سپس پخش صدا را انتخاب کنید. شما باید صدای پیو احمقانه ای را بشنوید. به موارد زیر توجه کنید:
  • آرگومان assetKey ارائه شده چیزی شبیه assets/sounds/pew1.mp3 است — همان رشته ای که برای هر API Flutter بارگذاری دارایی دیگری، مانند ویجت Image.asset() ارائه می دهید.
  • نمونه SoLoud یک متد loadAsset() ارائه می دهد که به صورت ناهمزمان یک فایل صوتی را از دارایی های پروژه Flutter بارگیری می کند و نمونه ای از کلاس AudioSource را برمی گرداند. روش‌های مشابهی برای بارگیری فایل از سیستم فایل (روش loadFile() ) و بارگیری روی شبکه از طریق URL (روش loadUrl() ) وجود دارد.
  • نمونه AudioSource تازه به دست آمده سپس به متد play() SoLoud منتقل می شود. این روش نمونه ای از نوع SoundHandle را برمی گرداند که صدای تازه پخش شده را نشان می دهد. این دسته به نوبه خود می تواند به سایر روش های SoLoud برای انجام کارهایی مانند مکث، توقف یا تغییر حجم صدا منتقل شود.
  • اگرچه play() یک روش ناهمزمان است، پخش اساساً به صورت آنی شروع می شود. بسته flutter_soloud از رابط تابع خارجی دارت (FFI) برای فراخوانی مستقیم و همزمان کد C استفاده می کند. پیام رسانی معمولی بین کد دارت و کد پلتفرم که مشخصه اکثر پلاگین های فلاتر است، در هیچ کجا یافت نمی شود. تنها دلیل ناهمزمان بودن برخی از روش ها این است که برخی از کدهای افزونه در ایزوله خودش اجرا می شود و ارتباط بین ایزوله های دارت ناهمزمان است.
  • شما به سادگی ادعا می کنید که فیلد _soloud با _soloud! . این دوباره برای اختصار است. کد تولید باید به خوبی با شرایطی برخورد کند که توسعه‌دهنده سعی می‌کند صدایی را قبل از اینکه کنترل‌کننده صدا فرصت اولیه‌سازی کامل را داشته باشد پخش کند.

با استثناها برخورد کنید

شاید متوجه شده باشید که یک بار دیگر استثناهای احتمالی را نادیده می گیرید. بیایید آن را برای این روش خاص برای اهداف یادگیری برطرف کنیم. (برای اختصار، Codelab بعد از این بخش به نادیده گرفتن استثناها باز می گردد.)

  • برای مقابله با استثناها در این مورد، دو خط متد playSound() را در یک بلوک try/catch بپیچید و فقط نمونه هایی از SoLoudException را بگیرید.

lib/audio/audio_controller.dart

  ...

  Future<void> playSound(String assetKey) async {
    try {
      final source = await _soloud!.loadAsset(assetKey);
      await _soloud!.play(source);
    } on SoLoudException catch (e) {
      _log.severe("Cannot play sound '$assetKey'. Ignoring.", e);
    }
  }

  ...

SoLoud استثناهای مختلفی مانند استثناهای SoLoudNotInitializedException یا SoLoudTemporaryFolderFailedException را ایجاد می کند. اسناد API هر روش، انواع استثناهایی را که ممکن است ایجاد شوند فهرست می کند.

SoLoud همچنین یک کلاس والد برای تمام استثناهای خود ارائه می دهد، استثنای SoLoudException ، به طوری که شما می توانید تمام خطاهای مربوط به عملکرد موتور صوتی را دریافت کنید. این به ویژه در مواردی که پخش صدا حیاتی نیست مفید است. به عنوان مثال، وقتی نمی‌خواهید جلسه بازی بازیکن را خراب کنید، فقط به این دلیل که یکی از صداهای پیو-پیو نمی‌تواند بارگذاری شود.

همانطور که احتمالاً انتظار دارید، متد loadAsset() همچنین می تواند یک خطای FlutterError ایجاد کند اگر کلید دارایی را ارائه کنید که وجود ندارد. تلاش برای بارگیری دارایی‌هایی که با بازی همراه نیستند، معمولاً چیزی است که باید به آن توجه کنید، بنابراین یک خطا است .

پخش صداهای مختلف

شاید متوجه شده باشید که فقط فایل pew1.mp3 را پخش می کنید، اما دو نسخه دیگر از صدا در فهرست دارایی ها وجود دارد. وقتی بازی‌ها چندین نسخه از یک صدا دارند و نسخه‌های مختلف را به صورت تصادفی یا چرخشی پخش می‌کنند، اغلب طبیعی‌تر به نظر می‌رسد. به عنوان مثال، این کار از صدای پا و شلیک بیش از حد یکنواخت و در نتیجه ساختگی جلوگیری می کند.

  • به عنوان یک تمرین اختیاری، کد را تغییر دهید تا هر بار که روی دکمه ضربه می‌زنید، صدایی متفاوت پخش شود.

یک تصویر از

5. حلقه های موسیقی را پخش کنید

صداهای طولانی تر را مدیریت کنید

برخی از صداها برای مدت زمان طولانی پخش می شوند. موسیقی مثال بارز آن است، اما بسیاری از بازی‌ها نیز محیطی را به نمایش می‌گذارند، مانند زوزه‌کشی باد در راهروها، آواز خواندن راهبان از راه دور، صدای جیر جیر فلز چند صد ساله، یا سرفه‌های دور بیماران.

اینها منابع صوتی با زمان پخش هستند که می توانند در چند دقیقه اندازه گیری شوند. باید آنها را پیگیری کنید تا بتوانید در صورت نیاز آنها را متوقف یا متوقف کنید. آنها همچنین اغلب توسط فایل‌های بزرگ پشتیبانی می‌شوند و می‌توانند حافظه زیادی را مصرف کنند، بنابراین دلیل دیگری برای ردیابی آنها این است که بتوانید نمونه AudioSource را زمانی که دیگر مورد نیاز نیست، از بین ببرید.

به همین دلیل، یک فیلد خصوصی جدید را به AudioController معرفی خواهید کرد. این یک دسته برای آهنگ در حال پخش است، در صورت وجود. خط زیر را اضافه کنید:

lib/audio/audio_controller.dart

...

class AudioController {
  static final Logger _log = Logger('AudioController');

  SoLoud? _soloud;

  SoundHandle? _musicHandle;    // ← Add this.

  ...

موسیقی را شروع کنید

در اصل، پخش موسیقی با پخش صدای تک شات تفاوتی ندارد. شما هنوز هم ابتدا باید فایل assets/music/looped-song.ogg را به عنوان نمونه ای از کلاس AudioSource بارگیری کنید، سپس از متد play() SoLoud برای پخش آن استفاده کنید.

با این حال، این بار، دسته صدا را که متد play() برمی گرداند، نگه می دارید تا صدا را در حین پخش دستکاری کنید.

  • اگر می خواهید، متد AudioController.startMusic() را به تنهایی پیاده سازی کنید. اگر برخی از جزئیات را به درستی دریافت نکنید، اشکالی ندارد. نکته مهم این است که موسیقی با انتخاب Start music شروع می شود.

در اینجا پیاده سازی مرجع است:

lib/audio/audio_controller.dart

...

  Future<void> startMusic() async {
    if (_musicHandle != null) {
      if (_soloud!.getIsValidVoiceHandle(_musicHandle!)) {
        _log.info('Music is already playing. Stopping first.');
        await _soloud!.stop(_musicHandle!);
      }
    }
    final musicSource = await _soloud!
        .loadAsset('assets/music/looped-song.ogg', mode: LoadMode.disk);
    _musicHandle = await _soloud!.play(musicSource);
  }

...

توجه داشته باشید که فایل موسیقی را در حالت دیسک بارگذاری می کنید (مجموعه LoadMode.disk ). این به سادگی به این معنی است که فایل فقط در صورت نیاز به صورت تکه بارگذاری می شود. برای صدای طولانی‌تر، معمولاً بهتر است در حالت دیسک بارگیری شود. برای جلوه‌های صوتی کوتاه، بارگیری و فشرده‌سازی آن‌ها در حافظه (عدد پیش‌فرض LoadMode.memory ) منطقی‌تر است.

با این حال شما چند مشکل دارید. اول اینکه موسیقی خیلی بلند است و بر صداها غلبه می کند. در بیشتر بازی‌ها، موسیقی در بیشتر مواقع در پس‌زمینه قرار دارد و به صداهای آموزنده‌تر، مانند گفتار و جلوه‌های صوتی، مرکز توجه است. رفع این مشکل با استفاده از پارامتر حجم از روش پخش آسان است. برای مثال می توانید _soloud!.play(musicSource, volume: 0.6) امتحان کنید تا آهنگ را با صدای 60 درصد پخش کنید. همچنین، می‌توانید صدا را در هر نقطه بعدی با چیزی مانند _soloud!.setVolume(_musicHandle, 0.6) تنظیم کنید.

مشکل دوم این است که آهنگ ناگهان قطع می شود. دلیلش این است که آهنگی است که قرار است به صورت حلقه ای پخش شود و نقطه شروع حلقه ابتدای فایل صوتی نیست .

88d2c57fffdfe996.png

این یک انتخاب محبوب برای موسیقی بازی است زیرا به این معنی است که آهنگ با یک مقدمه طبیعی شروع می شود و سپس تا زمانی که لازم باشد بدون یک نقطه حلقه واضح پخش می شود. زمانی که بازی باید از آهنگ در حال پخش خارج شود، به سادگی آهنگ را محو می کند.

خوشبختانه، SoLoud راه هایی برای پخش صدای حلقه ای ارائه می دهد. متد play() یک مقدار بولی برای پارامتر looping و همچنین مقدار نقطه شروع حلقه را به عنوان پارامتر loopingStartAt می گیرد. کد حاصل به شکل زیر است:

lib/audio/audio_controller.dart

...

_musicHandle = await _soloud!.play(
  musicSource,
  volume: 0.6,
  looping: true,
  // ↓ The exact timestamp of the start of the loop.
  loopingStartAt: const Duration(seconds: 25, milliseconds: 43),
);

...

اگر پارامتر loopingStartAt را تنظیم نکنید، به طور پیش فرض Duration.zero (به عبارت دیگر شروع فایل صوتی) است. اگر آهنگی دارید که بدون هیچ مقدمه ای یک حلقه کامل است، این همان چیزی است که می خواهید.

  • برای اطمینان از اینکه منبع صوتی پس از اتمام پخش به درستی از بین می رود، به جریان allInstancesFinished که هر منبع صوتی ارائه می دهد گوش دهید. با تماس‌های ثبت‌نام اضافه شده، متد startMusic() به شکل زیر است:

lib/audio/audio_controller.dart

...

  Future<void> startMusic() async {
    if (_musicHandle != null) {
      if (_soloud!.getIsValidVoiceHandle(_musicHandle!)) {
        _log.info('Music is already playing. Stopping first.');
        await _soloud!.stop(_musicHandle!);
      }
    }
    _log.info('Loading music');
    final musicSource = await _soloud!
        .loadAsset('assets/music/looped-song.ogg', mode: LoadMode.disk);
    musicSource.allInstancesFinished.first.then((_) {
      _soloud!.disposeSource(musicSource);
      _log.info('Music source disposed');
      _musicHandle = null;
    });

    _log.info('Playing music');
    _musicHandle = await _soloud!.play(
      musicSource,
      volume: 0.6,
      looping: true,
      loopingStartAt: const Duration(seconds: 25, milliseconds: 43),
    );
  }

...

محو صدا

مشکل بعدی شما این است که موسیقی هرگز تمام نمی شود. بیایید یک fade را اجرا کنیم.

یکی از راه‌هایی که می‌توانید fade را پیاده‌سازی کنید، داشتن نوعی تابعی است که چندین بار در ثانیه فراخوانی می‌شود، مانند Ticker یا Timer.periodic ، و صدای موسیقی را با کاهش‌های کوچک کاهش دهید. این کار می کند، اما کار زیادی است.

خوشبختانه، SoLoud روش های مناسبی برای آتش و فراموش کردن ارائه می دهد که این کار را برای شما انجام می دهد. در اینجا آمده است که چگونه می توانید موسیقی را در طول پنج ثانیه محو کنید و سپس نمونه صدا را متوقف کنید تا منابع CPU را بی جهت مصرف نکند. متد fadeOutMusic() با این کد جایگزین کنید:

lib/audio/audio_controller.dart

...

  void fadeOutMusic() {
    if (_musicHandle == null) {
      _log.info('Nothing to fade out');
      return;
    }
    const length = Duration(seconds: 5);
    _soloud!.fadeVolume(_musicHandle!, 0, length);
    _soloud!.scheduleStop(_musicHandle!, length);
  }

...

6. اعمال افکت ها

یکی از مزیت‌های بزرگ داشتن یک موتور صوتی مناسب این است که می‌توانید پردازش صدا را انجام دهید، مانند مسیریابی برخی صداها از طریق یک Reverb، یک اکولایزر یا یک فیلتر پایین گذر.

در بازی ها می توان از این برای تمایز شنوایی مکان ها استفاده کرد. به عنوان مثال، صدای کف زدن در یک جنگل متفاوت از یک پناهگاه بتنی است. در حالی که یک جنگل به دفع و جذب صدا کمک می کند، دیواره های لخت یک پناهگاه امواج صوتی را به عقب منعکس می کنند که منجر به طنین می شود. به طور مشابه، صدای افراد وقتی از دیوار شنیده می شود متفاوت است. فرکانس‌های بالاتر این صداها با عبور از محیط جامد راحت‌تر تضعیف می‌شوند و در نتیجه یک اثر فیلتر پایین‌گذر ایجاد می‌شود.

تصویری از صحبت دو نفر در یک اتاق. امواج صوتی نه تنها مستقیماً از یک نفر به شخص دیگر می روند، بلکه از دیوارها و سقف نیز پرتاب می شوند.

SoLoud چندین افکت صوتی مختلف ارائه می دهد که می توانید آنها را روی صدا اعمال کنید.

  • برای اینکه به نظر برسد پخش کننده شما در یک اتاق بزرگ است، مانند کلیسای جامع یا غار، از فهرست FilterType.freeverbFilter استفاده کنید:

lib/audio/audio_controller.dart

...

  void applyFilter() {
    _soloud!.addGlobalFilter(FilterType.freeverbFilter);
    _soloud!.setFilterParameter(FilterType.freeverbFilter, 0, 0.2);
    _soloud!.setFilterParameter(FilterType.freeverbFilter, 2, 0.9);
  }

  void removeFilter() {
    _soloud!.removeGlobalFilter(FilterType.freeverbFilter);
  }

...

همانطور که می بینید، با فیلترها در قلمرو سطح پایین تری جستجو می کنید. تنظیم پارامتر فیلتر با شاخص پارامتر انجام می شود. برای مثال، پارامتر Wet freeverb دارای اندیس 0 و پارامتر Room Size دارای اندیس 2 است.

با کد قبلی، کارهای زیر را انجام می دهید:

  • فیلتر freeverb را به صورت سراسری یا در کل میکس صدا، نه تنها یک صدا، فعال کنید.
  • پارامتر Wet را روی 0.2 تنظیم کنید، به این معنی که صدای حاصل 80٪ اصلی و 20٪ خروجی اثر Reverb خواهد بود. اگر این پارامتر را روی 1.0 تنظیم کنید، مثل این است که فقط امواج صوتی را می شنوید که از دیوارهای دور اتاق به شما بازمی گردد و هیچ صدای اصلی را نمی شنوید.
  • پارامتر Room Size را روی 0.9 قرار دهید. شما می توانید این پارامتر را به دلخواه تغییر دهید یا حتی آن را به صورت پویا تغییر دهید. 1.0 یک غار عظیم است در حالی که 0.0 یک حمام است.
  • اگر می خواهید، کد را تغییر دهید و یکی از فیلترهای زیر یا ترکیبی از فیلترهای زیر را اعمال کنید:
  • FilterType.biquadResonantFilter (می تواند به عنوان فیلتر پایین گذر استفاده شود)
  • FilterType.eqFilter
  • FilterType.echoFilter
  • FilterType.lofiFilter
  • FilterType.flangerFilter
  • FilterType.bassboostFilter
  • FilterType.waveShaperFilter
  • FilterType.robotizeFilter
  • FilterType.freeverbFilter

7. تبریک می گویم

شما یک کنترلر صوتی اجرا کرده اید که صداها را پخش می کند، موسیقی را حلقه می کند و جلوه ها را اعمال می کند.

بیشتر بدانید

  • سعی کنید کنترل‌کننده صدا را با ویژگی‌هایی مانند بارگیری از قبل صداها در هنگام راه‌اندازی، پخش آهنگ‌ها به صورت متوالی یا اعمال فیلتر به تدریج در طول زمان بیشتر کنید.
  • اسناد بسته flutter_soloud را بخوانید.
  • صفحه اصلی کتابخانه زیرین C++ را بخوانید.
  • درباره Dart FFI ، فناوری مورد استفاده برای رابط با کتابخانه ++C بیشتر بخوانید.
  • صحبت های گای سامبرگ را در مورد برنامه نویسی صوتی بازی تماشا کنید تا الهام بگیرید. ( یکی طولانی تر نیز وجود دارد.) وقتی گای در مورد "Middleware" صحبت می کند، منظورش کتابخانه هایی مانند SoLoud و FMOD است. بقیه کدها برای هر بازی خاص هستند.
  • بازی خود را بسازید و آن را منتشر کنید.

تصویری از هدفون