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

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

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

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

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

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

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

מה לומדים

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

מה צריך

  • ה-SDK של Flutter
  • עורך קוד לבחירתך

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
  }
}

כפי שאפשר לראות, מדובר רק בשלד לפונקציונליות עתידית. אנחנו נטמיע את הכול במהלך ה-Codelab הזה.

  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. אחרי שמורידים את קובצי האודיו, צריך ליצור ספרייה ברמה הבסיסית (root) של הפרויקט בשם assets.
  2. בספרייה assets יוצרים שתי ספריות משנה, אחת בשם music והשנייה בשם sounds.
  3. מעבירים את הקבצים שהורדתם לפרויקט כך שקובץ השיר יהיה בקובץ assets/music/looped-song.ogg והצלילים של הספסלים יהיו בקבצים הבאים:
  • assets/sounds/pew1.mp3
  • assets/sounds/pew2.mp3
  • assets/sounds/pew3.mp3

עכשיו מבנה הפרויקט אמור להיראות כך:

תצוגת עץ של הפרויקט, עם תיקיות כמו &#39;android&#39;, &#39;ios&#39;, קבצים כמו &#39;README.md&#39; ו-&#39;analysis_options.yaml&#39;. בין אלה אפשר לראות את הספרייה &#39;assets&#39; עם ספריות המשנה &#39;music&#39; ו-&#39;sounds&#39;, את הספרייה &#39;lib&#39; עם &#39;main.dart&#39; ואת ספריית המשנה &#39;audio&#39; עם &#39;audio_controller.dart&#39; ואת הקובץ &#39;pubspec.yaml&#39;.  החצים מצביעים על הספריות החדשות ועל הקבצים שבהם נגעת עד עכשיו.

עכשיו, כשהקבצים כבר שם, צריך ליידע אותם על כך ב-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];

הם מגיעים מספריית ה-C++‏ SoLoud שמתחת. אין להם כל השפעה על הפונקציונליות, ואפשר להתעלם מהם בבטחה.

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 ב-Singleton. אי אפשר ליצור כמה מכונות של SoLoud. המנוע של C++ לא מאפשר זאת, ולכן גם הפלאגין של Dart לא מאפשר זאת.
  • האתחול של הפלאגין הוא אסינכרוני ולא יסתיים עד שתחזרו ל-method של 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 שנרכשה מועברת ל-method play() של SoLoud. השיטה הזו מחזירה מופע מסוג SoundHandle שמייצג את הצליל החדש שמושמע. לאחר מכן, ניתן להעביר את הכינוי הזה לשיטות אחרות של SoLoud כדי לבצע פעולות כמו להשהות, להפסיק או לשנות את עוצמת הקול של הצליל.
  • למרות ש-play() היא שיטה אסינכרונית, ההפעלה מתחילה באופן כמעט מיידי. חבילת flutter_soloud משתמשת בממשק פונקציות זרות (FFI) של Dat כדי לקרוא לקוד C באופן ישיר וסינכרוני. ההודעות הרגילות הלוך ושוב בין קוד Dat לבין קוד פלטפורמה שאופייניות לרוב יישומי הפלאגין של Flutter לא ניתן למצוא בשום מקום. הסיבה היחידה לכך שכמה שיטות הן אסינכרוניות היא שחלק מקוד הפלאגין פועל בבידוד עצמאי, והתקשורת בין בידוד של Drt היא אסינכרונית.
  • את/ה מצהיר/ה שהשדה _soloud אינו אפס לגבי _soloud!. גם כאן, הסיבה לכך היא קצרנות. קוד הייצור צריך לטפל בצורה יעילה במצב שבו המפתח מנסה להשמיע צליל לפני שלב האתחול המלא של בקר האודיו.

טיפול בחריגים

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

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

הפעם, עם זאת, אתם מקבלים את ה-handle של הצליל שמוחזרת על ידי 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);
    _musicHandle = await _soloud!.play(musicSource);
  }

...

שימו לב שאתם טוענים את קובץ המוזיקה במצב דיסק (הטיפוס enum LoadMode.disk). המשמעות היא שהקובץ נטען רק בחלקים לפי הצורך. לרוב, מומלץ לטעון אודיו במצב דיסק לאורך זמן רב יותר. אם מדובר באפקטים קוליים קצרים, הגיוני יותר לטעון אותם ולבטל את הדחיסה שלהם לזיכרון (ברירת המחדל LoadMode.memory enum).

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

הערה: _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. שאר הקוד נוטה להיות ספציפי לכל משחק.
  • פיתוח המשחק והשקתו.

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