1. Zanim zaczniesz
Gry to doświadczenia audiowizualne. Flutter to świetne narzędzie do tworzenia atrakcyjnych wizualizacji i przejrzystego interfejsu, które znacznie rozszerzają walory wizualne. Pozostały brakujący składnik to dźwięk. Z tego ćwiczenia w Codelabs dowiesz się, jak za pomocą wtyczki flutter_soloud
wprowadzić do projektu dźwięk i muzykę o małym opóźnieniu. Zaczynasz od podstawowego szablonu, dzięki czemu możesz od razu przejść do interesujących Cię części.
Korzystając z uzyskanych tu informacji, możesz oczywiście dodać dźwięk do aplikacji, a nie tylko gier. Jednak prawie wszystkie gry wymagają dźwięku i muzyki, a większość aplikacji nie. Dlatego ten samouczek koncentruje się na grach.
Wymagania wstępne
- Podstawowa znajomość technologii Flutter.
- umiejętność uruchamiania i debugowania aplikacji Flutter;
Czego się nauczysz
- Jak odtwarzać dźwięki jednego uderzenia.
- Jak odtwarzać i dostosowywać pętle muzyczne bez przerw.
- Jak włączać i wyłączać dźwięk.
- Jak zastosować efekty środowiskowe do dźwięków.
- Jak radzić sobie z wyjątkami.
- jak połączyć wszystkie te funkcje w jednym kontrolerze audio.
Wymagania
- Pakiet SDK Flutter
- Wybrany przez Ciebie edytor kodu.
2. Skonfiguruj
- Pobierz te pliki. Jeśli masz wolne połączenie, nie martw się. Potrzebujesz tych plików później, więc możesz je pobrać podczas pracy.
- Utwórz projekt Flutter o dowolnej nazwie.
- Utwórz w projekcie plik
lib/audio/audio_controller.dart
. - W pliku wpisz ten kod:
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
}
}
Jak widzisz, to tylko szkielet przyszłej funkcji. W ramach tego Codelab wprowadzimy to wszystko w kodzie.
- Następnie otwórz plik
lib/main.dart
i zastąp jego zawartość tym kodem:
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();
}
},
),
],
),
],
),
),
);
}
}
- Po pobraniu plików audio utwórz w katalogu głównym projektu katalog o nazwie
assets
. - W katalogu
assets
utwórz 2 podkatalogi: jeden o nazwiemusic
, a drugi o nazwiesounds
. - Przenieś pobrane pliki do projektu, tak aby plik utworu znajdował się w pliku
assets/music/looped-song.ogg
, a dźwięki ławki w tych plikach:
assets/sounds/pew1.mp3
assets/sounds/pew2.mp3
assets/sounds/pew3.mp3
Struktura Twojego projektu powinna teraz wyglądać mniej więcej tak:
Teraz, gdy pliki są już w tym miejscu, musisz poinformować o nich Fluttera.
- Otwórz plik
pubspec.yaml
, a potem zastąp sekcjęflutter:
na dole pliku tym tekstem:
pubspec.yaml
...
flutter:
uses-material-design: true
assets:
- assets/music/
- assets/sounds/
- Dodaj zależność od pakietu
flutter_soloud
i pakietulogging
.
pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
flutter_soloud: ^2.0.0
logging: ^1.2.0
...
- Uruchom projekt. Nic jeszcze nie działa, ponieważ te funkcje zostały dodane w sekcjach poniżej.
/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];
Pochodzą one z biblioteki C++ SoLoud
. Nie mają one żadnego wpływu na działanie aplikacji i można je zignorować.
3. Inicjowanie i wyłączanie
Aby odtworzyć dźwięk, musisz użyć wtyczki flutter_soloud
. Ta wtyczka jest oparta na projekcie SoLoud, czyli silniku dźwięku w języku C++ używanym między innymi w grach firmy Nintendo SNES Classic.
Aby zainicjować silnik audio SoLoud:
- W pliku
audio_controller.dart
zaimportuj pakietflutter_soloud
i dodaj do klasy prywatne pole_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
}
...
Kontroler audio zarządza podstawowym mechanizmem SoLoud za pomocą tego pola i przekierowuje do niego wszystkie wywołania.
- W metodzie
initialize()
wpisz ten kod:
lib/audio/audio_controller.dart
...
Future<void> initialize() async {
_soloud = SoLoud.instance;
await _soloud!.init();
}
...
Zapełnia pole _soloud
i oczekuje na zainicjowanie. Pamiętaj:
- SoLoud udostępnia pojedyncze pole
instance
. Nie można utworzyć kilku instancji SoLoud. Silnik C++ nie zezwala na to, więc wtyczka Dart też nie może tego zrobić. - Inicjowanie wtyczki jest asynchroniczne i nie kończy się, dopóki nie zostanie zwrócona metoda
init()
. - W tym przykładzie w celu zachowania zwięzłości nie przechwytujesz błędów w bloku
try/catch
. W kodzie produkcyjnym chcesz to zrobić i zgłosić użytkownikowi wszelkie błędy.
- W metodzie
dispose()
wpisz ten kod:
lib/audio/audio_controller.dart
...
void dispose() {
_soloud?.deinit();
}
...
Wyłączanie funkcji SoLoud przy wychodzeniu z aplikacji to dobra praktyka, chociaż wszystko powinno działać jak należy, nawet jeśli tego nie zrobisz.
- Zwróć uwagę, że metoda
AudioController.initialize()
jest już wywoływana z funkcjimain()
. Oznacza to, że ponowne uruchomienie projektu z pamięcią inicjuje funkcję SoLoud w tle, ale nie przyniesie to pożytku, dopóki nie odtworzy się niektórych dźwięków.
4. Odtwórz dźwięki jednego uderzenia
Ładowanie i odtwarzanie komponentu
Teraz, gdy już wiesz, że SoLoud jest inicjowany podczas uruchamiania, możesz poprosić o odtworzenie dźwięku.
SoLoud odróżnia źródło dźwięku, którym jest dane i metadane używane do opisania dźwięku, od jego „wystąpień dźwięku”, czyli rzeczywiście odtwarzanych dźwięków. Przykładem źródła dźwięku może być plik mp3 wczytany do pamięci, gotowy do odtwarzania i reprezentowany przez instancję klasy AudioSource
. Za każdym razem, gdy odtwarzasz to źródło dźwięku, SoLoud tworzy „występ dźwięku”, który jest reprezentowany przez typ SoundHandle
.
Po jej wczytaniu otrzymasz instancję AudioSource
. Jeśli na przykład masz w zasobach plik MP3, możesz go załadować, aby otrzymać AudioSource
. Następnie mówisz SoLoud, aby odtworzyć AudioSource
. Możesz w nie grać wiele razy, a nawet jednocześnie.
Gdy nie używasz już danego źródła dźwięku, możesz je usunąć za pomocą metody SoLoud.disposeSource()
.
Aby załadować zasób i odtworzyć go:
- W metodzie
playSound()
klasyAudioController
wpisz ten kod:
lib/audio/audio_controller.dart
...
Future<void> playSound(String assetKey) async {
final source = await _soloud!.loadAsset(assetKey);
await _soloud!.play(source);
}
...
- Zapisz plik, wykonaj szybkie przeładowanie i wybierz Odtwórz dźwięk. Usłyszysz głupią minę. Pamiętaj:
- Podany argument
assetKey
ma postaćassets/sounds/pew1.mp3
– ten sam ciąg znaków, który trzeba podać w przypadku dowolnego innego interfejsu Flutter API, który wczytuje zasoby, np. widżetuImage.asset()
. - Instancja SoLoud udostępnia metodę
loadAsset()
, która asynchronicznie wczytuje plik audio z zasobów projektu Flutter i zwraca instancję klasyAudioSource
. Istnieją równoważne metody wczytywania pliku z systemu plików (metodaloadFile()
) oraz wczytywania przez sieć z adresu URL (metodaloadUrl()
). - Nowo pozyskana instancja
AudioSource
jest następnie przekazywana do metodyplay()
SoLoud. Ta metoda zwraca instancję typuSoundHandle
, która reprezentuje odtwarzany obecnie dźwięk. Ten obiekt można następnie przekazać do innych metod SoLoud, aby wykonać takie czynności jak wstrzymanie, zatrzymanie lub zmiana głośności dźwięku. - Chociaż
play()
jest metodą asynchroniczną, odtwarzanie zaczyna się zasadniczo od razu. Pakietflutter_soloud
wykorzystuje interfejs funkcji obcych (FFI) Dart do bezpośredniego i synchronicznego wywoływania kodu C. Normalny komunikat pomiędzy kodem Dart a kodem platformy, charakterystyczny dla większości wtyczek Flutter, nie jest nigdzie dostępny. Jedynym powodem, dla którego niektóre metody są asynchroniczne, jest to, że część kodu wtyczki jest uruchamiana samodzielnie, a komunikacja między izolowanymi elementami Dart jest asynchroniczna. - Wystarczy, że pole
_soloud
nie jest puste w przypadku_soloud!
. Powtarzam to dla zwięzłości. Kod produkcyjny powinien bezproblemowo obsługiwać sytuację, w której deweloper próbuje odtworzyć dźwięk, zanim kontroler audio zdąży się w pełni zainicjować.
Wyjątki
Być może zauważyliście, że ponownie ignorujecie potencjalne wyjątki. Poprawmy to w przypadku tej konkretnej metody w celach edukacyjnych. (dla zwięzłości ćwiczenia w Codelabs po tej sekcji wracają do ignorowania wyjątków).
- Aby rozwiązać w tym przypadku wyjątki, spakuj 2 wiersze metody
playSound()
w bloktry/catch
i przechwytuj tylko wystąpieniaSoLoudException
.
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 zgłasza różne wyjątki, takie jak SoLoudNotInitializedException
lub SoLoudTemporaryFolderFailedException
. Dokumentacja interfejsu API każdej metody zawiera listę wyjątków, które mogą wystąpić.
SoLoud udostępnia też klasę nadrzędną dla wszystkich wyjątków, czyli wyjątek SoLoudException
, dzięki czemu możesz przechwytywać wszystkie błędy związane z funkcjonalnością silnika audio. Jest to szczególnie przydatne w przypadkach, gdy odtwarzanie dźwięku nie ma kluczowego znaczenia. Jest tak na przykład wtedy, gdy nie chcesz przerywać sesji gry gracza tylko dlatego, że nie udało się wczytać jednego z dźwięków.
Jak można się spodziewać, metoda loadAsset()
może też zwrócić błąd FlutterError
, jeśli podasz klucz zasobu, który nie istnieje. Próba załadowania zasobów, które nie są zawarte w grze, jest zwykle czymś, co należy naprawić. Dlatego jest to błąd.
Odtwarzanie różnych dźwięków
Zauważysz, że odtwarzasz tylko plik pew1.mp3
, ale w katalogu zasobów są 2 inne wersje dźwięku. Często brzmi to bardziej naturalnie, gdy gry mają kilka wersji tego samego dźwięku i grają różne wersje w losowy lub naprzemienny sposób. Zapobiega to na przykład temu, że odgłosy kroków lub strzałów wydają się zbyt jednolite, a tym samym fałszywe.
- Opcjonalnie zmodyfikuj kod tak, aby przy każdym dotknięciu przycisku odtwarzał inny dźwięk.
5. Odtwarzanie pętli muzycznych
Zarządzaj dźwiękami, które trwają dłużej
Niektóre dźwięki są przeznaczone do odtwarzania przez dłuższy czas. Oczywistym przykładem jest muzyka, ale w wielu grach panuje klimat, taki jak szum wiatru biegnący przez korytarze, śpiew mnichów w oddali, odgłosy wielowiekowego metalu czy kaszle w oddali pacjentów.
Są to źródła dźwięku, których czas odtwarzania może być mierzony w minutach. Musisz je śledzić, aby w razie potrzeby móc je wstrzymać lub zatrzymać. Często mają też duże pliki i mogą zajmować dużo pamięci, dlatego warto je śledzić, aby pozbyć się instancji AudioSource
, gdy nie jest już potrzebna.
W związku z tym wprowadzisz nowe pole prywatne do AudioController
. Jest to identyfikator bieżąco odtwarzanego utworu (jeśli taki istnieje). Dodaj ten wiersz:
lib/audio/audio_controller.dart
...
class AudioController {
static final Logger _log = Logger('AudioController');
SoLoud? _soloud;
SoundHandle? _musicHandle; // ← Add this.
...
Rozpocznij odtwarzanie muzyki
W zasadzie odtwarzanie muzyki nie różni się od odtwarzania dźwięku jednorazowego. Najpierw musisz załadować plik assets/music/looped-song.ogg
jako instancję klasy AudioSource
, a potem odtworzyć go za pomocą metody play()
w SoLoud.
Tym razem jednak przechwytujesz uchwyt dźwięku zwracany przez metodę play()
, aby manipulować dźwiękiem podczas jego odtwarzania.
- Jeśli chcesz, samodzielnie zaimplementuj metodę
AudioController.startMusic()
. Nie musisz podawać wszystkich szczegółów. Ważne jest to, że muzyka zaczyna się od razu po wybraniu opcji Rozpocznij odtwarzanie muzyki.
Oto przykładowa implementacja:
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);
}
...
Pamiętaj, że plik muzyczny jest wczytywany w trybie dyskowym (enumeracja LoadMode.disk
). Oznacza to po prostu, że plik jest wczytywany tylko w porcjach, w miarę potrzeby. Jeśli dźwięk działa dłużej, lepiej ładować go w trybie dysku. W przypadku krótkich efektów dźwiękowych lepiej jest je wczytać i dekompresować do pamięci (domyślna wartość enum LoadMode.memory
).
Masz jednak kilka problemów. Po pierwsze, muzyka jest zbyt głośna, co przytłacza dźwięki. W większości gier muzyka jest zwykle odtwarzana w tle, aby stworzyć przestrzeń dla bardziej informacyjnych elementów dźwiękowych, takich jak mowa i efekty dźwiękowe. Można to łatwo naprawić, używając parametru głośności metody odtwarzania. Możesz na przykład użyć polecenia _soloud!.play(musicSource, volume: 0.6)
, by odtworzyć utwór z głośnością 60%. Możesz też ustawić głośność w dowolnym momencie później, na przykład za pomocą polecenia _soloud!.setVolume(_musicHandle, 0.6)
.
Drugi problem polega na tym, że utwór nagle się zatrzymuje. Dzieje się tak, ponieważ jest to utwór, który ma być odtwarzany w pętli, a punkt początkowy pętli nie znajduje się na początku pliku audio.
Jest to popularny wybór w przypadku muzyki z gier, ponieważ oznacza, że utwór zaczyna się od naturalnego wprowadzenia, a potem odtwarza się tak długo, jak to konieczne, bez wyraźnej pętli pętli. Gdy gra musi przejść do następnego utworu, po prostu ściemnia ten, który jest aktualnie odtwarzany.
Na szczęście SoLoud zapewnia sposoby odtwarzania dźwięku w pętli. Metoda play()
przyjmuje wartość logiczną parametru looping
oraz wartość punktu początkowego pętli jako parametr loopingStartAt
. Wynikowy kod wygląda tak:
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),
);
...
Jeśli nie ustawisz parametru loopingStartAt
, zostanie on domyślnie ustawiony na Duration.zero
(czyli na początek pliku audio). Jeśli masz utwór muzyczny, który idealnie nadaje się do pętli, bez wprowadzenia, to właśnie jest dla Ciebie.
- Aby mieć pewność, że po zakończeniu odtwarzania źródło dźwięku zostało prawidłowo usunięte, posłuchaj strumienia
allInstancesFinished
z każdego z nich. Po dodaniu wywołań dziennika metodastartMusic()
wygląda tak:
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),
);
}
...
Zanikanie dźwięku
Kolejny problem polega na tym, że muzyka nigdy się nie kończy. Wprowadzimy efekt znikania.
Jednym ze sposobów wdrożenia zanikania jest użycie jakiejś funkcji, która jest wywoływana kilka razy na sekundę, np. Ticker
lub Timer.periodic
, i zmniejszanie głośności muzyki o niewielkie wartości. To zadziała, ale jest pracochłonne.
Na szczęście SoLoud udostępnia wygodne metody, które pozwalają to zrobić. Oto jak możesz płynnie zmniejszyć głośność muzyki w ciągu 5 sekund, a potem zatrzymać odtwarzanie, aby nie zużywało niepotrzebnie zasobów procesora. Zastąp metodę fadeOutMusic()
tym kodem:
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. Zastosuj efekty
Jedną z wielkich zalet posiadania odpowiedniego silnika audio jest możliwość przetwarzania dźwięku, np. kierowania niektórych dźwięków przez pogłos, korektor lub filtr dolnoprzepustowy.
W grach może służyć do różnicowania dźwięków w zależności od lokalizacji. Na przykład oklaski brzmią inaczej w lesie niż w betonowym bunkrze. Las pomaga rozpraszać i absorbować dźwięk, a gołe ściany bunkra odbijają fale dźwiękowe, co powoduje pogłos. Podobnie głosy ludzi brzmią inaczej, gdy słyszy się je przez ścianę. Wyższe częstotliwości tych dźwięków są łatwiej stłumione, gdy przechodzą przez światło stałe, co skutkuje efektem filtra dolnoprzepustowego.
SoLoud udostępnia kilka różnych efektów dźwiękowych, które możesz zastosować do dźwięku.
- Aby uzyskać efekt dużej sali, na przykład katedry lub jaskini, użyj pola
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();
}
...
Pole SoLoud.filters
zapewnia dostęp do wszystkich typów filtrów i ich parametrów. Każdy parametr ma też wbudowane funkcje, takie jak stopniowe zanikanie i oscylacja.
Uwaga: _soloud!.filters
udostępnia filtry globalne. Jeśli chcesz zastosować filtry do jednego źródła, użyj odpowiednika AudioSource.filters
, który działa tak samo.
Za pomocą poprzedniego kodu:
- Włącz globalnie filtr Freeverb.
- Ustaw parametr Wet na
0.2
, co oznacza, że wygenerowany dźwięk będzie w 80% oryginalny, a 20% generacji z efektu pogłosu. Ustawienie tego parametru na1.0
jest jakby słychać tylko fale dźwiękowe, które do Ciebie dochodzi z odległych ścian pomieszczenia, bez żadnego oryginalnego dźwięku. - Ustaw wartość parametru Rozmiar sali na
0.9
. Możesz dostosować ten parametr do swoich potrzeb lub nawet zmieniać go dynamicznie.1.0
to ogromna jaskinia, a0.0
to łazienka.
- W razie potrzeby zmień kod i zastosuj jeden z tych filtrów lub kombinację tych filtrów:
biquadFilter
(może być używany jako filtr dolnoprzepustowy)pitchShiftFilter
equalizerFilter
echoFilter
lofiFilter
flangerFilter
bassboostFilter
waveShaperFilter
robotizeFilter
7. Gratulacje
Wdrożyłeś/wdróżyłaś kontroler dźwięku, który odtwarza dźwięki, odtwarza muzykę w pętli i zachowuje efekty.
Więcej informacji
- Możesz też skorzystać z zaawansowanych funkcji sterownika dźwięku, takich jak wstępne wczytywanie dźwięków podczas uruchamiania, odtwarzanie utworów w kolejności lub stopniowe stosowanie filtra z upływem czasu.
- Przeczytaj dokumentację pakietu
flutter_soloud
. - Przeczytaj stronę główną biblioteki C++.
- Dowiedz się więcej o Dart FFI, technologii używanej do łączenia się z biblioteką C++.
- Aby znaleźć inspirację, obejrzyj film Guya Somberga o programowaniu audio z gier. (dostępny jest też dłuższy film). Gdy Guy mówi o oprogramowaniu pośredniczącym, ma na myśli takie biblioteki jak SoLoud i FMOD. Pozostała część kodu jest zwykle specyficzna dla każdej gry.
- Utwórz grę i opublikuj ją.