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

演出で「気持ちよさ」を作る

こより

跳ねる、揺れる、光る。どれも0.3秒くらいで足りて、増やさない場所を決めるほど際立つよ。

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

当たった瞬間が気持ちいい

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

なぜ要るか

ここまでで音は揃いました。でも画面はまだ静かです。

勝っても負けても、文字が変わるだけです。同じ勝敗なのに、返ってくる情報が少ないと手触りが軽くなります。

この術でやるのは3つだけです。跳ねる・揺れる・光る。 それ以上は要りません。

使う道具: なし(CSSとJSだけ)

AIプロンプト

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

index.html に決着の演出を足して。守り切った的だけを少し持ち上げて金色の枠を付け、取られた的は薄くして沈める。動きは0.3秒前後。1手ごとの演出は増やさないで、決着のときだけにして。それ以外は変えないで。

「的」の部分は、自分の企画で勝敗が決まる場所の呼び名に置き換えてください。

演出は「時間」が9割です

先に結論を書きます。何を動かすかより、何秒で動かすかで決まります。

僕が使っている長さです。

演出 長さ
札が跳ねる 0.32秒
画面が揺れる 0.28秒
画面が光る 0.4秒

全部0.3秒前後です。 ここが要点です。

0.1秒だと気づかれません。1秒だと待たされます。 待たされる演出は、2回目から邪魔になります。ゲームは同じ動作を何十回も繰り返すので、ここでの0.5秒の差が積み上がります。

迷ったら0.3秒。 そこから短くする方向で調整してください。

やってみる

  1. 取った的を持ち上げる

    決着したとき、守り切った的だけを浮かせます。CSSはこれだけです。

    .target { transition: transform .32s, box-shadow .32s; }
    .target.won {
      transform: translateY(-8px);
      border-color: #ffd479; box-shadow: 0 0 18px rgba(255, 212, 121, .4);
    }
    .target.lost { opacity: .55; }
    

    transition は、見た目が変わったときにその差をなめらかに繋ぐ指定です。ここでは0.32秒かけて移り変わります。wonlost はクラスの名前で、JSからこれを付け外しすると、そのとき初めて見た目が動きます。クラスは、見た目の指定をまとめて名前で呼び出す仕掛けです。

    取った的を上げるだけでなく、取られた的を沈めます。 片方だけだと差が出ません。上げると下げるを対にすると、同じ動き幅で倍の差が出ます。

    実際の見た目がこれです。

    決着画面。守り切った2つの的が持ち上がって金色の枠が付き、取られた的は沈んでいる

    どの的を取ったかを、数字を読まずに分かる状態です。3つ並んだうちの2つが浮いている、それだけで勝ちが伝わります。

  2. 開いた札だけ動かす

    1手ごとの演出は、これ1つに絞ります。伏せた札がひらく瞬間です。

    @keyframes hiraku { 0% { transform: rotateY(90deg); } 100% { transform: rotateY(0); } }
    .card.hiraku { animation: hiraku .26s ease-out; }
    

    @keyframes で動きの型を決めて、animation でそれを再生します。横に90度倒れた状態から正面へ戻すので、札がめくれるように見えます。

    問題はどの札に付けるかです。盤の札は毎回まとめて描き直すので、全部に付けると過去の札まで踊り出します。

    fresh[e.key] = true;   // reveal() で、いま開いた札だけ印を付ける
    ...
    var cls = 'card mini' + (fresh[entry.key] ? ' hiraku' : '');
    ...
    fresh = {};            // render() の最後で印を消す
    

    「いま起きたことだけ」を持つ入れ物を1つ用意する。 これが、描き直す型の画面で演出を入れるときの定石です。付けたクラスを消して回る必要がなくなります。

  3. 負けたら画面を揺らす

    .shake { animation: yure .28s ease-in-out; }
    @keyframes yure {
      0%, 100% { transform: translateX(0); }
      25%      { transform: translateX(-6px); }
      75%      { transform: translateX(6px); }
    }
    
    function shake() {
      document.body.classList.remove('shake');
      void document.body.offsetWidth;
      document.body.classList.add('shake');
    }
    

    JSの側では、クラスを一度外してから付け直しています。真ん中の void document.body.offsetWidth は、その外した状態をブラウザにいったん確定させるための1行です。これが無いと、外してすぐ付け直したことをブラウザが「何も変わっていない」と判断して、2回目以降の揺れが再生されません。

    揺れ幅は6pxです。 20px揺らすと画面が壊れたように見えます。気づく最小で足ります。

  4. 決着で一瞬光らせる

    <div class="flash" id="flash"></div>
    
    .flash {
      position: fixed; inset: 0; pointer-events: none;
      background: #ffd479; opacity: 0;
    }
    .flash.on { animation: hikari .4s ease-out; }
    @keyframes hikari {
      0%   { opacity: .28; }
      100% { opacity: 0; }
    }
    

    position: fixedinset: 0 で、画面いっぱいに広がる板を1枚置いています。ふだんは opacity: 0 なので見えません。決着で on を付けたときだけ、金色がうっすら差してすぐ消えます。

    pointer-events: none を忘れないでください。 これが無いと、光の板が画面を覆って何も押せなくなります。

    光った瞬間がこれです。

    ゲーム画面全体が金色にほんのり染まり、2つの的を守ってあなたの勝ちですと出ている

    静止画だと強く見えますが、0.4秒で消えます。 止めた瞬間だけを切り出しているので、実際に遊ぶとほんの一瞬です。

  5. 音と同じタイミングで出す

    ここが最後の仕上げです。絵と音をずらさない。

    if (myTargets > cpuTargets) {
      text = myTargets + 'つの的を守って、あなたの勝ちです!';
      SE.win();                              // 音
      el('flash').classList.add('on');       // 絵
    }
    

    同じ場所で並べて書きます。 音を先に鳴らして絵を0.2秒遅らせる、のようなズレを作ると、それだけで安っぽくなります。

  6. 3回遊んで判定する

    open index.html
    

    「音を出す」を押してから、最後まで打ち切ってください。決着で、的が上下に分かれて、画面が一瞬光るか揺れるはずです。判定はこれだけです。2回目に「早く進みたい」と思ったら過剰です。

    1回目は誰でも面白いと感じます。2回目の感じ方が本当の答えです。

  7. 保存する

    git add .
    git commit -m "跳ね・揺れ・光の演出をつけた"
    git push
    

AIに頼むときの言い方

演出は「何を動かすか」より「何秒で」「どこだけ」が効きます。だから指示にもその2つを入れます。

claude -p "index.html に決着の演出を足して。守り切った的だけを少し持ち上げて金色の枠を付け、取られた的は薄くして沈める。動きは0.3秒前後。1手ごとの演出は増やさないで、決着のときだけにして。それ以外は変えないで。"

「1手ごとの演出は増やさないで」が要点です。 これを言わないと、親切に全部の操作へ演出が付いて返ってきます。演出は足し算ではなく配分なので、増やさない場所を先に指定します。

足すより「引く」方が効きます

僕の失敗を書きます。最初は全部の出来事に演出を付けました。

札を押したら跳ねる、勝ったら光る、負けたら揺れる、引き分けでも点滅する。結果、どれも目立たなくなりました。

演出は相対的です。常に何か動いていると、大事な瞬間が埋もれます。

いま入れているのはこれだけです。

出来事 演出
札をえらんだ 札が少し浮く 軽い音
的に置いた なし 紙の音
札がひらいた ひらいた札だけ回る 開く音
勝った 取った的が浮く+画面が光る
負けた 取られた的が沈む+画面が揺れる 低い音

的に置いた瞬間に絵の演出を入れていません。 ここを空けているから、0.5秒後にひらく瞬間が立ちます。

これは4-6でキャラの反応を3つに絞ったのと同じ考え方です。空白があるから、動きが見えます。

動きが苦手な人がいます

見落とされやすい話です。画面の揺れや点滅で気分が悪くなる人がいます。

パソコンやスマホには「動きを減らす」設定があって、こちらから読めます。1回入れておけば、以降のすべての作品で効きます。

@media (prefers-reduced-motion: reduce) {
  .card.win, .shake, .flash.on, .chara {
    animation: none;
  }
}

@media は条件を付けた指定で、条件に合う相手にだけ中身が適用されます。prefers-reduced-motion: reduce は「動きを減らす設定にしている人」を表す条件です。

設定した人にだけ演出が止まります。 ほかの人の見え方は変わりません。

3行で済むので、入れておいてください。8章の公開前チェックでも、ここは見る項目に入れています。

つまずきどころ

2回目からアニメーションが動かない

void el.offsetWidth が入っていません。上の手順2をもう一度見てください。これは必ず1回はまります。

揺らしたら画面のレイアウトが崩れた

transform ではなく marginleft で動かしていると崩れます。transform は場所を計算し直さないので崩れません。 動かすものは transform を使ってください。

光が消えずに残る

animation が終わったあとの状態が opacity: 0 になっているか確認してください。animation-fill-modeforwards にしていると、最後の状態で止まります。今回は止めたくないので指定しません。

何も押せなくなった

pointer-events: none が抜けています。光の板が画面全体を覆っているので、そのままだとクリックを全部吸い込みます。

演出が地味に感じる

数字を大きくする前に、音を確認してください。 演出が地味に感じるときは、たいてい音が足りていません。絵より音の方が効きます。

キャラの呼吸と喧嘩する

4-2で書いたとおり transform は1つしか持てません。呼吸を外側の要素、演出を内側の要素と分けてください。

Windows の場合

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

次の一歩

跳ねの長さを 0.32秒から1秒に変えて、5回遊んでみてください。

3回目くらいで邪魔になります。 その感覚が「演出は短く」の理由です。数字で説明されるより、1回体験した方が確実に身につきます。

これで5章は終わりです。押せば音が返り、曲が流れ、キャラが喋り、勝った瞬間に画面が動く。ここまでで「遊べるもの」から「触って気持ちいいもの」に変わりました。 次の章では中身を増やして、何度も遊べる形にしていきます!

ここまでの三つの的

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

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

術の一覧へ戻る

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