歡迎大家前往騰訊雲技術社群,獲取更多騰訊海量技術實踐乾貨哦~
作者:QQ音樂技術團隊
遊戲demo來源背景
2017年春節期間,一款來自東洋霓虹的遊戲開始在微博、朋友圈火了起來,這款遊戲就是《不要停!八分音符醬》。八分音符醬之所以能夠火起來,是因為它不通過手工操作,而是通過聲音來控制遊戲的行走和跳躍,這樣會讓使用者感覺很新穎。其有趣的玩法也在網上產生了很多段子,如”要不是鄰居來敲門,我早就通關了“等等,現在網上都有人通過樂器來玩這個遊戲。
一開始八分音符醬只有PC版本,目前又好像開始有了ios、android版,相關資源可以自行搜尋下載。本文則嘗試使用JS,結合web端音訊處理介面webAudio,實現一個H5版本的《不要停!八分音符醬》demo。本人也是第一次寫小遊戲,文章中出現的不足(比如遊戲建模、程式碼實現)也麻煩讀者們批評指正,共同學習。
開始
先看下游戲的截圖吧,體驗地址(由於系統相容問題,建議複製地址在微信內webview開啟) zhazhaxia.github.io/server/publ…
玩法
連線耳機後,最好在微信或手Q開啟這個頁面(系統需android5.0+),同意獲取麥克風許可權。然後對著麥克風大聲說幾句話,如“啊……”,然後遊戲裡面的doge就會開始走了,聲音大到一定程度,doge就會跳起來,掉坑則輸。
遊戲建模
本質上這應該是一個碰撞模型的遊戲,碰撞模型中幾個主要的概念是
- 目標物體:遊戲中doge方塊
- 碰撞物體:遊戲中的坑
- 輸贏條件:目標物體與碰撞物體部分體積重合則判為輸
根據以上的概念我們可以開始設計這款遊戲了。
遊戲設計
先看一張初始設計圖吧
- 目標物體
圖中棕色物體為目標物體,是我們視覺中的操作物件,可以進行行走或者跳躍
- 目標物體的載體
圖中藍色框則為遊戲中的路,承載了物體的行走。遊戲中的路是一個整體,我們實際在程式碼操作的物件,可以對下方的路整體移動,在視覺上感覺是目標物體的移動。移動後如下圖
- 碰撞物體
碰撞物體其實就是遊戲路中的坑。目標物體移動的時候,遊戲會給物體設定障礙,目標物體必須跳過這些坑,否則就遊戲就失敗重來了。
遊戲實現
遊戲建模設計後就可以開始實現了,由於這個是單頁面且動作相對簡單,所以採用單體的設計模式實現。
實現部分分兩塊介紹,第一部分介紹遊戲的總體實現思路,這部分相對比較容易。第二部分主要介紹遊戲中的webAudio聲控部分,這部分是遊戲的核心。
實現思路
- 引數配置
遊戲中涉及到一些引數的配置用來控制遊戲的狀態,具體的配置可以在編寫的時候生成,這裡有本文部分的配置資訊。
config:{
barrierWidth:80,//障礙物寬
containerWidth:$('.container').width(),//大容器寬
numberOfBarrier:0,//容器障礙物數目
rank : 2,//難度1,2,3
timer:null,
lockMove:false,//鎖定移動
lockLost:false,//坑來了,開始判斷
dangerArea:[null,null],//危險區域,碰撞區域
$tmpBarrier:$(".barrier-low"),
lockConsole:true,
volSum:0,//音量大小
vol:0,
score:0,
gameEnd:false,
walkValue:1,//走的音量臨界值
jumpValue:~~$('.threshold').val()//起跳的音量臨界值
}
複製程式碼
- 初始化
初始化主要是生成載體,填充到頁面中。本文主要根據遊戲容器的寬,生成初始載體的個數,填充到容器中。
initStat:function () {//初始化障礙物寬高,初始化載體
$('.barrier').width(exports.config.barrierWidth);
exports.config.numberOfBarrier = Math.ceil(exports.config.containerWidth / exports.config.barrierWidth) + 2;
$('.bottom-barrier').width(exports.config.numberOfBarrier * exports.config.barrierWidth);//障礙物容器寬
exports.createBarrier(exports.config.numberOfBarrier);//建立並填充
}
複製程式碼
- 建立載體
本文遊戲中的各種物體設計採用的是DOM來實現,當然也可以採用canvas或其他實現。載體移動到一定距離便在容器後面插入一個載體,插入的載體有可能是路,也可能是坑。插入後要把前面移動過的載體刪了,以免DOM過多造成的能效能問題。
createBarrier:function (num) {//建立障礙物,num個數
...//其他程式碼
$bc.append(exports.getBarrier(num,type));
},
getBarrier:function (num,type) {//獲取障礙物
var html = "";
for(var i = 0; i < num; i++){
html += '<div class="barrier '+(type === 1 ? "barrier-high" : "barrier-low")+'" data-id="'+new Date().getTime()+'">》</div>'
}
return html;
}
複製程式碼
- 目標物體移動和跳動
當音量達到一定條件,目標物體在視覺中就開始移動,實際我們移動的是目標物體下面的載體。
letsGo:function (vol) {//達到條件行走或者跳躍
if (vol > exports.config.walkValue ) {//走
exports.moveBarrier();
if (vol > exports.config.jumpValue) {//跳
exports.jumpNotes();
}
}else {//停
exports.stopBarrier();
}
}
複製程式碼
- 碰撞檢測
碰撞檢測就是對目標物體和碰撞物體之間距離的檢測。在本文這個遊戲中,採用一個陣列來更新碰撞物體,碰撞物體來的時候新增,離開的時候再更新一次。邊移動邊檢測。
judgeLost:function(){//是否失敗,碰撞檢測
....//其他程式碼
if(exports.config.$tmpBarrier.attr('data-id') !== $barrier.attr("data-id")){//更新碰撞物體
...//其他程式碼,更新,計分
}
...//
if (parseInt($notes.css("bottom")) <= 200) {//判斷是否在區間
var left = exports.config.dangerArea[0],
right = exports.config.dangerArea[1];
if(left <= 80 && right >= 160){//是否達到碰撞條件
exports.lost();
}
}
}
複製程式碼
- 失敗重置
遊戲失敗後會重新初始化設定引數,重複以上步驟
lost:function () {//輸了掉坑了
$('.title').text('啊!掉坑了!重新來一遍吧!');//一下是重置部分
exports.stopBarrier();
exports.config.gameEnd = true;
$notes.stop(true).animate({bottom:0},500)
setTimeout(function () {
exports.config.gameEnd = false;
$('.bottom-barrier').html("");
exports.initStat();
$notes.css("bottom",200)
$('.title').text('大聲點!不要停!八分音符醬')
exports.config.score = 0;
$('.j_score').text(exports.config.score);
}, 3000)
}
複製程式碼
利用webAudio控制遊戲的行走和跳躍
- 獲取麥克風跟音量大小
在web中獲取麥克風可以通過navigator.getUserMedia獲取,不過目前在移動端只有android5.0+才有這個功能,iPhone目前還沒有提供這方面的介面給JS呼叫。目前國內部分手機廠商的預設瀏覽器對這個許可權也有限制,或者有相容問題,建議用微信、手Q等webview採用QQ瀏覽器X5核心的app進行體驗(賣了個廣告)。
navigator.getUserMedia在pc的相容一般是
navigator.getUserMedia = (navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
複製程式碼
webAudioApi
獲取麥克風的大小需要用到webAudioApi的相關介面(webAudioApi的瞭解可以參考筆者之前寫的介紹github.com/zhazhaxia/w… )
- 簡要介紹
webAudioApi是W3C制定的用來處理web音訊的規範。核心是 AudioContext , AudioContext 是處理web音訊的核心物件,所有的處理介面以節點方式連線。如下圖所示,描述了一個源節點到目標節點的web音訊處理過程。
- 錄音音訊返耳
音訊返耳指的是在錄音的過程中,麥接收的音訊在耳機的實時反饋。
利用webAudioApi的scriptProcessNode可以獲取到麥克風的音訊資料,將音訊資料再輸出,就會有返耳效果。
實現過程:webAudio獲取到麥克風音訊源後,連線到ScriptProcess節點,ScriptProcess可以獲取音訊輸入資料,並將音訊實時輸出,從而達到返耳效果。
var source=exports.audioContext.createMediaStreamSource(stream);
//用於錄音的processor節點
var recorder=exports.audioContext.createScriptProcessor(1024,1,1);
source.connect(recorder);//節點的連線
recorder.onaudioprocess=function(e){//正在錄音
var inputBuffer = e.inputBuffer;
var outputBuffer = e.outputBuffer;
for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
var inputData = inputBuffer.getChannelData(channel);//音訊輸入
var outputData = outputBuffer.getChannelData(channel);
for (var sample = 0; sample < inputBuffer.length; sample++) {
outputData[sample] = inputData[sample];//返耳
}
}
};
複製程式碼
- 音訊振幅資訊
獲取音訊振幅可以理解為獲取音訊的音量大小。
利用webAudioApi的Analyser介面可以獲取到音訊經過傅立葉變換後的資料,這些資料包含了音訊振幅等資訊。如果要實時獲取音訊振幅大小,需要在 onaudioprocess 中獲取資料。由於麥克風獲取到的音訊噪音成分有點大,此處作一個加權處理,平均後的值作為目標振幅值。最後根據處理後的音訊振幅進行遊戲的行走和跳躍。
var analyser = exports.audioContext.createAnalyser();//音訊解析器
recorder.connect(analyser);
analyser.connect(exports.audioContext.destination);
// 設定資料
analyser.fftSize = 1024;//頻道數量
bufferLength = analyser.fftSize;
dataArray = new Float32Array(bufferLength);//每個頻道的頻率
recorder.onaudioprocess=function(e){
analyser.getFloatTimeDomainData(dataArray);//獲取振幅資訊
exports.getVolume(dataArray);//加權振幅
}
};
複製程式碼
1.由於不同硬體之間的差距,返耳效果的延遲有所區別
2.由於PC跟手機硬體有所區別,實際的振幅值,PC會明顯高於手機
以上就是本文遊戲的主要設計的相關思路。
結束語
本文從PC遊戲《不要停?八分音符醬》的靈感出發,描述了其H5簡易版本的開發思路,遊戲的設計許多不足,請讀者們批評指正。
筆者開發H5版本《八分音符醬》的意圖不只是為了把pc的遊戲用H5來實現,而且想通過這麼一個在玩法上有些創新的遊戲,來完成一個webAduio的demo。目前web正在蓬勃發展,W3C也出了許多新的web標準,如webAudioApi,webAssembly,webAR,webGL等,這些都在發展階段,在實際的應用中還沒有廣泛應用。所以希望通過這麼一個demo,能夠有更多想法,利用webAudio做出更多好玩有趣的應用。
W3C webAudioApi www.w3.org/TR/webaudio…
webAudioApi及應用案例分析 github.com/zhazhaxia/w…
相關閱讀
王者榮耀高併發背後的故事
最快速的視野管理演算法
web 前端入門神經網路(一)
此文已由作者授權騰訊雲技術社群釋出,轉載請註明文章出處
原文連結:https://cloud.tencent.com/community/article/429878