経緯
学生時代、顔を判定するAPIの存在を知ったときに「これを使えば、いろいろなゲームや体験に活かせるのでは」とワクワクしたことを覚えています。
ただ、当時は画像をクラウドのサービスに送って結果を受け取る必要があり、リアルタイムに遊べるようなものをつくるのは難しそうだと感じて、そのまま月日が経ってしまいました。
最近ではブラウザ上でも機械学習のモデルが十分に動くようになり、クラウドサービスに接続しなくてもローカル動作だけで顔の判定ができそうです。
以前に書いた体験型アプリケーションとTouchDesignerではTouchDesignerを使って体を動かす体験型アプリケーションをつくりましたが、今回は「顔」をテーマに、Claude Code(Opus 5.5)と、一部の画像作成ではCodex CLIを使って2つのゲームをつくってみました。
つくったもの
FACE SHOOTER
カメラの前で顔を動かして逃げる「Face側」と「Shooter側」に分かれる2人対戦ゲームです。
| 役割 | 操作 | 目的 |
|---|---|---|
| Face側 | カメラの前で顔を動かす・表情をつくる | 90秒間生き残る / シューターを撃墜する |
| Shooter側 | Switch Proコントローラー(キーボードでも可) | Face側のHPを0にする |
カメラに映った自分の顔が丸く切り抜かれて、画面上を逃げ回ります。
顔のまわりのリングがHPゲージになっていて、ただ逃げるだけでなく表情によって行動が変わります。
- 笑顔 をキープしている間は少しずつ回復
- 怒り顔 をキープすると、顔のまわりに放射状のミサイルを発射して反撃
見た目はファミコン風のレトロゲームとして仕上げました。
背景やロゴ、自機や弾などはすべてコードで描いたドット絵で、カメラに映った顔もリアルタイムでドット絵風に変換しています。
タイトル画面の方向性を決める際は、Codex CLIの画像生成(gpt-image-2)でデザイン案を4パターンつくってもらい、そのなかから惑星をモチーフにした案を選んでClaude Codeにドット絵として実装してもらいました。
文章で「ファミコン風」と伝えるよりも、先に絵で方向性を決めてしまうことで手戻りを回避しています。
FACE FACE MUSIC
FACE SHOOTERをつくってみて、自宅や展示などで遊んでもらうにはゲームコントローラーの接続やキーボードの準備が意外と面倒だなと感じました。
環境によってゲームコントローラーをうまく認識できなかったり、遊ぶ前の準備で子供たちが待ちきれなくなってしまいます。
そこで、カメラだけで成り立つゲームを検討しました。
FACE FACE MUSICは、陽気な曲に合わせて奥から飛んでくる「顔マーク」が自分の顔の枠にハマる瞬間に、同じ顔をする表情リズムゲームです。
- カメラの前に立つと、映った人数(1〜4人)に合わせて画面が分割される
- 全員がにっこり笑い続けるとゲームがスタート
- 2人・4人のときは隣どうしで「見つめあって!」という区間があり、そろうとハートが飛ぶ
- 成功するとパーティーゲージが溜まり、レベルが上がるごとに曲の楽器が増えて、最後はFEVER
マウスやキーボードを使わずに、スタートからリザルト、もう一回遊ぶところまで顔だけで操作できるようにしています。
こちらはThree.jsを使い、ブルーム(発光)のエフェクトをかけて、パーティーっぽい華やかな見た目にしました。
ちなみに、曲と効果音は音素材ファイルを使わず、Web Audio APIでリアルタイムに合成しています。
開発の進め方
どちらのゲームも、まずはClaude Codeと相談しながら仕様書(実装指示書)をMarkdownでまとめるところから始めました。
FACE SHOOTERの仕様書では、下記のような内容を最初に整理しています。
- ゲーム概要と勝敗条件
- 技術選定と理由(なぜネイティブアプリではなくWebでつくるのか)
- ハードウェア構成(Mac mini + iPhoneの連係カメラ + 大きめのモニター)
- システムアーキテクチャとディレクトリ構成
- 実装フェーズとチェックリスト
- リスクと対策
あとは仕様書のフェーズごとに実装してもらい、実際に体を動かして遊んでみて、気になったところを伝えて直してもらう、の繰り返しです。
実装自体は、土日の2日間、用事の合間にClaude Codeへ指示を出して動作確認する、という進め方でした。それでも、この2つのアプリケーションを遊べるところまで持っていくことができました。
以前なら、PhaserやMediaPipeのAPIを調べながら一つずつ実装していたと思います。今回は、人間側は仕様と体験の確認にかなり集中できました。
使用した技術
| 技術 | 用途 |
|---|---|
| MediaPipe Tasks Vision(Face Landmarker) | 顔の検出・ランドマーク・表情係数の取得 |
| Phaser 3 | FACE SHOOTERのゲームフレームワーク |
| Three.js | FACE FACE MUSICの描画 |
| Vite + TypeScript | ビルド・開発環境 |
| Web Audio API | BGM・効果音の合成 |
| Gemini TTS | 掛け声の音声 |
| Netlify | 静的サイトとしてホスティング |
Phaser 3は以前、子供用のひらがな学習ゲームをつくったときにも使いましたが、今回はClaude Codeにすべてお任せできました。Phaser 3のAPIドキュメントを読んで試行錯誤しながらコードを自作していたのが懐かしいです。
MediaPipe Face Landmarker
顔の判定には、Googleが提供しているMediaPipeのFace Landmarkerを使いました。
ブラウザ上(WebAssembly + GPU)で動作し、1つの顔につき478点のランドマーク(顔の特徴点の座標)と、52種類のブレンドシェイプ(表情係数)を出力してくれます。
ブレンドシェイプは「口角がどれくらい上がっているか」「眉がどれくらい下がっているか」といった値が0〜1で得られるので、これを組み合わせて表情を判定しています。
例えば、FACE FACE MUSICの怒り顔の判定は下記のようなルールベースの計算です。
const smile = avg(g('mouthSmileLeft'), g('mouthSmileRight'));
// 怒り: 眉を下げる + 下唇を突き出す(への字口)。眉を上げているとき(驚き)は打ち消す
const angry = Math.max(
0,
0.55 * avg(g('browDownLeft'), g('browDownRight')) +
0.25 * g('mouthShrugLower') +
0.2 * Math.max(avg(g('noseSneerLeft'), g('noseSneerRight')), avg(g('mouthFrownLeft'), g('mouthFrownRight'))) -
0.25 * avg(g('browInnerUp'), g('browOuterUpLeft'), g('browOuterUpRight')),
);
顔の向き(左右)は、鼻先のランドマークが左右の頬のあいだのどこにあるかで計算しています。
ただ、表情の大きさは人によってかなり違います。
怒っているつもりでも眉があまり動かない人もいますし、笑うと目が細くなって怒り顔と誤判定されることもあります。
そこで、どちらのゲームもプレイ前に「ふつう → 笑顔 → 怒り顔」などの表情をしてもらうキャリブレーションの時間を設けて、その人の表情の幅に合わせて判定するようにしています。
FACE FACE MUSICではこれを「かおチェック」としてチュートリアルも兼ねるようにしました。
全体構成
FACE FACE MUSICを例に、全体構成を整理してみます。
Netlifyからは、HTMLやJavaScriptに加えて、MediaPipeのwasmと顔認識モデル(face_landmarker.task)もあわせて配信しています。
ブラウザに読み込まれたあとは、顔の判定もゲームの処理もすべてブラウザの中で完結します。
顔解析ループとゲームループの分離
カメラの映像は約30fps、ゲームの描画は60fpsで動くので、両者を分離しています。
顔認識は requestVideoFrameCallback でカメラのフレームが届くたびに実行し、ゲーム側は最新の状態を読むだけにしています。
こうしておくと、顔認識の処理が少し重くなってもゲームの描画がカクつきにくくなります。
また、顔の座標はそのまま使うと細かくブレてしまうので、FACE SHOOTERではOne Euro Filterという平滑化の手法を使っています。
静止しているときのブレを抑えつつ、素早く動いたときにはちゃんと追従してくれるので、顔で操作するゲームの手触りがかなり良くなりました。
プライバシーへの配慮
顔の映像や特徴量を扱うアプリケーションなので、できるだけ外部にデータを送らずに完結する構成としました。
顔の映像はサーバーに送信しない
カメラの映像は保存も送信もしていません。
顔の判定はすべてブラウザ内のwasmで行っていて、映像や顔のデータが外部のサーバーに送られることはありません。
Netlifyは静的ファイルを配信しているだけで、APIサーバーのようなものはありません。
顔認識モデルもGoogleのサーバーから実行時に取得するのではなく、自分のサイトから配信しています。
ブラウザの開発者ツールのNetworkタブを開いたままプレイしてみると、最初のファイル読み込み以降は通信が発生していないことが確認できます。
ブラウザに保存するデータ
FACE SHOOTERでは、2回目以降のキャリブレーションを省略できるように、下記のデータをプレイヤー名と一緒にブラウザのlocalStorageに保存しています。
- キャリブレーションで測った表情の値
- 顔の特徴(目・鼻などのランドマーク間の距離の比率)
顔の特徴を保存しているのは、Shooter側の人やうしろを通った人の顔がカメラに映り込んだときに、追跡対象が入れ替わらないようにするためです。
顔認証というほどの精度はありませんが、「キャリブレーションした本人らしさ」を簡易的に判定して、その人を優先して追跡しています。
これらはあくまで自分のブラウザ内だけに保存されるもので、外部には送信されません。
設定画面の「保存した顔データを削除」から、いつでも消せるようにしています。
服装や髪の色などの見た目の特徴も追跡に使っていますが、こちらはプレイ中のメモリ上だけで使い、保存はしていません。
音声は Gemini TTS で生成
FACE FACE MUSICの「レディー?」「ゴー!」「みつめあって!」「みんなで わらって!」といった掛け声は、Gemini 3.8 Flash TTSで生成しました。
Gemini 3.8 Flash TTSは、読み上げるテキストとは別に「どんな雰囲気で話すか」を自然文で指示できるのが面白いところです。
今回は下記のように「パーティー会場の明るく元気な女性アナウンサー」というスタイルを指定しています。
実際には、セリフと生成指示を下記のような自作の設定JSONにまとめています。
{
"model": "gemini-3.8-flash-tts",
"voice": "Laomedeia",
"style": "A bright, bubbly young female game announcer at a party. Very cheerful and energetic, with big, bouncy, sing-song intonation and a smile in her voice. Short and punchy, like shouting a call to the crowd.",
"lines": {
"go": {
"text": "ゴー!",
"style": "explosive, super excited shout"
},
"check_angry": {
"text": "おもいっきり、おこって!",
"style": "playful, pretending to be angry, still fun"
}
}
}
セリフごとに追加のスタイルも指定できるので、「ゴー!」は爆発的に興奮した感じで、「おもいっきり、おこって!」は怒ったふりをしつつも楽しげに、といった調整ができます。
以前のひらがな学習ゲームでは音読さんで1つずつ音声を生成していましたが、今回はセリフをJSONにまとめておき、スクリプトから一括で生成しています。
生成したWAVはffmpegで前後の無音を削って音量をそろえ、MP3にしてリポジトリに含めています。
掛け声が曲の拍にぴったり合うように、無音のカットは意外と大事でした。
生成済みの音声ファイルを配信しているので、遊ぶときにAPIキーやネット接続は必要ありません。
紹介動画もつくってみた
ついでに、紹介用の動画もClaude CodeとGemini TTSでつくってみました。
おわりに
学生時代に「顔の判定でいろいろな体験をつくってみたい」と思ってから、ずいぶん時間が経ってしまいましたが、ようやく形にすることができました。
当時はクラウドに画像を送る必要があり、リアルタイムで遊ぶのは難しそうだった顔の判定も、今ではブラウザだけで、しかもサーバーに映像を送らずに実現できます。
仕様の整理から実装、音声や画像の素材づくりまでAIの力を借りることで、以前ならもっと時間をかけていたものを、今回は土日の2日間で遊べるところまで持っていけました。
一方で、表情判定の閾値や顔の追従の気持ちよさ、子供たちが遊ぶときの準備の手間など、実際に遊んでみないと分からない部分はやはり人によって確かめる必要があります。
つくる時間が短くなったぶん、こうした「体験の手触り」を確かめることに時間を使えるのが、今の開発の良いところだと感じました。





