webAudio 開發 H5 版《 八分音符醬 》

騰訊雲加社群發表於2017-10-19

歡迎大家前往騰訊雲技術社群,獲取更多騰訊海量技術實踐乾貨哦~

作者: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


相關文章