ズキュンと爽快! WebAudioAPIでブラウザゲームに効果音を!

ズキュンと爽快! WebAudioAPIでブラウザゲームに効果音を!

つっちー

つっちー

こんにちは。フロントエンドエンジニアのつっちーです。
恥ずかしながらわたくし、ブラインドタッチが得意ではありません。特に人差し指。担当するキー多すぎませんか。

(そういえば昔、「苦手な部分だけ抜き出して練習しなさい」って先生が言ってたナァ……) O o 。.(´-`)

ということで、特定の文字(キー)だけひたすら出題するタイピングゲームを、WebAudioAPI を使って爽快に仕上げてみます。
WebAudioAPI 実用の一例となれば幸いです。

表示と仕組みをつくる

type2

最終的にはタイピングに合わせて音源を再生しますが、まずは、文字の表示と、タイピングに応じて表示を切り替える仕組みから作っていきましょう。

手順を3段階に分けて進めていきます。

  • 特定の文字をランダムに表示する
  • 表示された文字がタイプされたら別の文字を表示する
  • 連続成功回数を表示し、ミスしたら回数をリセットする

特定の文字をランダムに表示する

まずは、出題したい文字だけを格納した配列を用意し、そこからランダムにひとつを選択、表示させます。

<p class="char" id="js-char"></p>
'use strict';

var random, charList, index, elChar;

random = function(length) {
  return Math.floor(Math.random() * length);
};
charList = ['R','T','Y','U','F','G','H','J','V','B','N','M'];
index = random(charList.length);
elChar = document.getElementById('js-char');
elChar.textContent = charList[index];

 
DEMO
GitHub

リロードするたびに、異なる文字が表示されるかと思います。

表示された文字がタイプされたら別の文字を表示する

次に、表示させた文字がタイピングされたら、再度配列から 1 文字を選択、表示させます。

<p class="char" id="js-char"></p>
'use strict';

var random, onKeydown, charList, index, elChar;

random = function(current, length) {
  var result;
  do {
    result = Math.floor(Math.random() * length);
  } while (result === current);
  return result;
};
onKeydown = function(e){
  if (String.fromCharCode(e.keyCode) === charList[index]) {
    index = random(index, charList.length);
    elChar.textContent = charList[index];
  }
};
charList = ['R','T','Y','U','F','G','H','J','V','B','N','M'];
index = random('', charList.length);
elChar = document.getElementById('js-char');
elChar.textContent = charList[index];
window.addEventListener('keydown', onKeydown);

 
DEMO
GitHub

表示された文字がタイプされた場合は別の文字が表示され、表示された文字と異なる文字がタイピングされた場合は何も起きない状態となりました。

連続成功回数を表示し、ミスしたら回数をリセットする

今回は好きなだけ練習できるように、出題に終わりは設けません。
その代わり、目標の設定や、上達の確認ができるように、何回連続で正しくタイピングしたかを表示します。ゲームらしく、COMBOと表示しましょう。

<p class="char" id="js-char"></p>
<p class="counter"><span id="js-counter"></span> COMBO</p>
'use strict';

var random, onKeydown, charList, index, elChar, elCounter;

random = function(current, length) {
  var result;
  do {
    result = Math.floor(Math.random() * length);
  } while (result === current);
  return result;
};
onKeydown = function(e){
  if (String.fromCharCode(e.keyCode) === charList[index]) {
    index = random(index, charList.length);
    elChar.textContent = charList[index];
    elCounter.textContent = parseInt(elCounter.textContent, 10) + 1;
  } else {
    elCounter.textContent = 0;
  }
};
charList = ['R','T','Y','U','F','G','H','J','V','B','N','M'];
index = random('', charList.length);
elChar = document.getElementById('js-char');
elChar.textContent = charList[index];
elCounter = document.getElementById('js-counter');
elCounter.textContent = 0;
window.addEventListener('keydown', onKeydown);

 
DEMO
GitHub

これで表示と仕組みは完成です。

音を設定する

ここからは、いよいよ音の設定です。

  • ズキュンと爽快な音源を用意する
  • 表示された文字がタイプされたら音を鳴らす
  • 表示された文字と異なる文字がタイピングされた場合は別の音を鳴らす
  • 使用する音源をランダムに切り替える

の、4段階の手順で作っていきます。

ズキュンと爽快な音源を用意する

こちらの気持ち良い音源を使用させていただくことにします。

作品名 銃01
音源ファイル http://maoudamashii.jokersounds.com/archives/se_maoudamashii_battle_gun01.html
作者名 魔王魂

 

表示された文字がタイプされたら音を鳴らす

音声ファイルを利用するための AudioNode としては、短く、発音タイミングの正確な制御が必要な音源に適した AudioBufferSourceNode を使用します。
※参考:https://liginc.co.jp/293921
出力を表す AudioDestinationNode へ接続し、keydown イベントのハンドラ内で再生メソッドを実行しましょう。

'use strict';

var
  ctx, sound, loadSound, playSound,
  random, onKeydown, charList, index, elChar, elCounter;

loadSound = function(sound) {
  var xml;
  xml = new XMLHttpRequest();
  xml.responseType = 'arraybuffer';
  xml.open('GET', sound.url, true);
  xml.onload = function() {
    ctx.decodeAudioData(
      xml.response,
      function(data) {
        sound.data = data;
      },
      function(e) {
        alert(e.err);
      }
    );
  };
  xml.send();
};
playSound = function(sound) {
  var bufferSource;
  if (!sound.data) {
    return;
  }
  bufferSource = ctx.createBufferSource();
  bufferSource.buffer = sound.data;
  bufferSource.connect(ctx.destination);
  bufferSource.start(0);
};
window.AudioContext = window.AudioContext || window.webkitAudioContext;
ctx = new AudioContext();
sound = {url: '../media/gun.mp3', data: null};
loadSound(sound);

// ...省略...

onKeydown = function(e){
  playSound(sound);
  if (String.fromCharCode(e.keyCode) === charList[index]) {
    index = random(index, charList.length);
    elChar.textContent = charList[index];
    elCounter.textContent = parseInt(elCounter.textContent, 10) + 1;
  } else {
    elCounter.textContent = 0;
  }
};

// ...省略...

 
DEMO
GitHub

爽快感が出ましたね。

表示された文字と異なる文字がタイピングされた場合は別の音を鳴らす

タイプミスしたことがわかるよう、表示と異なる文字がタイピングされた場合には別の音源を鳴らしましょう。
ちょっと間抜けな、こちらの音源を使用させていただくことにします。

作品名 物音13
音源ファイル http://maoudamashii.jokersounds.com/archives/se_maoudamashii_se_sound13.html
作者名 魔王魂

 
こちらも同じく AudioBufferSourceNode を使用します。

'use strict';

var
  ctx, soundList, loadSound, playSound,
  random, onKeydown, charList, index, elChar, elCounter;

// ...省略...

window.AudioContext = window.AudioContext || window.webkitAudioContext;
ctx = new AudioContext();
soundList = [
  {url: '../media/miss.mp3', data: null},
  {url: '../media/gun.mp3', data: null}
];
soundList.forEach(loadSound);

// ...省略...

onKeydown = function(e){
  if (String.fromCharCode(e.keyCode) === charList[index]) {
    playSound(soundList[1]);
    index = random(index, charList.length);
    elChar.textContent = charList[index];
    elCounter.textContent = parseInt(elCounter.textContent, 10) + 1;
  } else {
    playSound(soundList[0]);
    elCounter.textContent = 0;
  }
};

// ...省略...

 
DEMO
GitHub

使用する音源をランダムに切り替える

さらにこれらの音源も使用させていただき、音の種類に幅をもたせましょう。

作品名 戦闘12
音源ファイル http://maoudamashii.jokersounds.com/archives/se_maoudamashii_battle12.html
作者名 魔王魂

 

作品名 戦闘17
音源ファイル http://maoudamashii.jokersounds.com/archives/se_maoudamashii_battle17.html
作者名 魔王魂

 

'use strict';

var
  ctx, soundList, loadSound, playSound,
  random, onKeydown, charList, index, elChar, elCounter;

// ...省略...

window.AudioContext = window.AudioContext || window.webkitAudioContext;
ctx = new AudioContext();
soundList = [
  {url: '../media/miss.mp3', data: null},
  {url: '../media/gun.mp3', data: null},
  {url: '../media/beat.mp3', data: null},
  {url: '../media/slash.mp3', data: null}
];
soundList.forEach(loadSound);

// ...省略...

onKeydown = function(e){
  if (String.fromCharCode(e.keyCode) === charList[index]) {
    playSound(soundList[Math.floor(Math.random() * 3) + 1]);
    index = random(index, charList.length);
    elChar.textContent = charList[index];
    elCounter.textContent = parseInt(elCounter.textContent, 10) + 1;
  } else {
    playSound(soundList[0]);
    elCounter.textContent = 0;
  }
};

// ...省略...

 
DEMO
GitHub

完成です!

まとめ

今回は WebAudioAPI 実用の一例ということで、仕組みの部分はライトなものになっていますが、もちろん、本格的なゲームでも同様に利用が可能です。

これによって、僕のブラインドタッチがどこまで高速化するのか。楽しみです。

ではまた。つっちーでした。3分で飽きる自信ある。

LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。

Webサイト制作の実績・料金を見る

この記事のシェア数

つっちー
つっちー フロントエンドエンジニア / 土屋 大輔

フロントエンドエンジニアのつっちーです。 作曲してたらエンジニアになってました。 地図が好きで、一日中眺めていられます。 推しマップは路線地図。

このメンバーの記事をもっと読む
それいけ!フロントエンド | 213 articles
デザイン力×グローバルな開発体制でDXをトータル支援
お問い合わせ 会社概要DL