הוספת אודיו ומוזיקה למשחק Flutter

1. לפני שמתחילים

משחקים הם חוויות אודיו-ויזואליות. Flutter הוא כלי מצוין ליצירת רכיבים חזותיים יפים וממשק משתמש יציב, כך שהוא עוזר לכם מאוד בצד החזותי של הדברים. הרכיב החסר שנותר הוא אודיו. בשיעור ה-Codelab הזה תלמדו איך להשתמש בפלאגין flutter_soloud כדי להוסיף לפרויקט שלכם אודיו ומוזיקה עם זמן אחזור נמוך. אתם מתחילים עם שלד בסיסי כדי שתוכלו לדלג ישירות לחלקים המעניינים.

איור ידני של אוזניות.

כמובן, תוכלו להשתמש במה שלמדתם כאן כדי להוסיף אודיו לאפליקציות שלכם, ולא רק למשחקים. אבל בעוד שברוב המשחקים נדרשים אודיו ומוזיקה, ברוב האפליקציות לא נדרשים אודיו ומוזיקה, ולכן סדנת הקוד הזו מתמקדת במשחקים.

דרישות מוקדמות

  • היכרות בסיסית עם Flutter.
  • ידע בהפעלה ובניפוי באגים של אפליקציות Flutter.

מה תלמדו

  • איך משמיעים צלילים חד-פעמיים.
  • איך מפעילים לולאות מוזיקה ללא הפסקות ומתאימים אותן אישית.
  • איך להעביר צלילים בהדרגה.
  • איך להוסיף לאפקטים קוליים השפעות סביבתיות.
  • איך מטפלים בחריגים.
  • איך להכיל את כל התכונות האלה בתוך בקר אודיו אחד.

מה צריך

  • Flutter SDK
  • עורך קוד לבחירתכם

2. הגדרה

  1. מורידים את הקבצים הבאים. אם החיבור שלכם איטי, אל דאגה. תצטרכו את הקבצים בפועל בהמשך, לכן אפשר להוריד אותם תוך כדי העבודה.
  1. יוצרים פרויקט Flutter בשם לבחירתכם.
  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),
      ),
      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 ברמה הבסיסית (root) של הפרויקט.
  2. בספרייה assets, יוצרים שתי ספריות משנה, אחת בשם music והשנייה בשם sounds.
  3. מעבירים את הקבצים שהורדתם לפרויקט כך שקובץ השיר יהיה בקובץ assets/music/looped-song.ogg וקולות הספסלים יהיו בקבצים הבאים:
  • 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.
flutter pub add flutter_soloud logging

עכשיו לקובץ pubspec.yaml צריכות להיות יחסי תלות נוספים בחבילות flutter_soloud ו-logging.

pubspec.yaml

...

dependencies:
  flutter:
    sdk: flutter

  flutter_soloud: ^3.1.10
  logging: ^1.3.0

...
  1. מפעילים את הפרויקט. עדיין לא ניתן להשתמש בתכונות האלה כי צריך להוסיף את הפונקציונליות בקטעים הבאים.

10f0f751c9c47038.png

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:async';

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 מסוג singleton. אי אפשר ליצור כמה מכונות של 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) של Dart כדי לקרוא לקוד C באופן ישיר וסינכררוני. אין כאן את ההודעות הרגילות בין קוד Dart לקוד הפלטפורמה, שאופייניות לרוב הפלאגינים של Flutter. הסיבה היחידה לכך שחלק מהשיטות הן אסינכרוניות היא שחלק מקוד הפלאגין פועל ב-isolate משלו, והתקשורת בין ה-isolates של Dart היא אסינכרונית.
  • טענת הנכוֹנוּת (assertion) ששדה _soloud לא null באמצעות _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 כדי להפעיל אותו.

הפעם, עם זאת, אתם מקבלים את הידית של הצליל שמוחזרת על ידי method‏ play() כדי לבצע פעולות על האודיו בזמן שהוא מושמע.

  • אם רוצים, אפשר להטמיע את השיטה AudioController.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!);
      }
    }
    final musicSource = await _soloud!.loadAsset(
      'assets/music/looped-song.ogg',
      mode: LoadMode.disk,
    );
  }

...

שימו לב שצריך לטעון את קובץ המוזיקה במצב דיסק (ה-enum של LoadMode.disk). המשמעות היא שהקובץ נטען רק בחלקים לפי הצורך. בדרך כלל, עדיף לטעון אודיו ארוך יותר במצב דיסק. לאפקטים קוליים קצרים, עדיף לטעון אותם ולחלץ אותם לזיכרון (ברירת המחדל היא enum‏ 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),
    );
  }

...

עמעום הצליל

הבעיה הבאה היא שהמוזיקה אף פעם לא נגמרת. הגיע הזמן להטמיע מעברים.

אחת מהדרכים להטמיע את ההעלמה היא ליצור פונקציה כלשהי שמופיעה כמה פעמים בשנייה, כמו Ticker או Timer.periodic, ולהקטין את עוצמת המוזיקה בהפחתות קטנות. אפשר לעשות את זה, אבל זה כרוך בהרבה עבודה.

למרבה המזל, ב-SoLoud יש שיטות נוחות מסוג 'ירו וזהו' שיעזרו לכם לעשות זאת. כך אפשר להפחית את עוצמת המוזיקה במשך חמש שניות ואז להפסיק את מופע האודיו כדי שלא ייצר עומס מיותר על המעבד. מחליפים את השיטה 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. החלת אפקטים

אחד היתרונות הגדולים של שימוש במנוע אודיו מתאים הוא שאפשר לבצע עיבוד אודיו, למשל להעביר צלילים מסוימים דרך הדהוד, אקולייזר או מסנן תדר נמוך.

במשחקים, אפשר להשתמש בזה כדי להבדיל בין מיקומים שונים באמצעות אודיו. לדוגמה, מחיאות כפיים נשמעות אחרת ביער מאשר בבונקר מבטון. בעוד שהיערות עוזרים לפזר ולספוג את הצליל, הקירות החשופים של המרתף משקפים את גלי הקול בחזרה, וכתוצאה מכך נוצר הדהוד. באופן דומה, קולות של אנשים נשמעים שונים כששומעים אותם דרך קיר. התדרים הגבוהים יותר של הצלילים האלה נחלשים יותר כשהם עוברים דרך המדיום המוצק, וכתוצאה מכך נוצר אפקט של מסנן מסוג 'מסנן מסנן נמוך'.

איור של שני אנשים מדברים בחדר. גלי הקול עוברים לא רק ישירות מאדם אחד לשני, אלא גם מקיימים רפלקס מהקירות ומהתקרה.

אפליקציית SoLoud מספקת כמה אפקטים קוליים שונים שאפשר להחיל על אודיו.

  • כדי ליצור תחושה שהנגן נמצא בחדר גדול, כמו קתדרלה או מערה, משתמשים בשדה SoLoud.filters:

lib/audio/audio_controller.dart

...

  void applyFilter() {
    _soloud!.filters.freeverbFilter.activate();
    _soloud!.filters.freeverbFilter.wet.value = 0.2;
    _soloud!.filters.freeverbFilter.roomSize.value = 0.9;
  }

  void removeFilter() {
    _soloud!.filters.freeverbFilter.deactivate();
  }

...

השדה SoLoud.filters מעניק גישה לכל סוגי המסננים ולפרמטרים שלהם. לכל פרמטר יש גם פונקציות מובנות כמו דהייה הדרגתית ותנודות.

הערה: ה-API _soloud!.filters חושף מסננים גלובליים. אם רוצים להחיל מסננים על מקור יחיד, משתמשים באפשרות המקבילה AudioSource.filters, שמבצעת את אותה פעולה.

עם הקוד הקודם, מבצעים את הפעולות הבאות:

  • מפעילים את המסנן freeverb ברמת המערכת.
  • מגדירים את הפרמטר Wet (רטוב) לערך 0.2. המשמעות היא שהאודיו שייווצר יהיה 80% מקורי ו-20% פלט של אפקט ההדהוד. אם תגדירו את הפרמטר הזה לערך 1.0, זה יהיה כמו לשמוע רק את גלי הקול שחוזרים אליכם מהקירות הרחוקים של החדר, בלי האודיו המקורי.
  • מגדירים את הפרמטר Room Size (גודל החדר) לערך 0.9. אפשר לשנות את הפרמטר הזה לפי הצורך, או אפילו לשנות אותו באופן דינמי. 1.0 הוא מערה ענקית, ו-0.0 הוא חדר אמבטיה.
  • אם אתם רוצים, תוכלו לשנות את הקוד ולהחיל אחד מהמסננים הבאים או שילוב של המסננים הבאים:
  • biquadFilter (אפשר להשתמש בו כמסנן מסוג 'מעבר נמוך')
  • pitchShiftFilter
  • equalizerFilter
  • echoFilter
  • lofiFilter
  • flangerFilter
  • bassboostFilter
  • waveShaperFilter
  • robotizeFilter

7. מזל טוב

הטמעתם בקר אודיו שמפעיל צלילים, מפעיל מוזיקה בלופ ומחיל אפקטים.

מידע נוסף

  • אתם יכולים להשתמש בפקדי האודיו בצורה מתקדמת יותר באמצעות תכונות כמו טעינת אודיו מראש בזמן ההפעלה, השמעת שירים בסדר מסוים או החלת מסנן בהדרגה לאורך זמן.
  • קוראים את מסמכי התיעוד של החבילה של flutter_soloud.
  • כדאי לעיין בדף הבית של ספריית ה-C++ הבסיסית.
  • מידע נוסף על Dart FFI, הטכנולוגיה שמשמשת ליצירת ממשק עם ספריית C++.
  • מומלץ לצפות בהרצאה של גי סומברג על תכנות אודיו במשחקים כדי לקבל השראה. (יש גם גרסה ארוכה יותר). כשגיא מדבר על 'תוכנה לעיבוד נתונים', הוא מתכוון לספריות כמו SoLoud ו-FMOD. שאר הקוד הוא בדרך כלל ספציפי לכל משחק.
  • פיתוח המשחק והשקתו.

איור של אוזניות