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

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

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

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

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

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

  • היכרות בסיסית עם 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++ מאפשר זאת, ולכן גם הפלאגין Drt אינו מתיר זאת.
  • האתחול של הפלאגין הוא אסינכרוני ולא יסתיים עד שתחזרו ל-method של init().
  • לשם הקיצור בדוגמה הזו, לא מתגלות שגיאות בבלוק try/catch. בקוד הייצור, מומלץ לעשות זאת ולדווח למשתמש על שגיאות.
  1. בשיטה dispose(), מזינים את הקוד הבא:

lib/audio/audio_controller.dart

...

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

...

מומלץ להשבית את SoLoud ביציאה מהאפליקציה, למרות שהכול אמור לפעול בצורה תקינה גם אם לא עושים זאת.

  1. שימו לב שה-method 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 כדי להפעיל אותו.

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

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

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

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

SoLoud מספק כמה אפקטים קוליים שונים, שאותם ניתן להפעיל על האודיו.

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

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

  • מפעילים את המסנן לשימוש ב-חופשיות באופן גלובלי, או לכל מיקס האודיו, לא רק לצליל אחד.
  • מגדירים את הפרמטר Wet לערך 0.2. כלומר, האודיו שיושמע יהיה 80% מקורי ו-20% מהפלט של אפקט ההד. אם מגדירים את הפרמטר הזה ל-1.0, זה נשמע כמו לשמוע רק את גלי הקול שחוזרים אליכם מהקירות הרחוקים של החדר בלי את האודיו המקורי.
  • מגדירים את הפרמטר גודל החדר לערך 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++.
  • כדאי לצפות בשיחה של Guy Somberg על תכנות האודיו של משחקים כדי לקבל השראה. (יש גם ארוך יותר). כשגיא מדבר על "תווכה", הוא מתייחס לספריות כמו SoLoud ו-FMOD. שאר הקוד נוטה להיות ספציפי לכל משחק.
  • בונים את המשחק ומפרסמים אותו.

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