第4章 キャラクターの作り方/6 本目 / 全 6/約25分

キャラをゲーム画面に立たせる

こより

画面に置くだけでは飾りのまま。置いた札へ一言返してくれたら、はじめて相手がそこにいる!

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

ゲーム内にキャラがいる

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

なぜ要るか

ここまでで、動くキャラができました。でもまだキャラとゲームが別々です。

この術でつなぎます。そして大事なのは、置くだけでは「いる」ことにならないという点です。

画面に絵が貼ってあるだけなら、それは飾りです。こちらの手に反応して初めて「相手がいる」と感じます。 今回やるのはそこまでです。

使う道具: なし(ここまでで作ったものだけ)

AIプロンプト

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

index.html にキャラを立たせて、こちらの手に反応させて。

1) タイトルの下、相手の手札の上にキャラの絵を置く。幅は240px。
   絵には、どんな相手なのかが伝わる alt を付ける。
2) 表情の差し替えとセリフ表示をまとめた react(表情, 言葉) を1つ作って、
   反応はすべてこの関数から出す。呼び出し側には書かない。
   表情は ふだん / 驚き / 勝ち / 負け の4つ。
3) reveal() の最後で、自分と相手が同じ的にぶつかったかどうかで一言を変える。
   finish() で、勝ち・負け・引き分けごとに表情と一言を出す。

反応はこの3つだけにして、増やさないで。それ以外は変えないで。

表情の絵のファイル名は、自分のキャラのものに置き換えてください。

どこに置くかは、もう決まっています

3章で作った画面は、上から順にこうなっています。

タイトル
相手の手札
的が3つ(上が相手の札、下が自分の札)
自分の手札

キャラは「相手の手札」の上に置きます。 迷う必要はありません。

理由は、画面の上が相手の陣地だと決まっているからです。そこにキャラを置けば、説明なしで「この手札はこの忍者のものだ」と伝わります。

置いた画面がこれです。

ゲーム画面。上部に忍者の少女が立ち、その下に相手の手札、的3つ、自分の手札が並んでいる

透過しておいた効果がここで出ます

この画面をよく見てください。キャラの周りに四角い枠が見えません。

4-1で背景を透過したからです。透明な部分にはゲームの背景がそのまま出るので、置くだけで馴染みます。

そして、ここが透過にしておいた本当の価値です。背景を変えても、そのまま使えます。

いまのゲームの背景は #101820 の暗い色です。もし6章で明るい背景に変えたくなったら、透過されていればキャラは1枚も作り直しません。 背景色に合わせて作っていたら、その1色専用の絵なので全部作り直しです。

さらに、この絵は他のゲームにも持っていけます。 8章で公開したあと、別の作品で同じキャラを使うときにも効きます。

透過に払った工程1つが、ここから先ずっと効き続けます。

やってみる

  1. 相手の手札の上に置く

    <h1>三つの的</h1>
    
    <div class="chara" id="chara">
      <img id="charaImg" src="assets/koyori-guide.png"
           alt="白と藍の忍装束を着た、藍色の髪の少女。対戦相手のこより">
      <p class="fukidashi" id="serifu"></p>
    </div>
    
    <p class="label">相手の手札</p>
    

    alt は、絵の代わりに読まれる説明文です。絵が表示できなかったときに出るほか、画面を読み上げて使う人にはこの文が読まれます。p class="fukidashi" は、セリフを出すための空の場所です。中身は次の手順で入れます。

    alt を必ず書いてください。絵が読めない人にも相手がいることが伝わります。 ここは手を抜くところではありません。

  2. 反応の出口を1つにする

    ここが一番大事です。反応させる場所を増やす前に、反応そのものを1箇所にまとめます。

    var KAO = {
      guide: 'koyori-guide.png',
      odoroki: 'koyori-odoroki.png',
      kachi: 'koyori-kachi.png',
      make: 'koyori-make.png',
    };
    function react(kao, words) {
      el('charaImg').src = 'assets/' + KAO[kao];
      el('serifu').textContent = words || '';
    }
    

    KAO は、表情の呼び名と絵のファイル名の対応表です。react() はその表を引いて絵を差し替え、textContent でセリフの文字を入れ替えます。やっているのは、絵1枚とセリフ1行の書き換えだけです。

    これで、呼ぶ側は react('kachi', 'わたしの勝ち!') の1行だけで済みます。4-2で作った傾きも、4-3で作った口パクも、この関数の中から呼びます。 呼び出し側には書きません。

    出口を1つにしておかないと、あとで「口パクだけ止めたい」となったときに全箇所を探すことになります。AGENTS.md の「同じ処理を2箇所に書かない」が、そのまま効く場所です。

  3. 札を開いた瞬間に反応させる

    3-1で作った reveal() の最後に、2行足すだけです。

    if (myMove.target === cpuMove.target) {
      react('odoroki', 'おっと、同じ的でぶつかったね。');
    } else {
      react('guide', 'そこに置いたか……。');
    }
    

    reveal() は、伏せて置いた札を0.5秒後にひらく処理です。ひらいた直後は、自分と相手がどこに置いたかが確定した瞬間なので、反応を出すのにいちばん都合がいい場所になります。

    同じ的にぶつかったかどうかで、言うことを変えています。 ここが「見ている」感じの正体です。盤面のどこにも書いていない情報を、キャラが言葉にしています。

    相手の的がリードした場面がこれです。

    ゲーム画面。上部の忍者の少女が両手を上げて喜び、一の的はもらったと言っている

    この一言があるだけで、盤面の数字が「事件」になります。

  4. 決着で締める

    finish() は、手札を置き切ったときに勝敗を出す処理です。その中で、勝敗ごとに1行です。

    if (myTargets > cpuTargets) {
      react('make', 'まいった。次は勝つよ。');
    } else if (cpuTargets > myTargets) {
      react('kachi', 'わたしの勝ち!');
    } else {
      react('guide', 'いい勝負だったね。');
    }
    

    自分が勝ったときに、相手が悔しがる。 これが決着の手応えを作ります。勝敗の文字だけでは出ない部分です。

  5. 3回遊ぶ

    open index.html
    

    判定はこれだけです。相手がいる感じがするか。

    していなければ、反応が足りません。次を読んでください。

  6. 保存する

    git add .
    git commit -m "キャラをゲーム画面に立たせて、勝敗に反応させた"
    git push
    

反応は「3つまで」にします

僕が最初にやった失敗です。全部の出来事に反応させたら、うるさくなりました。

札を選んだら喋る、勝ったら喋る、負けたら喋る、引き分けでも喋る。常に動いているキャラは、逆に無視されます。

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

出来事 反応
札を開いた 一言だけ。同じ的にぶつかったかで変える
相手が的を取った 喜ぶ顔に変える
決着 表情と一言で締める

1手ごとに大げさに動かさないのが要点です。開いた瞬間の一言は短く、決着だけ強く。強弱があるから、決着が効きます。

ファイルが増えました

3-2で「1ファイルで動かす」と決めました。いまはこうです。

index.html
assets/koyori-guide.png
assets/koyori-odoroki.png
assets/koyori-kachi.png
assets/koyori-make.png

表情の数だけ増えます。 これは想定どおりの増え方です。

大事なのは、まだサーバーが要らないことです。このフォルダごと置けば、ダブルクリックで動きます。人に渡すときはフォルダごと送ればいい、それだけの違いです。

8章で公開するときも、このフォルダを置くだけで済みます。外部から何も読み込んでいないので、公開が一番簡単な形のままです。

3Dモデルを作った人はどうするか

4-4と4-5で3Dを作った人向けです。いま作った1ファイル構成に、3Dをそのまま入れることはできません。

3Dを画面に出すには、ブラウザで3Dを描くための部品(three.js など)が必要です。それを読み込むと「外部のものを読まない」という前提が崩れます。3-2で避けると決めたところです。

選べる道は2つです。

やり方 向いている場合
3Dモデルを画像に書き出して、この術と同じ仕組みで置く 最初の1本を完成させたい
three.js を入れて3Dのまま出す 3Dで動かすことが目的

僕は前者を推します。 Blenderで正面・斜め・横の3方向を書き出せば、それは差分画像です。ここまで作った仕組み(差し替えと傾き)が全部そのまま使えます。

3Dのまま出すのは、この講座の枠を1つ超えます。まず1本完成させて、次の作品で挑んでください。

つまずきどころ

キャラが大きすぎて手札が見えない

.charawidth を下げてください。ただし4-2で書いたとおり、小さくすると傾きが見えなくなります。 240pxあたりが折り合いです。

キャラの周りに四角い枠が見える

透過できていません。背景が単色で残っています。4-1のキー抜きをやり直してください。

キャラの輪郭に緑や白の縁が見える

透過は効いているものの、縁に色が残っています。4-1で書いたとおり、単純な色置換で抜くとこうなります。soft-matte と despill が入った処理で抜き直してください。

スマホで縦に長くなりすぎる

画面幅に合わせます。

.chara { width: min(240px, 45vw); }

vw は画面の横幅を100とした単位で、45vwなら画面幅の45%です。min() は小さい方を採るので、広い画面では240px、狭い画面では画面幅に合わせた幅になります。

キャラが動きすぎてうるさい

反応を減らしてください。迷ったら1手ごとの一言を消すのが効きます。決着だけ残せば、うるささは消えて手応えは残ります。

claude -p "index.html のキャラの反応が多すぎるので、札を開いた直後の一言だけやめて、決着のときだけ反応するようにして。react() の呼び出しを消すだけにして、react() 自体は残して。"

反応しているのか分からない

表情の差が小さいです。普段の顔と、決着の顔が似すぎていないかを見てください。似ているなら、決着用にもっと振り切った絵を1枚足すのが早いです。react() の中から呼ぶ faceTo の角度を上げるのも効きます。

Windows の場合

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

次の一歩

誰かに1回遊んでもらってください。 そして相手の顔を見てください。

キャラが反応した瞬間に笑うか、何も起きないか。そこが「いる」と「貼ってある」の差です。 反応が弱ければ、上の3つを調整してください。

これで4章は終わりです。数字だけだった画面に、息をして、まばたきをして、勝ったら黙って上を向く相手が立ちました。次の章では音を付けて、手触りを上げていきます!

ここまでの三つの的

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

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

術の一覧へ戻る

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