第5章 見た目と音のつけ方/3 本目 / 全 5/約25分

キャラに声を持たせる

こより

口の動く長さは、声の長さから決めてみて。台詞を差し替えても、ひとりでに合うようになるよ。

案内役 こより
この術でできるようになること

キャラが喋る

画像は押すと原寸で開きます。

なぜ要るか

4-3で口パクを付けました。あのとき、こう書きました。

音声がまだ無いので、実は口の動きが合っているかを判定できません。
5章で声を付けると「合っていない」がはっきり分かるようになります。

その答え合わせをします。

そして声は、この章でいちばん効きます。効果音は手応え、BGMは温度。声は「そこに誰かいる」を作ります。

使う道具: ElevenLabs

AIプロンプト

この術の作業をAIへ任せるときは、これをそのまま貼ってください。

react() に3つ目の引数で音声ファイル名を渡せるようにして。渡されたときだけ再生して、口パクは秒数の指定をやめて音声の長さに合わせて。呼び出し側は音声を渡すところだけ足して、他は変えないで。

react() の部分は、4-6で作った反応の出口の名前に置き換えてください。

台詞は開始と決着だけ作ります

増やしたくなりますが、場面ごとに1本ずつで足ります。

場面 台詞
開始 「いざ、勝負!」
手番のあと 「そこに置いたか……。」(同じ的なら「おっと、同じ的でぶつかったね。」)
決着(勝ち・負け・引き分け) 「わたしの勝ち!」「まいった。次は勝つよ。」「いい勝負だったね。」

長さは 1〜3秒です。実際にこのゲームで使っている声を測ると、1本 1.0〜2.8秒、ファイルは18〜46KBでした。

長い台詞は入れません。 遊んでいる最中に2秒以上黙って待たされるのは、それだけでストレスになります。

口パクの秒数を手で書かない

ここが今回いちばん大事です。

4-3の口パクは talk(900) のように秒数を手で渡していました。 声を入れたら、これはやめます。

音の長さから決めます。

var voice = new Audio();
voice.volume = 0.9;         // 声はBGMより大きく。聞かせたい音だから

function speak(file) {
  if (!soundOn) return;
  voice.src = 'assets/voice/' + file;
  voice.currentTime = 0;
  voice.play().catch(function () { /* 声が無くてもゲームは止めない */ });
  voice.addEventListener('loadedmetadata', function once() {
    voice.removeEventListener('loadedmetadata', once);
    talk((voice.duration || 1.5) * 1000);   // 秒数を自分で書かない
  });
}

voice.currentTime = 0 は再生位置を先頭へ戻す指定です。loadedmetadata は、その音の長さなどの基本情報を読み終わったときに呼ばれる合図で、voice.duration に長さ(秒)が入ります。取れた長さを1000倍してミリ秒にして、4-3の talk() へ渡しています。中で removeEventListener して登録を外しているのは、台詞を差し替えるたびに合図が積み重なるのを防ぐためです。

loadedmetadata を待ってから talk() を呼んでいます。 読み込みが終わるまで voice.duration は取れないからです(下のつまずきどころ参照)。

理由は単純です。台詞を差し替えるたびに秒数を直すのは、必ず忘れます。 音の長さを見に行けば、差し替えても自動で合います。

念のため、鳴り終わりでも口を閉じます。

voice.addEventListener('ended', function () {
  talking = false;
  clearInterval(talkTimer);
  el('charaImg').src = 'assets/' + baseFace;
});

ended は、音が鳴り終わったときに呼ばれる合図です。そこで口パクの繰り返しを止めて、ふだんの顔へ戻しています。これで口が開いたまま止まる事故が防げます。

やってみる

  1. 声を作る

    ElevenLabs で、短い台詞を1本作ります。頼むときに決めるのは3つです。

    声質: 若い女性、少し高め、快活
    台詞: 「わたしの勝ち!」
    長さ: 2秒以内
    

    台詞の文を入れると、その声で読み上げた音声ファイルが出てきます。それを保存して使います。

    キャラに合う声を1つ決めたら、以降は同じ声で作ります。 台詞ごとに声が変わると別人になります。ElevenLabs なら、最初に選んだ声のIDを控えておいてください。

  2. mp3 としてフォルダに置く

    assets/voice/kachi.mp3
    
  3. 鳴らして、口パクをつなぐ

    4-6で作った react() の中を1行変えるだけです。呼び出し側は1箇所も触りません。

    受け取る値を1つ増やします。関数の丸括弧の中に並んでいる kaowords が引数(関数へ渡す値)で、ここへ koe を足しました。渡されたときだけ声を鳴らすので、声のまだ無い場面はこれまでどおり動きます。

    function react(kao, words, koe) {
      stopKoe(); // 新しい台詞が始まったら、前の声と口パクは止める
      baseFace = KAO[kao] || KAO.guide;
      el('charaImg').src = 'assets/' + baseFace;
      el('serifu').textContent = words || '';
      if (koe) speak(koe);      // talk(900) をやめて、声の長さに口を合わせる
    }
    
    claude -p "react() に3つ目の引数で音声ファイル名を渡せるようにして。渡されたときだけ再生して、口パクは秒数の指定をやめて音声の長さに合わせて。呼び出し側は音声を渡すところだけ足して、他は変えないで。"
    

    出口を1つにしておいた効果がここで出ます。 4-6で react() にまとめていなければ、声を足すために全部の反応箇所を書き換えることになっていました。

  4. 鳴らして確かめる

    open index.html
    

    「音を出す」を押してから、1手置いてみてください。声が鳴って、その間だけ口が動きます。

    鳴っている間の口がこれです。

    ゲーム画面。声が鳴っている間、上部のこよりの口が開いている

    僕の環境では、この声の長さ 2.64秒、音量 0.9、鳴り終わりで口が閉じるところまで確認しました。

  5. 保存する

    git add .
    git commit -m "キャラに声を持たせ、口パクを声の長さに合わせた"
    git push
    

ここで口パクの粗が見えます

声を入れると、たぶんこう感じます。「口の動きが雑だ」

正常です。4-3の口パクは0.15秒ごとに開閉するだけで、言葉の中身を見ていません。 「あ」でも「ん」でも同じ動きをします。

対処は3段階あります。上から順に、費用対効果が高い順です。

やり方 効果
口パクを少し遅くする(0.15秒 → 0.18秒くらい) 落ち着いて見える。数字1つ
語尾で口を閉じる 言い終わった感じが出る
音の大きさに合わせて口を開く かなり合う。ただし実装が増える

最初は1番目で十分です。 3番目は、音を細かく分析して口の開き具合を決める作りになります。効果は高いですが、そこに時間を使うより台詞を1本増やした方が喜ばれます。

場面ごとに分けておくと後が楽です

僕のゲームでは、声をこう分けて置いています。

voices/
  stage-start/      ステージ開始
  gameplay-talk/    遊んでいる間の雑談
  versus/           対戦の開始
  chapter-1/ 2/ 3/  章ごとの語り

最初から全部作る必要はありません。置き場の形だけ決めておくと、増やすときに迷いません。

そして雑談用の声は、同じ場面で複数本用意してランダムに選ぶのが効きます。同じ台詞が2回続くと、急に機械に戻ります。

function pickVoice(list) { return list[Math.floor(Math.random() * list.length)]; }

渡した並びの中から1本をでたらめに選んで返すだけの関数です。同じ場面でも毎回同じ台詞にならないので、それだけで生っぽくなります。

つまずきどころ

voice.durationNaN になる

NaN は「数として読めない」ことを表す値です。音の情報をまだ読み込んでいないと、長さが取れずにこれになります。上のコードで || 1.5 を入れているのはこのためです。確実にやるなら、読み込み後に測ります。

voice.addEventListener('loadedmetadata', function () {
  console.log('長さ', voice.duration);
});

口パクが声より長く/短く続く

voice.duration を使わず秒数を手で書いていないか見てください。差し替え後に忘れるのは、ほぼこれです。

声が重なる

前の声が鳴り終わる前に次を鳴らすと重なります。このゲームは新しい台詞を優先して、前の声を止める方を選びました。react() の頭で stopKoe()(声を止めて口パクの繰り返しも消す)を呼んでいます。案内やリアクションは、いま起きたことを言う方が大事だからです。

声だけ大きすぎる/小さすぎる

声は聞かせたい音なので大きめが正しいです。 僕は 0.9 にしています。BGM 0.28 との差が大きく見えますが、実際に遊ぶとこれでちょうど台詞が通ります。

キャラのイメージと合わない

声質から選び直してください。台詞を変えるより声を変えた方が効きます。 ここは妥協すると毎回気になり続けます。

自分の声を使いたい

できます。クローン音声を作れば、自分の声でキャラを喋らせられます。僕は動画講義でこれを使っています。ただし最初の1本では要りません。 既成の声で十分に成立します。

公開して大丈夫か不安になる

商用利用の可否は、契約しているプランで変わります。公開する予定があるなら先に確認してください。 声は絵より条件が細かいので、ここは面倒でも読む価値があります。

Windows の場合

コードは同じです。open index.html の代わりに start index.html を使ってください。

次の一歩

負けたときの声を1回聞いてから、口パクの間隔 180150 に変えてもう1回聞いてみてください。

速くしただけで、せわしなく見えます(確かめたら 180 へ戻してください)。数字1つで「合っている感じ」が変わることが分かると、どこを触ればいいか掴めます。

音は3つ揃いました。次は絵の側の演出をつけて、当たった瞬間を気持ちよくします!

ここまでの三つの的

この講座は、見本ゲーム「三つの的」を術ごとにすこしずつ育てながら進みます。下にあるのはこの術を終えた時点の実物です。そのまま遊べます。

別の画面でこのゲームを開く

術の一覧へ戻る

メルマガ(無料)忖度なしのAI事情を、メールでお届けします。ご登録の方に、プレゼントをお渡ししています