なぜ要るか
前の術で顔が向くようになりました。でも、まだ目が一度も閉じません。
これは想像以上に効きます。まばたきをしないものは、生き物に見えません。 逆に言えば、まばたき1つ足すだけで「生きている」に切り替わります。
やり方は驚くほど単純です。目を閉じた絵を1枚用意して、0.1秒だけ差し替える。 それだけです。
使う道具: Codex の画像生成
AIプロンプト
この術の作業をAIへ任せるときは、これをそのまま貼ってください。
参照画像: char.png
参照画像のキャラクター(口寄せ使いの少女忍者こより)の顔ちがいを作る。
参照の造形と配色を変えない。
共通仕様(3枚すべて共通。顔の表情以外は同一の絵にする):
- 正面向き、全身、持ち物は巻物だけ
- flat solid #ff00ff background, no gradient, no shadow, no glow, no outline
- clean bold outlines, flat cel shading, chunky shapes, no fine loose hair strands
- 被写体はキャンバスの縁に接しない
- サイズ 1024x1024、文字は入れない
3枚作る。
1) char-magenta.png ふだんの顔。目を開け、口を閉じている
2) char-me-magenta.png 1と同一の絵で、目だけ閉じている
3) char-kuchi-magenta.png 1と同一の絵で、口だけ小さく開けている
キャラの説明の部分は、自分のキャラのものに置き換えてください。
差分は2枚だけ用意します
必要なのはこの2枚です。
| ファイル | 何の絵 | 使いどころ |
|---|---|---|
| char-me.png | 目を閉じた顔 | まばたき |
| char-kuchi.png | 口を開けた顔 | しゃべっているとき |
3枚目は要りません。 この2枚と元絵の計3枚で、まばたきも口パクも成立します。
実際に作った3枚がこれです。
昔のゲームで「立ち絵の差分」と呼ばれていたものと同じ考え方です。全部を描き直さず、変わる部分だけ別の絵にする。
3枚は「1回でまとめて」頼みます
先に一番大事なことを書きます。3枚を別々に頼むと揃いません。
1枚できたあとで「同じ絵で目だけ閉じて」と追加で頼むと、髪の模様や目の大きさが微妙に変わります。生成AIは部分修正をしているのではなく、毎回描き直しているからです。
対策は単純で、最初から3枚まとめて1回で頼むことです。同じ指示の中で作られるので、揃う確率が段違いに上がります。
僕はこの順番を逆にして一度失敗しました。1枚ずつ足していったら、しゃべるたびに別人になりました。
やってみる
3枚まとめて作らせる
4-1と同じキー色(マゼンタ)の背景で作ります。あとで一緒に透過するためです。1枚目の指定は4-1と同じで、参照画像に4-1の完成品を渡して造形を固定します。
cd my-first-game codex exec "参照画像: char.png 参照画像のキャラクター(口寄せ使いの少女忍者こより)の顔ちがいを作る。 参照の造形と配色を変えない。 共通仕様(3枚すべて共通。顔の表情以外は同一の絵にする): - 正面向き、全身、持ち物は巻物だけ - flat solid #ff00ff background, no gradient, no shadow, no glow, no outline - clean bold outlines, flat cel shading, chunky shapes, no fine loose hair strands - 被写体はキャンバスの縁に接しない - サイズ 1024x1024、文字は入れない 3枚作る。 1) char-magenta.png ふだんの顔。目を開け、口を閉じている 2) char-me-magenta.png 1と同一の絵で、目だけ閉じている 3) char-kuchi-magenta.png 1と同一の絵で、口だけ小さく開けている"「顔の表情以外は同一の絵にする」が効きます。 これを入れると、体の位置まで揃います。
3枚を透過する
4-1と同じやり方です。3枚に同じ処理をかけます。
for n in char char-me char-kuchi; do python3 keyout.py --input $n-magenta.png --out $n.png --max-height 512 doneforは同じ処理を繰り返す書き方です。nの中身が char、char-me、char-kuchi と順に入れ替わり、3回同じ透過処理が走ります。終わると、背景が透明な3枚が並びます。3枚を同じ枠で切る
ここは飛ばさないでください。枚ごとに余白を自動で切り詰めると、位置がずれます。
透過した3枚は、キャラの位置がぴったり同じでなければいけません。切り詰めるなら、3枚まとめて同じ枠で切ります。
for n in char char-me char-kuchi; do magick $n.png -crop 400x400+43+62 +repage $n.png done-crop 400x400+43+62は、左から43px・上から62pxの位置を起点に、400px四方を切り出すという指定です。+repageは切り出したあとの余りの情報を捨てて、絵の大きさを400x400へ揃えます。数字は自分の絵に合わせて決めます。3枚とも同じ数字を使うことだけが条件です。
サイズも確認しておきます。
sips -g pixelWidth char.png char-me.png char-kuchi.png3枚の横幅を表示するコマンドです。同じ数字が3つ並ぶのが正解です。
サイズや位置が違うと、差し替えた瞬間に顔がガタッと動きます。 一番気づきにくい不具合なので、ここで潰します。
ちなみに生成の生データは、僕の場合 1024 と頼んで 1254px で出てきました。指定どおりのサイズで出てこないことがある前提でいてください。
まばたきを付ける
var FACE = { open: 'char.png', shut: 'char-me.png', mouth: 'char-kuchi.png' }; var talking = false; function blink() { if (!talking) { charaImg.src = FACE.shut; setTimeout(function () { if (!talking) charaImg.src = FACE.open; }, 120); } setTimeout(blink, 2000 + Math.random() * 3000); } setTimeout(blink, 1500);やっているのは差し替えです。
charaImg.srcに別のファイル名を入れると、そこに表示される絵が入れ替わります。setTimeoutは「指定した時間だけ待ってから1回実行する」仕掛けなので、閉じた絵にして120ミリ秒後に開いた絵へ戻す、を繰り返しています。最後の行が自分自身をまた予約しているので、まばたきは止まりません。数字は2つだけです。
- 120 ミリ秒 = 目を閉じている時間
- 2000〜5000 ミリ秒 = 次のまばたきまでの間隔
間隔をランダムにするのが要点です。 きっちり3秒ごとに閉じると、生き物ではなく機械に見えます。
口パクを付ける
function talk(ms) { talking = true; var openMouth = false; var timer = setInterval(function () { openMouth = !openMouth; charaImg.src = openMouth ? FACE.mouth : FACE.open; }, 150); setTimeout(function () { clearInterval(timer); talking = false; charaImg.src = FACE.open; }, ms); }setIntervalは「指定した間隔で繰り返す」仕掛けで、clearIntervalでその繰り返しを止めます。talkingは、いましゃべっている最中かどうかを覚えておくための目印です。talk(1200)で1.2秒しゃべります。150ミリ秒ごとに口の開閉を繰り返すだけです。呼吸を足す
これは絵が要りません。CSSだけです。効果は3つの中でいちばん高いです。
.chara { animation: kokyu 4s ease-in-out infinite; } @keyframes kokyu { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }@keyframesは動きの型です。0%から100%までを1回分として、途中でどう変わるかを書きます。ここでは50%の時点だけ5px上へ持ち上げているので、上がって下がる動きになります。infiniteはそれを止めずに繰り返す指定です。4秒かけて5px上下するだけです。止まっているはずのキャラが、静止画に見えなくなります。
開いて眺める
open index.html何も操作せず、10秒ほど見てください。勝手にまばたきします。
ゲーム画面の中で目を閉じた瞬間がこれです。
保存する
git add . git commit -m "まばたきと口パクと呼吸をつけた" git push
それでもズレたときの考え方
まとめて頼んでも、細部は完全一致しないことがあります。目の大きさが少し違う、口の位置が1pxずれる、といった程度です。
対処を、効く順に書きます。
| 対処 | 効果 |
|---|---|
| 気にしない | まばたきは0.1秒。人間はその差に気づけない |
| 3枚まとめてもう一度作る | 揃った組が出るまで引く。1枚だけ足すより速い |
| 画像編集で目だけ差し替える | 完全に揃うが手間がかかる |
1番目を推します。 実際に動かしてみると、まばたきの0.1秒で細部が違うことは、まったく分かりません。
こよりでも実際に起きました。口を開けた差分が、口を頬の位置に描いて返ってきたのです。しゃべるたびに口が右へ飛ぶので、開いた口だけを切り出して正しい位置へ貼り直しました(3番目のやり方)。位置のズレは、絵の中の口の位置がズレていても起きます。 差し替えの仕組みを疑う前に、絵そのものを拡大して見てください。
ただし口パクは要注意です。 1.2秒間ずっと開閉するので、目の大きさが違うと「しゃべる時だけ別人」に見えます。口の差分だけは、少し粘って作り直す価値があります。
AIに「変更部分だけ合成して」と頼んではいけません
賢い解決を思いついたので試しました。「生成した絵から変わった部分だけを切り出して元絵に貼れ」と頼んだのです。
絵が壊れました。 顔の模様が消え、目のあった場所が黒く潰れた画像が出てきました。切り出す範囲の判断が雑になるためです。
素直に生成した3枚をそのまま使う方が、結果が良かったです。 凝った工程を足すと、そこが新しい壊れどころになります。
つまずきどころ
まばたきと口パクが喧嘩する
まばたきの最中に口パクが始まると、絵が飛び跳ねます。上のコードに talking という旗があるのはこのためです。しゃべっている間はまばたきを止めます。
旗を消して試してみると、どう壊れるかが分かります。1回見ておくと、以降のキャラでも同じ構造にできます。
別のタブに切り替えたら止まった/遅くなった
これは正常です。 ブラウザは、見えていないタブのタイマーを間引きます。僕の測定でも、裏に回した状態では150ミリ秒の口パクが500ミリ秒間隔になりました。
戻ってくれば元に戻ります。直す必要はありません。
まばたきが速すぎる/遅すぎる
120 を触ってください。80より短いと気づかれず、200を超えると眠そうになります。 キャラの性格に合わせて決めていいところです。
差し替えた瞬間に絵がチカッと光る
初回だけ起きます。その画像をまだ読み込んでいないためです。先に読ませておけば消えます。
[FACE.shut, FACE.mouth].forEach(function (src) { new Image().src = src; });
差分の2枚を、画面に出す前に読み込ませておくコードです。読み込み済みなら、差し替えは一瞬で終わります。
口パクが不自然
音声がまだ無いので、実は口の動きが合っているかを判定できません。 5章で声を付けると「合っていない」がはっきり分かるようになります。いまは開閉しているだけで十分です。
Windows の場合
sips は macOS のコマンドです。Windows ではサイズ確認をAIに頼んでください。
codex exec "char.png と char-me.png と char-kuchi.png の縦横サイズを教えて。"
for の書き方も違います。PowerShell ではこうです。
foreach ($n in "char","char-me","char-kuchi") {
python keyout.py --input "$n-magenta.png" --out "$n.png" --max-height 512
}
codex exec と keyout.py の使い方そのものは同じです。
次の一歩
まばたきの間隔を 2000 + Math.random() * 3000 から 3000 に変えて、比べてみてください。
固定にすると、驚くほど機械っぽくなります。ランダムが生き物らしさを作っていることが体感できます。この感覚は、5章の演出でもそのまま効きます。
これでキャラは息をして、まばたきをして、しゃべるようになりました。次はゲーム画面の中に、ちゃんと立たせます!
ここまでの三つの的
この講座は、見本ゲーム「三つの的」を術ごとにすこしずつ育てながら進みます。下にあるのはこの術を終えた時点の実物です。そのまま遊べます。

