音游制作雜談(230415)
一、延續(xù)性Note與分段優(yōu)化?
// 上次的雜談講到了將Note按照一定的時(shí)間間隔來(lái)進(jìn)行分組的方法。但是,在一些場(chǎng)合,這個(gè)方法存在一些潛在的弊病??紤]這樣一種情況:

// 如圖是一個(gè)具有2個(gè)節(jié)點(diǎn)的長(zhǎng)條:在后面的節(jié)點(diǎn)預(yù)存好前面節(jié)點(diǎn)的信息(時(shí)間、x位置、寬度),然后為每一個(gè)“后面的節(jié)點(diǎn)”生成一個(gè)Mesh,使得后面的節(jié)點(diǎn)視覺(jué)上與前面的節(jié)點(diǎn)連接在一起,形成一個(gè)長(zhǎng)條。
// 現(xiàn)在采用分組優(yōu)化,在這個(gè)例子中我們?cè)O(shè)想“將兩根格線的距離定為分組間距”,這根長(zhǎng)條的時(shí)間跨度便是4個(gè)組。
// 于是出現(xiàn)問(wèn)題:節(jié)點(diǎn)只出現(xiàn)在了第1個(gè)組的開(kāi)頭和第5個(gè)組的開(kāi)頭,在中間的3個(gè)組,長(zhǎng)條“后面的節(jié)點(diǎn)”不屬于這三個(gè)組的任何一個(gè)組,如果第5個(gè)組沒(méi)有納入“照度”的范圍內(nèi),長(zhǎng)條“后面的節(jié)點(diǎn)”便不會(huì)顯示在屏幕上,從而導(dǎo)致其附帶的Mesh也不會(huì)顯示在屏幕上,效果變成:

// 非常荒誕。不過(guò),插值法足以解決這個(gè)問(wèn)題:


二、分段索引?
// 上面介紹到,在段與段的分界點(diǎn)進(jìn)行插值處理,可以解決延續(xù)性Note因分段優(yōu)化導(dǎo)致的中斷問(wèn)題。插值法帶來(lái)的副作用有時(shí)非常實(shí)用(例如DanceRail3,采用每一個(gè)長(zhǎng)條中繼點(diǎn)均作為一個(gè)獨(dú)立的Catch Note的設(shè)計(jì),那么這種“插值副作用”便成功達(dá)成了“長(zhǎng)條每8分長(zhǎng)度增加1Combo”的設(shè)計(jì)目的),有時(shí)不需要將其納入考慮范圍(例如,如果在譜面內(nèi)指定緩動(dòng)曲線,那么解析譜面的過(guò)程中自然存在一個(gè)將曲線插值處理為折線的過(guò)程;只要達(dá)到了一定的插值精度,插值點(diǎn)與插值點(diǎn)的距離會(huì)自然地小于分段組距)。
// 但是,最終還是希望找到一種“無(wú)副作用”的分組優(yōu)化法,從而讓分組優(yōu)化得到更廣泛的適用、更自由的運(yùn)用。分組索引便是“無(wú)副作用的分組優(yōu)化法”之一,它的大體思路如下:
A. 確定分組組距;
B. 開(kāi)始遍歷所有Note。如遇到?jīng)]有規(guī)定父節(jié)點(diǎn)的Note,則直接將當(dāng)前Note的形式參數(shù)(時(shí)間、dtime、……)整除以規(guī)定的分組組距,將該Note的編號(hào)(可以是Array的位置,也可以是ID式的編號(hào))記錄到索引表的相應(yīng)位置;
C. 如遇到規(guī)定了父節(jié)點(diǎn)的Node,則計(jì)算n = (t//d) - (t0//d)。將t//d得出的數(shù)作為第一個(gè)組,錄入該Node的編號(hào);然后在其前n個(gè)組均錄入該Node的編號(hào)。
D. 正式使用時(shí),若輪詢到了“規(guī)定了父節(jié)點(diǎn)的Node”,則根據(jù)判定線位置的形式時(shí)間 f(t) 和繪制最大形式時(shí)間(照度形式時(shí)間)f(t) + i/k(可參考上期雜談)判定【父節(jié)點(diǎn)是否在屏幕上】及【該Node是否在屏幕上】。對(duì)于不在屏幕上的節(jié)點(diǎn),則根據(jù) f(t) ,?f(t) + i/k 中的一個(gè)插值生成一個(gè)臨時(shí)節(jié)點(diǎn),在Mesh的生成過(guò)程“取而代之”。
三、實(shí)現(xiàn)一個(gè)健壯的時(shí)間系統(tǒng)?
// 時(shí)間系統(tǒng)是一個(gè)音游賴以工作的前提。玩家常常討論某個(gè)音游的判定范圍時(shí)間(- xx ms ~ xx ms);譜面作者在創(chuàng)作譜面時(shí),也需要把Note放置在正確的時(shí)間位置上。
// 跟上次一樣的套路?!叭绻哉{(diào)用游戲引擎的API為切入點(diǎn)”,在時(shí)間系統(tǒng)的設(shè)計(jì)上便會(huì)進(jìn)入Timer的迷思,然后開(kāi)始思考:怎么游戲引擎給的Timer是倒計(jì)時(shí)?有沒(méi)有那種時(shí)間遞增的計(jì)時(shí)器?……
// 先說(shuō)結(jié)論:“時(shí)間遞增的計(jì)時(shí)器”是有的,存在于操作系統(tǒng)中。無(wú)論是iOS、Android還是Windows,都可以獲取操作系統(tǒng)提供的毫秒時(shí)間。關(guān)于這個(gè)時(shí)間為什么是操作系統(tǒng)提供的,個(gè)人認(rèn)為是為了保持“時(shí)間來(lái)源的統(tǒng)一”:在設(shè)備內(nèi)部實(shí)現(xiàn)時(shí)間來(lái)源的統(tǒng)一,會(huì)為權(quán)威授時(shí)中心在理想條件下建立全體設(shè)備“時(shí)間來(lái)源的統(tǒng)一”提供方便。此處不展開(kāi)講。
// 利用操作系統(tǒng)提供的絕對(duì)時(shí)間,可以進(jìn)行各種相對(duì)時(shí)間的計(jì)算。常見(jiàn)的用例有:
A. 標(biāo)記歌曲開(kāi)始時(shí)間 t0 ,然后在后續(xù)的每個(gè)Gameloop都調(diào)取一次當(dāng)前系統(tǒng)時(shí)間 t ,則樂(lè)曲進(jìn)度是 t-t0 ;
B. 玩家設(shè)置了譜面偏移 t1,那么樂(lè)曲進(jìn)度是 t-t0-t1 ;
C. 玩家在 t2 按下暫停,又在 t3 繼續(xù)游戲(假設(shè)沒(méi)有做恢復(fù)倒計(jì)時(shí)),那么樂(lè)曲進(jìn)度是 t-t0- 2*t1 - (t3-t2)?。這里的 2*t1?考慮的是從暫停狀態(tài)切換到到解除暫停狀態(tài)后,播放音頻又需要一段延遲;
D. 為了解決玩家【暫停后繼續(xù)】反應(yīng)不過(guò)來(lái)的問(wèn)題,決定在繼續(xù)游戲時(shí)對(duì)譜面進(jìn)行一段倒放。設(shè)玩家在 t2 按下暫停,又在 t3 按下繼續(xù)游戲,倒放的用時(shí)是 t4。倒放結(jié)束后,樂(lè)曲的進(jìn)度是 t-t0- 2*t1?-?(t3-t2) - 2*t4。這么做的話,建議在繼續(xù)游戲時(shí)就修正音頻的播放進(jìn)度,并立即播放音頻。
// 接下來(lái)依舊是講解幾個(gè)注意點(diǎn):
A. 對(duì)于一些關(guān)鍵節(jié)點(diǎn),最好建立起一個(gè) 事件-時(shí)間戳 表,例如樂(lè)曲開(kāi)始、玩家按下暫停、玩家按下繼續(xù)游戲、恢復(fù)倒計(jì)時(shí)歸零(預(yù)期時(shí)間)、譜面倒放完畢……
B. 如果需要一個(gè)能讓玩家在不調(diào)整判定線位置的情況下“對(duì)正樂(lè)曲與譜面”的機(jī)制的話,優(yōu)先考慮譜面偏移。樂(lè)曲偏移在動(dòng)態(tài)調(diào)整的過(guò)程中存在延遲堆積/漂移等問(wèn)題,相當(dāng)棘手;此外,游戲開(kāi)始時(shí)也需要?jiǎng)討B(tài)調(diào)整樂(lè)曲的播放時(shí)機(jī),便會(huì)遇到“xx時(shí)間發(fā)生xx事件”的漂移偏差,而此時(shí)樂(lè)曲播放決定了樂(lè)曲與譜面能否對(duì)正,這又是時(shí)間敏感事件了;
C. 每次暫停都會(huì)導(dǎo)致一次譜面偏移 t1 的增加,以及兩次倒放用時(shí) t4 的增加;
D. 玩家在游玩中途將游戲應(yīng)用切換至后臺(tái)的情況,通常需要觸發(fā)一個(gè)自動(dòng)暫停機(jī)制。如何判斷玩家“將游戲應(yīng)用切換至后臺(tái)”呢?iOS和Android對(duì)于游戲應(yīng)用呈現(xiàn)的畫(huà)面都給出了相應(yīng)的窗口概念,藉此“游戲應(yīng)用切換至后臺(tái)”便可以轉(zhuǎn)化為“游戲窗口失去焦點(diǎn),進(jìn)入非活動(dòng)狀態(tài)”。這一點(diǎn)不經(jīng)人點(diǎn)撥很難認(rèn)識(shí)到。