本章音效文件都来自于公共许可: http://files.cnblogs.com/mignet/sounds.zip
在游戏中,播放背景音乐和音效是基本的功能。
Libgdx提供了跨平台的声音播放功能,支持的文件格式有:
•wav (RIFF WAVE)
•mp3 (MPEG-2 Audio Layer III)
•ogg (Ogg Vorbis)
Libgdx有2个接口来管理音乐和音效:Music和Sound.
Music通常要花更多的CPU周期,因为它在播放到声卡之前需要解析。Sound就不用了,因为Sound在加载的时候都已经解析过了。
常规用法:Sound sound = Gdx.audio.newSound(Gdx.files.internal("sound.wav"));同时记得sound.dispose(); // free allocated memory
除了Music和Sound,Libgdx还提供了底层的接口AudioDevice、AudioRecorder用来访问声卡以记录和播放原始的声音样本。
AudioDevice接口允许您直接发送PCM编码音频样本到音频设备。例如:
AudioDevice audioDevice = Gdx.audio.newAudioDevice(44100, false);//44.1 kHz
audioDevice.dispose(); // free allocated memory
发送到声卡的声音数据可以是一组浮点数或者是一组16位带符号短整形数.
void writeSamples(float[] samples, int offset, int numSamples);//offset (start),numSamples (length)
void writeSamples(short[] samples, int offset, int numSamples);
立体声意味着需要采集的样本是双倍的,因为立体声就是左声道和右声道交叉合成的。就是说44.1 kHz的采样率需要44100的单声道样本和88200的立体声样本。
AudioRecorder audioRecordedr = Gdx.audio.newAudioRecorder(44100, false);
audioRecorder.dispose(); // free allocated memory
同样的:void read(short[] samples, int offset, int numSamples);
现在,我们拥有足够的知识来生成自己的声音了,自己合成并保存,播放合成的声音测试效果,都可以实行了。
但是这样做对于很多经验丰富的程序员来讲都太高端了,哈哈。我们这里不讨论。
一个可行的解决方案来获得一些不错的音响效果是使用一个现有的自由的和开放源码的声音生成器。
程序员自己的音效生成工具->声音生成器:
sfxr
这个生成器最初是由托马斯“DrPetter“佩特森在2007年开发的,后来慢慢的又出现了几个sfxr变体版本,像bfxr, cfxr, as3sfxr
首先来看sfxr:
sfxr之所以可以很快流行并被全球的程序员们使用,是因为只需要简单的按下这个软件上面的“RANDOMIZE”按钮,就可以生成声音。
而且还提供了很多基本的音效像PICKUP/COIN, LASER/SHOOT, EXPLOSION, POWERUP, HIT/HURT, JUMP, BLIP/SELECT
生成想要的声音之后,导出.wav文件就可以用了。
官方源码:https://code.google.com/p/sfxr/
cfxr是Cocoa sfxr的缩写,是Mac OS下原生的Cocoa应用程序,专门为Cocoa写的实现版本。
官方源码:https://github.com/nevyn/cfxr/
bfxr:这个功能很丰富,可以创建更复杂的音效,有兴趣可以自己摸索。
官方源码:https://github.com/increpare/bfxr/
现在开始在我们的游戏中使用音乐和音效。先把文件添加到assert(背景音乐没有提供下载因为太大了,你可以随便用什么音乐文件代替):
和前面的资源管理提到的一样,我们使用内部类来统一管理各种资源。在Asserts中添加:
public AssetSounds sounds; public AssetMusic music; public class AssetSounds { public final Sound jump; public final Sound jumpWithFeather; public final Sound pickupCoin; public final Sound pickupFeather; public final Sound liveLost; public AssetSounds(AssetManager am) { jump = am.get("sounds/jump.wav", Sound.class); jumpWithFeather = am.get("sounds/jump_with_feather.wav", Sound.class); pickupCoin = am.get("sounds/pickup_coin.wav", Sound.class); pickupFeather = am.get("sounds/pickup_feather.wav", Sound.class); liveLost = am.get("sounds/live_lost.wav", Sound.class); } } public class AssetMusic { public final Music song01; public AssetMusic(AssetManager am) { song01 = am.get("music/keith303_-_brand_new_highscore.mp3", Music.class); } }
然后在init里添加:
public void init(AssetManager assetManager) { this.assetManager = assetManager; // set asset manager error handler assetManager.setErrorListener(this); // load texture atlas assetManager.load(Constants.TEXTURE_ATLAS_OBJECTS, TextureAtlas.class); // load sounds assetManager.load("sounds/jump.wav", Sound.class); assetManager.load("sounds/jump_with_feather.wav", Sound.class); assetManager.load("sounds/pickup_coin.wav", Sound.class); assetManager.load("sounds/pickup_feather.wav", Sound.class); assetManager.load("sounds/live_lost.wav", Sound.class); // load music assetManager.load("music/keith303_-_brand_new_highscore.mp3", Music.class); // start loading assets and wait until finished assetManager.finishLoading(); Gdx.app.debug(TAG, "# of assets loaded: " + assetManager.getAssetNames().size); for (String a : assetManager.getAssetNames()) Gdx.app.debug(TAG, "asset: " + a); TextureAtlas atlas = assetManager.get(Constants.TEXTURE_ATLAS_OBJECTS); // enable texture filtering for pixel smoothing for (Texture t : atlas.getTextures()) t.setFilter(TextureFilter.Linear, TextureFilter.Linear); // create game resource objects fonts = new AssetFonts(); bunny = new AssetBunny(atlas); rock = new AssetRock(atlas); goldCoin = new AssetGoldCoin(atlas); feather = new AssetFeather(atlas); levelDecoration = new AssetLevelDecoration(atlas); sounds = new AssetSounds(assetManager); music = new AssetMusic(assetManager); }
还记得我们在Options菜单中让用户来设置音乐音效吗,现在派上用场了。
创建新类AudioManager来管理播放和停止:
package com.packtpub.libgdx.canyonbunny.util; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.audio.Sound; public class AudioManager { public static final AudioManager instance = new AudioManager(); private Music playingMusic; // singleton: prevent instantiation from other classes private AudioManager() { } public void play(Sound sound) { play(sound, 1); } public void play(Music music) { stopMusic(); playingMusic = music; if (GamePreferences.instance.music) { music.setLooping(true); music.setVolume(GamePreferences.instance.volMusic); music.play(); } } public void stopMusic() { if (playingMusic != null) playingMusic.stop(); } public void onSettingsUpdated() { if (playingMusic == null) return; playingMusic.setVolume(GamePreferences.instance.volMusic); if (GamePreferences.instance.music) { if (!playingMusic.isPlaying()) playingMusic.play(); } else { playingMusic.pause(); } } public void play(Sound sound, float volume) { play(sound, volume, 1); } public void play(Sound sound, float volume, float pitch) { play(sound, volume, pitch, 0); } public void play(Sound sound, float volume, float pitch, float pan) { if (!GamePreferences.instance.sound) return; sound.play(GamePreferences.instance.volSound * volume, pitch, pan); } }
修改MenuScreen:
private void onSaveClicked() { saveSettings(); onCancelClicked(); AudioManager.instance.onSettingsUpdated(); } private void onCancelClicked() { btnMenuPlay.setVisible(true); btnMenuOptions.setVisible(true); winOptions.setVisible(false); AudioManager.instance.onSettingsUpdated(); }
修改CanyonBunnyMain的create方法:
@Override public void create() { // Set Libgdx log level Gdx.app.setLogLevel(Application.LOG_DEBUG); // Load assets Assets.instance.init(new AssetManager()); // Load preferences for audio settings and start playing music GamePreferences.instance.load(); AudioManager.instance.play(Assets.instance.music.song01); // Start game at menu screen ScreenTransition transition = ScreenTransitionSlice.init(2, ScreenTransitionSlice.UP_DOWN, 10, Interpolation.pow5Out); setScreen(new MenuScreen(this), transition); }
Libgdx自动处理在游戏暂停和返回时的音乐播放问题,所以这里不需要额外的代码修改。
继续修改WorldController:
..
public void update(float deltaTime) {
.. if (!isGameOver() && isPlayerInWater()) { AudioManager.instance.play(Assets.instance.sounds.liveLost);
..
}
private void onCollisionBunnyWithGoldCoin(GoldCoin goldcoin) {
goldcoin.collected = true;
AudioManager.instance.play(Assets.instance.sounds.pickupCoin);
score += goldcoin.getScore();
Gdx.app.log(TAG, "Gold coin collected");
}
private void onCollisionBunnyWithFeather(Feather feather) {
feather.collected = true;
AudioManager.instance.play(Assets.instance.sounds.pickupFeather);
score += feather.getScore();
level.bunnyHead.setFeatherPowerup(true);
Gdx.app.log(TAG, "Feather collected");
}
修改BunnyHead:
public void setJumping(boolean jumpKeyPressed) { switch (jumpState) { case GROUNDED: // Character is standing on a platform if (jumpKeyPressed) { AudioManager.instance.play(Assets.instance.sounds.jump); // Start counting jump time from the beginning timeJumping = 0; jumpState = JUMP_STATE.JUMP_RISING; } break; case JUMP_RISING: // Rising in the air if (!jumpKeyPressed) { jumpState = JUMP_STATE.JUMP_FALLING; } break; case FALLING:// Falling down case JUMP_FALLING: // Falling down after jump if (jumpKeyPressed && hasFeatherPowerup) { AudioManager.instance.play( Assets.instance.sounds.jumpWithFeather, 1, MathUtils.random(1.0f, 1.1f)); timeJumping = JUMP_TIME_OFFSET_FLYING; jumpState = JUMP_STATE.JUMP_RISING; } break; } }
ok,运行起来看看。游戏从无声世界进入了有声世界里。从此,程序员也可以自己玩音乐了。