マガジン

ゲームのテンポに合わせて音楽の速さを変える:難しくなるほど速く

遊びをつくる

執筆にClaudeを使用しています。

振り子と色の異なる円を並べ、ゲームのリズムや間を表す立体作品。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

60秒の小さなゲームを、同じ人に3回遊んでもらう場面を考えてみます。1回目は音楽が一定の速さで流れ、2回目は60秒のあいだに少しずつ速くなり、3回目は無音です。ゲームの中身は3回とも同じで、的の出る間隔も数も変えません。変えるのは音楽のBPM(1分間の拍の数)だけです。 この記事が確かめるのは、音楽の速さだけを進行に連動させたとき、プレイヤーの緊張感は変わるのかという問いです。ゲームが難しくなる演出としてBPMを上げる手法はよく使われますが、「速い音楽」が緊張を生むのか、「難しいゲーム」が緊張を生んでいるのかは、混ざったままだとわかりません。ここでは難易度を固定して、音の効果だけを取り出す設計にします。 この記事には結果が載っていません。実験の設計と、手元で動く最小のコードと、結果の読み方の基準を示します。実際に遊んでもらって数字を集めるのは読者の側です。

この記事の目次
  1. 何を比べるのかを決める
  2. 準備するもの
  3. 手順
  4. 記録の取り方
  5. 結果の読み方
  6. 起こりやすい誤り
  7. 次の行動

何を比べるのかを決める

最初に、問いを「比べられる形」に直します。「緊張感が変わるか」のままでは測れません。次の3つの条件を用意します。

  • 条件A(固定):BPMを90で一定にします。
  • 条件B(上昇):BPMを90から150まで、60秒かけて連続的に上げます。
  • 条件C(無音):メトロノームの音もBGMも流しません。

Cを入れる理由は、「音楽があること自体」の影響を切り分けるためです。AとBだけを比べると、Bが緊張を生んだのか、Aが落ち着きを生んだのかが区別できません。

90から150という数字は、この実験のための仮の設計値です。上げ幅が約1.7倍あるので、速くなったことに気づけるはずという想定で置いています。気づかない程度の変化と、はっきり気づく変化では結果が変わる可能性があるため、後から上げ幅を変えた実験を追加できます。最初は1種類に絞ってください。

測りたいものは「緊張感」ですが、これは直接見えません。そこで、見える指標を3つ用意します。

  1. 主観の緊張度:遊んだ直後に1(落ち着いていた)から5(かなり張りつめていた)で答えてもらいます。
  2. 操作の乱れ:60秒あたりのミス数と、反応時間の変化を記録します。
  3. もう一度遊びたいか:1(遊びたくない)から5(すぐ遊びたい)で答えてもらいます。

緊張感は「高いほどよい」とは限りません。主観の緊張度が上がっても、もう一度遊びたい度が下がるなら、その速さは負担になっている可能性があります。この2つを分けて持っておくのが設計のポイントです。

準備するもの

必要なのは次の4つです。

  • 同じゲームを1本。落ちてくる的をキーで打つ、光った場所をクリックするなど、ルールが一分で伝わるもので十分です。条件ごとに的の出現間隔や速さを変えないことが最重要です。乱数を使う場合は、乱数の種を固定するか、出現のタイミングを配列に書き出して全条件で共通にします。
  • BPMを進行に連動させる音のコード。下の最小例を使います。
  • 記録用の紙か表計算シート。
  • 同じ機材と同じ音量設定。イヤホンかスピーカーかを統一し、音量を変えないでください。

BPMを進行に連動させる最小コードは次のとおりです。単独のHTMLとして保存すれば動きます。外部ライブラリは使いません。ブラウザは、ユーザーが操作する前の音の再生を制限していることが多いため、ボタンを押したときに音を開始する形にしています。

html
<!doctype html>
<meta charset="utf-8">
<button id="go">開始</button> <span id="info"></span>
<script>
const MODE = "ramp"; // "fixed" | "ramp" | "none"
const BASE = 90, TOP = 150, DURATION = 60; // 秒
let ctx, t0, next, beat = 0, timer;

function bpmAt(p) {
  if (MODE === "ramp") return BASE + (TOP - BASE) * p;
  return BASE;
}
function click(time, accent) {
  const o = ctx.createOscillator(), g = ctx.createGain();
  o.frequency.value = accent ? 880 : 660;
  g.gain.setValueAtTime(0.2, time);
  g.gain.exponentialRampToValueAtTime(0.001, time + 0.08);
  o.connect(g).connect(ctx.destination);
  o.start(time); o.stop(time + 0.1);
}
function schedule() {
  const elapsed = ctx.currentTime - t0;
  const p = Math.min(elapsed / DURATION, 1);
  while (next < ctx.currentTime + 0.2) {
    const q = Math.min((next - t0) / DURATION, 1);
    if (MODE !== "none") click(next, beat % 4 === 0);
    next += 60 / bpmAt(q);
    beat++;
  }
  info.textContent = "BPM " + Math.round(bpmAt(p)) + " / " + Math.floor(elapsed) + "秒";
  if (elapsed >= DURATION) { clearInterval(timer); info.textContent += " 終了"; }
}
go.onclick = () => {
  ctx = new AudioContext();
  t0 = ctx.currentTime + 0.1; next = t0; beat = 0;
  timer = setInterval(schedule, 25);
};
</script>

一番上の MODE を書き換えるだけで、条件A("fixed")、条件B("ramp")、条件C("none")を切り替えられます。このコードは音の側だけです。実際の実験では、自作ゲームの開始ボタンの中で同じ処理を呼び、ゲームの60秒と音の60秒を同時に始めてください。

仕組みのポイントは、次の拍の時刻を「その時点のBPM」で決めている点です。小節の頭でまとめて変えるのではなく、拍ごとに少しずつ間隔が縮むので、速度が階段状にならず連続的に上がります。階段状の変化と比べたい場合は、bpmAt の中で進行を区切って返す値を変えれば、条件Dとして追加できます。

手順

準備ができたら、次の順番で進めます。

  1. 遊ぶ人に説明する。ゲームのルールだけを伝えます。「音が速くなる」「緊張を測る」とは言わないでください。先に伝えると、その印象に答えが引っ張られます。
  2. 練習を1回。音なしで30秒ほど遊んでもらい、操作に慣れてもらいます。この回は記録しません。
  3. 本番を3条件、1人あたり各2回。合計6回です。1回60秒です。
  4. 順番を入れ替える。全員がA→B→Cの順だと、疲れや慣れが条件の差に見えてしまいます。人ごとに順番を変えてください。たとえば3人なら「A→B→C」「B→C→A」「C→A→B」と回します。人数が多ければ、6通りの並びすべてを使えます。
  5. 各回の直後に質問。主観の緊張度ともう一度遊びたい度を、1〜5で口頭か紙で答えてもらいます。
  6. 回の間に30秒以上の休憩。前の回の感覚を引きずらないためです。

人数の目安は、まず自分を含めて3〜5人です。少人数なので統計的に断言するのではなく、傾向をつかむ段階の実験と位置づけてください。同じ人に複数回遊んでもらうと個人差を減らせますが、慣れの影響が出やすくなる点は後述します。

記録の取り方

記録は1回ごとに1行です。次の列を用意します。

  • 参加者ID、内容:A、B、C…など、単位:匿名の記号
  • 条件、内容:固定 / 上昇 / 無音、単位:3種
  • 順番、内容:その人の何回目か、単位:1〜6
  • 緊張度、内容:直後に口頭で、単位:1〜5
  • 再挑戦したい度、内容:直後に口頭で、単位:1〜5
  • ミス数、内容:60秒間の合計、単位:回
  • 反応時間、内容:最初の20秒・中の20秒・最後の20秒の中央値、単位:ミリ秒

反応時間は、全体の平均ではなく3区間に分けて記録します。テンポが上がる条件では、後半に差が出るのかどうかを見たいからです。平均だけにすると、前半と後半の変化が消えます。中央値を使うのは、一度の大きな遅れに数字が引っ張られないようにするためです。

ミスと反応時間は、ゲーム側にコードを足せば自動で記録できます。入力のたびに performance.now() の値を配列に入れておき、60秒後に集計して画面に出す形が簡単です。手で数えると見落としが増えるので、可能なら自動にしてください。

主観の評価は、口頭で聞くときも毎回同じ言い回しにしてください。「どれくらい緊張しましたか」と「どれくらい焦りましたか」では、人によって答えが変わります。どちらを使うか決めたら最後まで固定します。

結果の読み方

データが集まったら、見る前に判断の基準を決めておきます。結果を見てから基準を決めると、都合のよい解釈に寄りやすいからです。たとえば次のような基準が考えられます。基準の数字は自分で決めてください。

  • 緊張度の平均が、BでAより1点以上高い人が、参加者の半数以上いるか。
  • その傾向が、各人の2回の本番で同じ向きに出ているか。
  • 反応時間の中央値が、Bの最後の20秒で、Aの最後の20秒より遅くなる人が多いか。

そのうえで、組み合わせごとに次のように読み分けます。

  • 緊張度が上がり、ミスも増える:速さが緊張とともに操作の乱れを生んでいる可能性があります。BPMの上げ幅を小さくした条件を追加して、乱れが出始める境目を探します。
  • 緊張度が上がり、ミスは変わらない:操作の質を落とさずに雰囲気だけが変わっている可能性があります。この場合は、演出としてBPM連動が使いやすいと考えられます。
  • 緊張度は変わらず、ミスが増える:本人は気づかないうちに、テンポにつられて操作を急いでいる可能性があります。反応時間の区間ごとの変化も合わせて見ます。
  • どちらも変わらない:上げ幅が小さすぎるか、ゲームの注意がテンポに向きにくい作りかもしれません。上げ幅を広げた条件で再実験するか、音の質を変えて試します。
  • AもBもCに比べて緊張度が高い:緊張を生んでいるのはテンポ変化ではなく、音があること自体かもしれません。

「再挑戦したい度」が下がっている場合は、緊張度が高いことが負担になっているサインとして別に扱います。緊張が高く、再挑戦したい度も高いなら、ゲームにとって望ましい緊張の可能性があります。

起こりやすい誤り

この実験で結果を濁らせやすいのは、次の点です。

  • ゲームの難易度が条件ごとに変わっている:的の速さや数をBPMに合わせて変えてしまうと、音の効果と難易度の効果が混ざります。この実験では、ゲーム側は完全に固定します。難易度とBPMの両方を連動させた条件は、音だけの効果を確かめたあとの次の段階として別に行ってください。
  • 順番の効果:最後に遊ぶ回は疲れや慣れの影響を受けます。順番を入れ替えて、条件と順番が一対一にならないようにします。
  • 音量や音色が条件で違う:クリック音のように単純な音と、メロディのあるBGMでは、同じBPMでも印象が変わります。AとBは同じ音色にしてください。曲を使うなら、再生速度を変えたときに音程まで変わる方式かどうかも確認します。
  • 音が出ない・遅れる:ブラウザは操作前の音の再生を止めることがあります。開始ボタンの押下から音を始める形にして、実験前に1回は鳴ることを確かめてください。Bluetooth機器は音が遅れて届くことがあるため、有線で統一すると安全です。
  • 質問で答えを誘導する:「音が速くなって焦りましたか」と聞くと、焦ったと答えやすくなります。質問は「どれくらい緊張しましたか」のように中立にします。
  • 少人数の結果を一般化する:数人の傾向は、あなたのゲームと、その場の人たちに限った話です。別のジャンルや別の遊ぶ人にそのまま当てはまるとは限りません。

次の行動

まず上のコードを保存して、MODE を3通りに書き換えながら、BPMの変化が耳で聞き分けられるかを確かめてください。聞き分けられたら、自作ゲームの開始処理に組み込み、同じ60秒の中で記録を取る準備に進みます。

完成したゲームは、直下に index.html を置いたHTMLまたはZIPとして、アソバースへ投稿できます。外部ライブラリやサーバー用コードを使わない構成なので、このコードの延長でも条件を満たせます。投稿時のサイズや回数などの上限は、投稿画面の案内で確認してください。