1. Trước khi bắt đầu
Trò chơi là trải nghiệm nghe nhìn. Flutter là một công cụ tuyệt vời để tạo dựng hình ảnh đẹp mắt và giao diện người dùng ổn định, vì vậy, nó giúp bạn cải thiện khía cạnh trực quan của mọi thứ. Thành phần còn thiếu còn lại là âm thanh. Trong lớp học lập trình này, bạn sẽ tìm hiểu cách sử dụng trình bổ trợ flutter_soloud
để đưa âm thanh và nhạc có độ trễ thấp vào dự án của mình. Bạn bắt đầu với một scaffold (giàn giáo) cơ bản để có thể chuyển thẳng đến các phần thú vị.
Tất nhiên, bạn có thể sử dụng những kiến thức học được tại đây để thêm âm thanh vào ứng dụng, chứ không chỉ trò chơi. Tuy hầu hết các trò chơi đều cần có âm thanh và nhạc, nhưng hầu hết các ứng dụng thì không. Vì vậy, lớp học lập trình này sẽ tập trung vào trò chơi.
Điều kiện tiên quyết
- Hiểu biết cơ bản về Flutter.
- Có kiến thức về cách chạy và gỡ lỗi ứng dụng Flutter.
Kiến thức bạn sẽ học được
- Cách phát âm thanh một lần.
- Cách phát và tuỳ chỉnh các vòng lặp nhạc không gián đoạn.
- Cách nhỏ dần âm thanh vào và ra.
- Cách áp dụng hiệu ứng môi trường cho âm thanh.
- Cách xử lý các trường hợp ngoại lệ.
- Cách đóng gói tất cả các tính năng này vào một bộ điều khiển âm thanh.
Bạn cần có
- SDK Flutter
- Trình soạn thảo mã do bạn chọn
2. Thiết lập
- Tải các tệp sau xuống. Nếu bạn có kết nối chậm, đừng lo lắng. Bạn sẽ cần các tệp thực tế sau này, vì vậy, bạn có thể tải các tệp đó xuống trong khi làm việc.
- Tạo một dự án Flutter bằng tên do bạn chọn.
- Tạo một tệp
lib/audio/audio_controller.dart
trong dự án. - Trong tệp đó, hãy nhập mã sau:
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
}
}
Như bạn có thể thấy, đây chỉ là bộ khung cho chức năng trong tương lai. Chúng ta sẽ triển khai tất cả trong lớp học lập trình này.
- Tiếp theo, hãy mở tệp
lib/main.dart
rồi thay thế nội dung của tệp bằng đoạn mã sau:
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();
}
},
),
],
),
],
),
),
);
}
}
- Sau khi tải các tệp âm thanh xuống, hãy tạo một thư mục trong gốc của dự án có tên là
assets
. - Trong thư mục
assets
, hãy tạo 2 thư mục con, một thư mục con tên làmusic
và thư mục còn lại tên làsounds
. - Di chuyển các tệp đã tải xuống vào dự án của bạn để tệp bài hát nằm trong tệp
assets/music/looped-song.ogg
và âm thanh của hàng ghế ngồi nằm trong các tệp sau:
assets/sounds/pew1.mp3
assets/sounds/pew2.mp3
assets/sounds/pew3.mp3
Lúc này, cấu trúc dự án của bạn sẽ có dạng như sau:
Giờ đây, khi các tệp đã có sẵn, bạn cần cho Flutter biết về các tệp đó.
- Mở tệp
pubspec.yaml
rồi thay thế phầnflutter:
ở cuối tệp bằng đoạn mã sau:
pubspec.yaml
...
flutter:
uses-material-design: true
assets:
- assets/music/
- assets/sounds/
- Thêm phần phụ thuộc vào gói
flutter_soloud
và góilogging
.
pubspec.yaml
...
dependencies:
flutter:
sdk: flutter
flutter_soloud: ^2.0.0
logging: ^1.2.0
...
- Chạy dự án. Chưa có ứng dụng nào hoạt động vì bạn đã thêm chức năng này vào các phần sau.
/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ác hàm này đến từ thư viện C++ SoLoud
cơ bản. Các lỗi này không ảnh hưởng đến chức năng và bạn có thể bỏ qua.
3. Khởi động và tắt
Để phát âm thanh, bạn cần sử dụng trình bổ trợ flutter_soloud
. Trình bổ trợ này dựa trên dự án SoLoud, một công cụ âm thanh C++ cho các trò chơi được Nintendo SNES Classic sử dụng cùng với các trò chơi khác.
Để khởi chạy công cụ âm thanh SoLoud, hãy làm theo các bước sau:
- Trong tệp
audio_controller.dart
, hãy nhập góiflutter_soloud
và thêm một trường_soloud
riêng tư vào lớp này.
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
}
...
Trình điều khiển âm thanh quản lý công cụ SoLoud cơ bản thông qua trường này và sẽ chuyển tiếp tất cả lệnh gọi đến công cụ đó.
- Trong phương thức
initialize()
, hãy nhập mã sau:
lib/audio/audio_controller.dart
...
Future<void> initialize() async {
_soloud = SoLoud.instance;
await _soloud!.init();
}
...
Thao tác này sẽ điền sẵn trường _soloud
và chờ khởi chạy. Xin lưu ý những điều sau:
- SoLoud cung cấp trường
instance
singleton. Không có cách tạo thực thể cho nhiều thực thể SoLoud. Đây không phải là điều mà công cụ C++ cho phép, do đó, trình bổ trợ Dart cũng không cho phép điều này. - Quá trình khởi chạy trình bổ trợ không đồng bộ và kết thúc cho đến khi phương thức
init()
trả về. - Tóm lại, trong ví dụ này, bạn không phát hiện lỗi trong khối
try/catch
. Trong mã phát hành chính thức, bạn cần thực hiện việc này và báo cáo lỗi cho người dùng.
- Trong phương thức
dispose()
, hãy nhập mã sau:
lib/audio/audio_controller.dart
...
void dispose() {
_soloud?.deinit();
}
...
Bạn nên tắt SoLoud khi thoát ứng dụng, mặc dù mọi thứ vẫn hoạt động tốt ngay cả khi bạn quên làm điều này.
- Lưu ý rằng phương thức
AudioController.initialize()
đã được gọi từ hàmmain()
. Điều này có nghĩa là việc khởi động lại dự án sẽ khởi chạy SoLoud ở chế độ nền, nhưng sẽ không mang lại lợi ích gì cho bạn trước khi bạn thực sự phát một số âm thanh.
4. Phát âm thanh một lần
Tải và phát nội dung
Giờ đây, khi đã biết SoLoud được khởi động khi khởi động, bạn có thể yêu cầu SoLoud phát âm thanh.
SoLoud phân biệt giữa nguồn âm thanh (là dữ liệu và siêu dữ liệu dùng để mô tả âm thanh) và "thực thể âm thanh" (là âm thanh thực sự được phát). Ví dụ về nguồn âm thanh có thể là tệp mp3 được tải vào bộ nhớ, sẵn sàng phát và được biểu thị bằng một thực thể của lớp AudioSource
. Mỗi khi bạn phát nguồn âm thanh này, SoLoud sẽ tạo một "phiên bản âm thanh" được biểu thị bằng loại SoundHandle
.
Bạn nhận được một thực thể AudioSource
bằng cách tải thực thể đó. Ví dụ: nếu có tệp mp3 trong tài sản, bạn có thể tải tệp đó để lấy AudioSource
. Sau đó, bạn yêu cầu SoLoud phát AudioSource
này. Bạn có thể phát đồng thời nhiều lần, thậm chí cùng lúc.
Khi dùng xong một nguồn âm thanh, bạn sẽ loại bỏ nguồn âm thanh đó bằng phương thức SoLoud.disposeSource()
.
Để tải và phát một thành phần, hãy làm theo các bước sau:
- Trong phương thức
playSound()
của lớpAudioController
, hãy nhập đoạn mã sau:
lib/audio/audio_controller.dart
...
Future<void> playSound(String assetKey) async {
final source = await _soloud!.loadAsset(assetKey);
await _soloud!.play(source);
}
...
- Lưu tệp, tải lại nhanh rồi chọn Phát âm thanh. Bạn sẽ nghe thấy âm thanh ngớ ngẩn. Xin lưu ý những điều sau:
- Đối số
assetKey
được cung cấp có dạng nhưassets/sounds/pew1.mp3
– chính là chuỗi mà bạn cung cấp cho bất kỳ API Flutter tải tài sản nào khác, chẳng hạn như tiện íchImage.asset()
. - Thực thể SoLoud cung cấp một phương thức
loadAsset()
tải không đồng bộ tệp âm thanh từ các tài sản của dự án Flutter và trả về một thực thể của lớpAudioSource
. Có các phương thức tương đương để tải một tệp từ hệ thống tệp (phương thứcloadFile()
) và tải qua mạng từ một URL (phương thứcloadUrl()
). - Sau đó, thực thể
AudioSource
mới thu được sẽ được chuyển đến phương thứcplay()
của SoLoud. Phương thức này trả về một thực thể của loạiSoundHandle
đại diện cho âm thanh mới phát. Sau đó, tên người dùng này có thể được truyền đến các phương thức SoLoud khác để thực hiện những thao tác như tạm dừng, dừng hoặc chỉnh sửa âm lượng của âm thanh. - Mặc dù
play()
là một phương thức không đồng bộ, nhưng quá trình phát về cơ bản sẽ bắt đầu ngay lập tức. Góiflutter_soloud
sử dụng giao diện hàm đối ngoại (FFI) của Dart để gọi mã C trực tiếp và đồng bộ. Thông thường, không còn tìm thấy thông báo qua lại giữa mã Dart và mã nền tảng, vốn đặc trưng cho hầu hết các trình bổ trợ Flutter. Lý do duy nhất khiến một số phương thức không đồng bộ là một số mã của trình bổ trợ chạy trong vùng chứa riêng và hoạt động giao tiếp giữa các vùng chứa Dart là không đồng bộ. - Bạn chỉ cần xác nhận rằng trường
_soloud
không rỗng bằng_soloud!
. Xin nhắc lại, điều này là để ngắn gọn. Mã phát hành chính thức phải xử lý linh hoạt trường hợp nhà phát triển cố gắng phát âm thanh trước khi trình điều khiển âm thanh có cơ hội khởi chạy đầy đủ.
Xử lý các trường hợp ngoại lệ
Bạn có thể nhận thấy rằng một lần nữa, bạn đang bỏ qua các ngoại lệ có thể xảy ra. Hãy khắc phục vấn đề đó cho phương thức cụ thể này cho mục đích học tập. (Tóm lại, lớp học lập trình sẽ quay lại phương thức bỏ qua các ngoại lệ sau phần này.)
- Để xử lý các ngoại lệ trong trường hợp này, hãy gói hai dòng của phương thức
playSound()
trong một khốitry/catch
và chỉ phát hiện các thực thể củaSoLoudException
.
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 gửi nhiều ngoại lệ, chẳng hạn như ngoại lệ SoLoudNotInitializedException
hoặc SoLoudTemporaryFolderFailedException
. Tài liệu API của mỗi phương thức liệt kê các loại ngoại lệ có thể được gửi.
SoLoud cũng cung cấp một lớp mẹ cho tất cả các ngoại lệ, ngoại lệ SoLoudException
, để bạn có thể phát hiện tất cả lỗi liên quan đến chức năng của công cụ âm thanh. Điều này đặc biệt hữu ích trong trường hợp việc phát âm thanh không quan trọng. Ví dụ: khi bạn không muốn phiên chơi của người chơi gặp sự cố chỉ vì một trong những âm thanh pew-pew không tải được.
Như bạn thường thấy, phương thức loadAsset()
cũng có thể gửi ra lỗi FlutterError
nếu bạn cung cấp khoá tài sản không tồn tại. Nhìn chung, bạn nên giải quyết việc cố gắng tải các thành phần không đi kèm với trò chơi, do đó đó là lỗi.
Phát nhiều âm thanh
Bạn có thể nhận thấy chỉ phát tệp pew1.mp3
, nhưng có hai phiên bản âm thanh khác trong thư mục nội dung. Việc này thường nghe tự nhiên hơn khi trò chơi có nhiều phiên bản âm thanh giống nhau và người dùng có thể phát các phiên bản khác nhau theo cách ngẫu nhiên hoặc luân phiên. Ví dụ: điều này giúp tiếng bước chân và tiếng súng không quá đồng nhất và giả tạo.
- Đây là một bài tập không bắt buộc, hãy sửa đổi mã để phát một âm thanh khác mỗi khi người dùng nhấn vào nút này.
5. Phát vòng lặp âm nhạc
Quản lý âm thanh chạy trong thời gian dài hơn
Một số âm thanh được thiết kế để phát trong thời gian dài. Ví dụ rõ ràng là âm nhạc, nhưng nhiều trò chơi cũng chơi không khí, chẳng hạn như tiếng gió hú qua hành lang, tiếng các nhà sư từ xa, tiếng kim loại hàng trăm năm tuổi kêu lách cách hay tiếng ho từ xa của bệnh nhân.
Đây là những nguồn âm thanh có thời lượng phát có thể đo lường được theo phút. Bạn cần theo dõi các phiên phát để có thể tạm dừng hoặc dừng phát khi cần. Các tệp này cũng thường được các tệp lớn hỗ trợ và có thể tốn nhiều bộ nhớ. Vì vậy, một lý do khác để theo dõi tệp là để bạn có thể loại bỏ thực thể AudioSource
khi không cần nữa.
Do đó, bạn sẽ giới thiệu một trường riêng tư mới cho AudioController
. Đây là tên người dùng của bài hát đang phát, nếu có. Hãy thêm dòng lệnh sau đây:
lib/audio/audio_controller.dart
...
class AudioController {
static final Logger _log = Logger('AudioController');
SoLoud? _soloud;
SoundHandle? _musicHandle; // ← Add this.
...
Bắt đầu phát nhạc
Về cơ bản, việc phát nhạc không khác gì việc phát một âm thanh một lần. Trước tiên, bạn vẫn cần tải tệp assets/music/looped-song.ogg
làm thực thể của lớp AudioSource
, sau đó sử dụng phương thức play()
của SoLoud để phát tệp đó.
Tuy nhiên, lần này, bạn sẽ giữ handle âm thanh mà phương thức play()
trả về để thao tác với âm thanh trong khi âm thanh đang phát.
- Nếu muốn, hãy tự triển khai phương thức
AudioController.startMusic()
. Bạn không cần phải lo lắng nếu có một số chi tiết chưa chính xác. Điều quan trọng là nhạc sẽ bắt đầu khi bạn chọn Bắt đầu phát nhạc.
Dưới đây là cách triển khai tham chiếu:
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);
}
...
Lưu ý rằng bạn tải tệp nhạc ở chế độ đĩa (enum LoadMode.disk
). Điều này chỉ có nghĩa là tệp chỉ được tải theo từng phần khi cần. Để có âm thanh chạy lâu hơn, thông thường, tốt nhất bạn nên tải ở chế độ đĩa. Đối với các hiệu ứng âm thanh ngắn, bạn nên tải và giải nén các hiệu ứng đó vào bộ nhớ (enum LoadMode.memory
mặc định).
Tuy nhiên, bạn có một vài vấn đề. Thứ nhất, nhạc quá to, lấn át các âm thanh khác. Trong hầu hết các trò chơi, nhạc thường phát ở chế độ nền, qua đó giúp âm thanh giàu thông tin hơn như lời nói và hiệu ứng âm thanh. Bạn có thể dễ dàng khắc phục vấn đề này bằng cách sử dụng tham số âm lượng của phương thức phát. Ví dụ: bạn có thể thử _soloud!.play(musicSource, volume: 0.6)
để phát bài hát ở mức âm lượng 60%. Ngoài ra, bạn có thể đặt âm lượng vào bất cứ lúc nào sau này bằng cách dùng _soloud!.setVolume(_musicHandle, 0.6)
.
Vấn đề thứ hai là bài hát dừng đột ngột. Lý do là vì đây là một bài hát cần phát lặp lại và điểm bắt đầu của vòng lặp không phải là điểm bắt đầu của tệp âm thanh.
Đây là một lựa chọn phổ biến cho nhạc trò chơi vì bài hát bắt đầu bằng một đoạn mở đầu tự nhiên, sau đó có thể phát lâu dài khi cần mà không có một điểm lặp rõ ràng. Khi trò chơi cần chuyển sang trạng thái khác của bài hát đang phát, bài hát đó chỉ tắt dần.
Rất may, SoLoud cung cấp các cách để phát âm thanh lặp lại. Phương thức play()
nhận một giá trị boolean cho tham số looping
, đồng thời nhận giá trị cho điểm bắt đầu của vòng lặp làm tham số loopingStartAt
. Mã kết quả có dạng như sau:
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),
);
...
Nếu bạn không đặt tham số loopingStartAt
, tham số này sẽ mặc định là Duration.zero
(tức là đầu tệp âm thanh). Nếu bạn có một bản nhạc là một vòng lặp hoàn hảo mà không cần có phần giới thiệu, thì đây chính là điều bạn cần.
- Để đảm bảo nguồn âm thanh được xử lý đúng cách sau khi phát xong, hãy nghe luồng
allInstancesFinished
mà mỗi nguồn âm thanh cung cấp. Với các lệnh gọi nhật ký được thêm, phương thứcstartMusic()
sẽ có dạng như sau:
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),
);
}
...
Làm mờ âm thanh
Vấn đề tiếp theo là nhạc không bao giờ kết thúc. Hãy triển khai hiệu ứng làm mờ.
Bạn có thể triển khai hiệu ứng làm mờ bằng cách dùng một loại hàm được gọi vài lần trong một giây, chẳng hạn như Ticker
hoặc Timer.periodic
, sau đó giảm âm lượng của bản nhạc xuống những lần giảm nhỏ. Như vậy có được, nhưng mất rất nhiều công sức.
Rất may, SoLoud cung cấp các phương thức bắn và quên thuận tiện để thực hiện việc này cho bạn. Sau đây là cách bạn có thể làm giảm âm lượng nhạc trong vòng 5 giây, sau đó dừng thực thể âm thanh để không tiêu tốn tài nguyên CPU một cách không cần thiết. Thay thế phương thức fadeOutMusic()
bằng mã sau:
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. Áp dụng hiệu ứng
Một lợi thế lớn khi có một công cụ âm thanh phù hợp là bạn có thể xử lý âm thanh, chẳng hạn như định tuyến một số âm thanh thông qua âm vang, bộ cân bằng hoặc bộ lọc thông thấp.
Trong trò chơi, bạn có thể dùng tính năng này để phân biệt vị trí bằng âm thanh. Ví dụ: tiếng vỗ tay trong rừng sẽ khác với tiếng vỗ tay trong hầm bê tông. Trong khi rừng cây giúp phân tán và hấp thụ âm thanh, thì các bức tường trần của hầm trú ẩn phản chiếu sóng âm trở lại, dẫn đến âm vang. Tương tự, giọng nói của mọi người sẽ khác khi nghe qua tường. Tần số cao hơn của những âm thanh đó dễ bị suy giảm hơn khi chúng đi qua môi trường chất rắn, dẫn đến hiệu ứng bộ lọc thông thấp.
SoLoud cung cấp một số hiệu ứng âm thanh mà bạn có thể áp dụng cho âm thanh.
- Để nghe như thể người chơi đang ở trong một căn phòng lớn, chẳng hạn như nhà thờ hoặc hang động, hãy sử dụng trường
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();
}
...
Trường SoLoud.filters
cho phép bạn truy cập vào tất cả các loại bộ lọc và tham số của chúng. Mọi tham số cũng có các chức năng tích hợp sẵn như làm mờ dần và dao động.
Lưu ý: _soloud!.filters
hiển thị các bộ lọc chung. Nếu bạn muốn áp dụng các bộ lọc cho một nguồn, vui lòng sử dụng phần tử tương ứng AudioSource.filters
. Thao tác này cũng tương tự như trên.
Với mã trước đó, bạn làm như sau:
- Bật bộ lọc freeverb trên toàn cục.
- Đặt thông số Wet (Âm ướt) thành
0.2
, nghĩa là âm thanh thu được sẽ là 80% âm thanh gốc và 20% là đầu ra của hiệu ứng âm vang. Nếu bạn đặt tham số này thành1.0
, thì việc này sẽ giống như chỉ nghe thấy sóng âm thanh quay lại bạn từ những bức tường xa của phòng và không có âm thanh gốc. - Đặt thông số Room size (Kích thước phòng) thành
0.9
. Bạn có thể điều chỉnh tham số này theo ý thích hoặc thậm chí thay đổi linh động.1.0
là một hang động lớn trong khi0.0
là một phòng tắm.
- Nếu bạn thích, hãy thay đổi mã và áp dụng một trong các bộ lọc sau hoặc kết hợp các bộ lọc sau:
biquadFilter
(có thể dùng làm bộ lọc thông báo thấp)pitchShiftFilter
equalizerFilter
echoFilter
lofiFilter
flangerFilter
bassboostFilter
waveShaperFilter
robotizeFilter
7. Xin chúc mừng
Bạn đã triển khai một trình điều khiển âm thanh phát âm thanh, phát nhạc theo vòng lặp và áp dụng hiệu ứng.
Tìm hiểu thêm
- Hãy thử sử dụng bộ điều khiển âm thanh với các tính năng như tải trước âm thanh khi khởi động, phát các bài hát theo trình tự hoặc áp dụng bộ lọc dần dần theo thời gian.
- Đọc tài liệu gói của
flutter_soloud
. - Đọc trang chủ của thư viện C++ cơ bản.
- Đọc thêm về Dart FFI, công nghệ dùng để giao tiếp với thư viện C++.
- Xem bài nói của Guy Somberg về lập trình âm thanh trò chơi để tìm cảm hứng. (Ngoài ra còn có một URL dài hơn.) Khi nói về "phần mềm trung gian", Guy muốn nói đến các thư viện như SoLoud và FMOD. Phần còn lại của mã thường dành riêng cho từng trò chơi.
- Tạo bản dựng và phát hành trò chơi.