聲音 (Audio)
遊戲中的聲音包括背景音樂與特效音,背景音樂會循環播放,而且會根據關卡的不同切換,舉例來說:當玩家遭遇 Boss 戰的時候,會切換到比緊張的音樂,當 Boss 戰結束後,又會切換回原本的音樂。特效音就不同了,特效音的播放時間很短而且通常不會重複,像是物體碰撞、爆炸、閃光等等的聲音都是屬於特效音。
播放 (Play)
播放的聲音檔必須先通過 loadStream()
或是 loadStream()
加載進來,取得一個聲音 handle。然後再用 play
與先前取得的 handle 播放。成功播放後會取得一個 channel number,這個 channel number 會被用在後續該聲音的停止、暫停、恢復行為上。 當然你也可以指定 channel
,讓該聲音在指定的 channel 播放,舉例來說,我們可以讓背景音樂都在相同的頻道播放,而遊戲設定的頁面就可以透過調整指定頻道的聲音大小,來達到調整背景音樂的聲音大小的功能。
如果是背景音樂,你可能需要將 loops
設定為 -1
,讓它循環播放。而背景音樂開始時,你也可以指定 fadein
,讓聲音是漸強 (從小聲變大聲) 地播放,這樣的好處是在音樂切換時比較不會讓玩家覺得:奇怪,怎麼音樂就突然出來了。
local bgmHandle = audio.loadStream("sounds/bg1.mp3")
local backgroundMusicChannel = audio.play( bgmHandle, {
channel=1,
loops=-1,
fadein=5000
})
注意 Corona SDK 目前只支援 32 個 channel,如果全部的 channel 都被佔用,就沒辦法播放新的聲音了。
loadStream() 與 loadSound() 的異同
loadStream()
與 loadSound()
都能將聲音加載到記憶體,那麼它們兩者有什麼不同或相同之處呢?
記憶體與 CPU
loadStream()
不會將完整的聲音檔載入記憶體,而是會將聲音加載進一段 buffer,試著去最小化該聲音的記憶體使用量,所以 loadStream()
一般用於播放較大較長的聲音檔,像是背景音樂和演講。而 loadSound()
則會將整個音樂檔案加載進記憶體,由於不需要像 loadStream()
一樣操作 buffer,loadSound()
會佔用較少的 CPU。如果你的聲音檔很小而且很常被播放:像是射擊遊戲中的子彈聲音,就會建議使用 loadSound()
。
多 channel 同時播放
此外被 loadStream()
加載的聲音實體沒辦法被同時在不同的 channel 播放。如果你希望多個 channel 同時播放被 loadStream()
加載的聲音,就必須用 loadStream()
加載多次。但是被 loadSound()
加載的聲音卻沒有這樣的限制,loadSound()
加載的聲音只需要要加載一次,就可以同時在不同 channel 播放。
重新播放
重新播放的意思是指:先使用 audio.stop()
停止聲音,之後再使用 audio.play()
播放剛才被停止的聲音。重新播放的時候,會發生什麼事情呢?
如果你的聲音是透過 loadStream
加載,會從上次停止的地方繼續播放。反之如果是 loadSound
加載的聲音,則會從頭播放。如過 loadStream
加載的聲音再次重頭播放,就必須使用 audio.rewind()
倒帶 (rewind)
audio.rewind() 可以讓聲音從頭播放。其中參數可以接受 channel 或是 handle。而要帶入什麼樣的參數則是跟聲音當初被怎樣加載有關的。如果聲音是透過 loadSound()
加載,由於當初 loadSound
的設計就是為了能在多個 channel 有效率的同時播放同一個 handle,所以 loadSound()
的聲音要倒帶只能帶入 channel 參數。反正 loadStream()
加載的聲音可以透過 channel 或是 handle 倒帶。有趣的是,如果在被 loadStream
加載的聲音正在播放的時候,倒帶該聲音,你會發現聲音會持續一段時間,將 buffer 內的聲音播完,才會從頭播放。如果你希望在 loadStream()
的機制下做到馬上重頭播放的效果,你可以先透過 audio.pause()
暫停,然後再倒帶。
回收機制
不管你使用的是 loadStream()
或是 loadSound()
,將聲音透過 audio.dispose()
回收,是開發者的責任,Corona SDK 並不會幫你回收沒有在使用的聲音。
透過 audio.dispose()
回收後,可以釋放該聲音佔用的 handle 與記憶體。然而在大部分的用途上,開發者也許會希望聲音資源在遊戲結束前都能一直存在,因為遊戲聲音佔用的記憶體很小,完全不必擔心記憶體超量的問題。在這種情況下,開發者當然就不需要煩惱如何回收聲音資源了。
暫停
暫停全部聲音
audio.pause(0)
暫停指定頻道的聲音
audio.pause( backgroundMusicChannel )
停止
停止全部聲音
audio.stop()
停止指定頻道的聲音
audio.pause( backgroundMusicChannel )
恢復
恢復全部聲音
audio.resume(0)
暫停指定頻道的聲音
audio.resume( backgroundMusicChannel )
倒帶 (rewind)
audio.rewind( { channel = backgroundMusicChannel} )
audio.rewind(bgmHandle)
成功會回傳 true
,反之則回傳 false
。
sfx.lua
內容豐富的遊戲通常伴隨著豐富的音樂,這些音樂檔案就和 sprites 一樣,需要一個 module 集中管理,這樣做的好處除了讓程式碼維護容易外,也能避免頻繁加載音樂造成的效能低落問題。這裡提供讀者一個能集中管理聲音檔案的 module:sfx.lua。
sfx.lua 會先透過 loadSound()
將聲音預載,並且在 init()
初始化保留的 channel 和音量。
--
-- sfx.lua
--
local config = require("gameConfig")
local sfx = {}
sfx.CHANNEL_BG = 1
sfx.CHANNEL_UI = 2
sfx.bg = {
handle = audio.loadSound( "sounds/bg1.mp3" ),
channel = sfx.CHANNEL_BG
}
sfx.bgBoss = {
handle = audio.loadSound( "sounds/bg2.mp3" ),
channel = sfx.CHANNEL_BG
}
sfx.title = {
handle = audio.loadSound( "sounds/bg3.mp3"),
channel = sfx.CHANNEL_BG
}
sfx.button = {
handle = audio.loadSound("sounds/button.mp3"),
channel = sfx.CHANNEL_UI
}
function sfx:initVolumn()
audio.setVolume( 0.7, { channel = self.CHANNEL_BG } ) --music track
audio.setVolume( 0.8, { channel = self.CHANNEL_UI } ) --ui
end
function sfx:init()
audio.reserveChannels(2)
self:initVolumn()
end
function sfx:play(name, options)
if not config.soundOn then
return
end
--reset volume
self:initVolumn()
if not options then
options = {}
end
local reservedChannel = self[name].channel
if reservedChannel and reservedChannel ~= 0 then
audio.stop(reservedChannel)
options.channel = reservedChannel
else
local availableChannel = audio.findFreeChannel( 3 )
audio.setVolume( 1, { channel=availableChannel } )
options.channel = availableChannel
end
return audio.play(self[name].handle, options)
end
function sfx:pause(channel)
audio.pause(channel)
end
function sfx:stop(channel)
if not channel then
audio.stop()
else
audio.stop(channel)
end
end
function sfx:pause(channel)
audio.pause(channel)
end
function sfx:resume(channel)
audio.resume(channel)
end
function sfx:fadeOut(channel, time)
audio.fadeOut(channel, time)
end
sfx:init()
return sfx
此外讀者可以將 gameConfig
取代成自己遊戲內的設定檔,gameConfig
內的 soundOn
可以決定聲音是否要播出,這在實作靜音功能的時候非常有用。
--
-- gameConfig.lua
--
local config = {}
config.soundOn = true
return config