
ブラウザで歩き回れる海辺を、AIに作ってもらった
前回の記事 で、長年アプリのマスコットだった「えびふりゃ」をAIに3D化してもらいました。最後はブラウザの中を歩き回れるところまで行って、そこで欲が出ました。歩けるなら、世界が欲しい。世界があるなら、遊びが欲しい。 というわけで今回は釣りゲームです。えびふりゃが海岸で竿を投げて魚を釣る。そういうものをブラウザで動かしたい。進め方は前回と同じで、コードはすべてAIに書かせて、私は指示とダメ出しだけ。使ったのはClaude Code(Anthropicのコーディング用AI)のフェイブル5です。技術はThree.js(ブラウザで3Dを描くJavaScriptライブラリ)にしました。UnityのようなゲームエンジンではなくWebを選んだのは、ビルドが要らず、URLを開くだけでスマホからでも確認できて、直しの往復が速いからです。 今回のように「コードは全部AIに書かせて、自分は注文とダメ出しに徹する」進め方は、少し前ならうまく回りませんでした。フェイブル5も登場した当初は評判がいまひとつでしたが、その後の調整でずいぶん使い勝手が良くなっています。一つ頼むたびに手を止めて確認を求めてくるのではなく、一時間規模のまとまった作業をそのまま任せておける。裏でずっと手を動かしていてくれる感覚に近く、細かい指示の往復や不毛なやり取りが減りました。正直、これに慣れるともう元のやり方には戻れません。もっとも、この調子で回すと消費も速く、数日もあればひと区切りの枠を使い切ってしまうくらいには働いてもらっています。 先に言っておくと、釣りの仕組みそのものは数時間でできました。大変だったのはその後です。キャラの体と、そして海。特に海には三度負けました。この記事はそのつまずきの記録です。 「すごいひどいことになってますから」── AIは自分の画面を見ていない 最初の釣りプロトタイプは威勢がよかった。キャスト、アタリ、糸のテンションを見ながらの駆け引き、釣果と表情のリアクション。仕組みは一通り入っています、URLをどうぞ、と来たので開いてみたら、キャラは画面からはみ出すほど巨大で、顔は見切れ、竿は宙に浮いていました。 考えてみれば当たり前で、AIはコードを書いてはいるけれど、レンダリングされた画面を一度も見ていないのです。3D空間のカメラ位置もキャラの大きさも、数値の上でつじつまが合っていれば「できました」と言ってくる。前回のモデリングでは「レンダリングして元絵と重ねて検証してから見せて」という約束で回っていたのに、ゲームになった途端そのループが消えていました。 そこで「自分で画面を確認できるようにしてくださいよ」と頼んだところ、AIはヘッドレスブラウザ(画面を持たないブラウザ)でゲームを開き、スクリーンショットを撮って自分で見る仕組みを作りました。これで開発が一変します。以降のやりとりでは、AIが自分で撮った画像を見て「キャラが地面に埋まっていました、直します」と自己申告してくるようになりました。埋まっているのを見つけるのが私の仕事から、AIの仕事になったわけです。 その仕組みで自己修正を重ねた結果が、いちおう遊べる最初の釣り画面です。長押しでパワーを溜めて離すとウキが飛び、アタリの「!」でタップ、魚が走ったら指を離し、落ち着いたら巻く。釣れれば魚種とサイズが出て、キャラが表情を変えて跳ねる。 操作の「当たり前」は、言わないと伝わらない 次に世界を歩けるようにしました。散歩モードと釣りモードを分け、波打ち際に近づくとキーひとつで釣りが始まる。ここでも細かい攻防があって、たとえば最初の移動操作は「Wで画面の奥へ、Aで画面の左へ」という、カメラ基準の平行移動でした。Dで右を向いたら、進むのはWですよね、という「よくある3Dゲームの操作」は、明示的に言うまで出てきませんでした。 もうひとつ面白かったのはカメラのバグです。キャラが向きを変えると、カメラが背後へ回り込む瞬間に一度ズームインしてしまう。AIに調べさせると、カメラの位置を直線で補間していたのが原因でした。円弧の上を回るべきところを直線で近道するので、旋回中だけキャラに寄ってしまう。角度を補間するよう直して解決です。言われてみればそれはそうだ、という話ですが、こういう「触ると気持ち悪いが、コード上は間違っていない」類のバグは、実際に触った人間が指摘しないと直りませんでした。 お手本のURLを渡したら、世界が変わった このころの見た目は、砂色の床と青い板が並んでいるだけの、いかにも工作然としたものでした。言葉で「もっと綺麗に」「夏っぽく」と言っても、良くなる気配がない。そこで方針を変えて、私が理想とする世界観のWebGL作品、Vicente Lucendo氏のSummer Afternoon のURLをそのまま渡しました。「このような世界観でできないものなの?」と。 効果は劇的でした。AIは作品を特徴ごとに分解して──暖色のパレット、グラデーションの空、風でなびく大量の草、様式化された水、柔らかい光──それを一つずつ実装に落とし始めました。百回言葉で注文するより、見本をひとつ渡すほうが早い。人間のデザイナーに頼むときと同じでした。 余談ですが、このSummer Afternoon、ただ眺めるだけでなく歩き回って遊べて、マップのどこかに秘密が5つ隠されています。探しはじめると止まらなくなって、私は4つまで見つけたものの、最後のひとつがどうしても見つかりませんでした。この世界に憧れて今回の海辺づくりが始まったので、スクリーンショットを貼るより、ぜひリンクから本物を歩いてみてほしいです。開くだけですぐ動きます。 人間キャラは、木偶にしかならなかった 世界観を変えるにあたって、キャラは「無理にえびふりゃじゃなくてもいい、少年にしよう」ということになりました。麦わら帽子の男の子が海辺で釣りをする。いいじゃないですか。 ところが、これが今回いちばんはっきりした失敗になりました。AIは円柱や球を組み合わせて少年を作り、関節の位置でパーツを回して歩かせようとします。出てきたものは、腕と脚の生えた木の人形が滑っていくような、見ていて萎える動きでした。おまけに一度は頭が丸ごと消える事故まで起きています(内部の座標変換が壊れて、頭のグループだけ描画されなくなっていました)。 AIに「抜本的に品質を上げるにはどうすればいいか」と正面から聞いたところ、返ってきた答えは率直でした。人型が自然に動いて見えるためには、骨格(リグ)と、骨に皮膚が追従して変形する仕組み(スキニング)と、ちゃんと作られた歩行アニメーションの三点セットが必要で、これはプリミティブの寄せ集めをコードでどう頑張っても埋まらない。つまり自作をやめて、リグとアニメーションが最初から入っているモデルを使うべきだ、と。 ライセンスを調べさせると、これがなかなか勉強になりました。AdobeのMixamoは商用無料だけれど素材単体の再配布が禁止で、ブラウザゲームは配信ファイルが誰でもダウンロードできてしまうためグレー。一方Quaternius などのCC0(実質パブリックドメイン)素材なら、商用でも帰属表示なしで完全に自由。Webで出すならCC0一択という結論です。 ちなみに、「無料で自由に使えるキャラクター」の代表格としてくまモンを思い浮かべる人もいるかもしれませんが、あれはCC0ではありません。くまモンの著作権は熊本県が持っていて、商品や広告など商用で使うには県への利用許諾の申請が要ります(許可されれば利用料はかからず、熊本のPRになることが条件です)。一方で、個人が非営利で楽しむぶんには申請もいらず、SNSや個人ブログへの掲載などかなり自由に使えます。CC0の「誰でも無条件・無申請で完全に自由」とは似て非なるもので、あの気前のよさは権利を手放しているのではなく、熊本県が許諾制度でうまく開放しているからなんですね。だからこの記事にも、くまモンの画像そのものは載せていません(この記事は自作アプリの宣伝も兼ねているので、勝手に使うなら許諾が要る側です)。 ひとまず確実に入手できたCC0のロボット(Three.js公式サンプルのRobotExpressive。歩き・待機・ジャンプなどのアニメーション入り)を組み込んでもらいました。移動すれば歩きへ、止まれば待機へなめらかに切り替わり、釣果が出ればワンショットで喜ぶ。木偶人形との差は歴然でした。少年の見た目は失いましたが、「動きの質は素材で買う」が正解だと納得した瞬間です。 木も草も、自作をやめてフリー素材に頼る キャラで学んだことは、そのまま背景にも効きました。AIが球と円柱で作った木はどう見てもおもちゃで、草に至っては「尖った針がゆらゆら動いている」ようで気持ち悪い。ここもQuaterniusのSimple Nature(CC0)に差し替えることにして、私がサイトからFBXファイルをダウンロードし、AIに渡しました。 ここで小さな学びがふたつ。ひとつは形式の話で、配布されていたのはFBX・OBJ・Blenderファイルの3種でしたが、Web(Three.js)で使うならglTF/glbが本命、なければFBXをBlenderで変換すればよく、OBJは避ける、.blendは自分で編集しない限り不要。つまり「今後はglbかFBXだけ落とせばいい」。 もうひとつは罠の話。FBXをglbに変換して組み込んだのに、木がまったく表示されない。AIが調べたら、変換の過程でマテリアルの不透明度が0(完全に透明)になっていました。木は透明人間として、ちゃんとそこに立っていたわけです。不透明に戻して一件落着。草は1本のモデルを1万5千本、インスタンシングという手法でまとめて描画し、シェーダーで根元を固定したまま穂先だけ風に揺らしています。 海との戦い その一 ── 画面が真っ黒になる呪い さて海です。私はこのゲームの主役は海だと思っています。普段海を見ない人が、画面の中の海を見てちょっと嬉しくなる。そういうものにしたかったので、「キラキラ水面に光る波、太陽の感じ、空の綺麗な青。ここは絶対に維持してほしい」と注文しました。 AIは水面のシェーダー(画素ごとの色を計算するプログラム)を書き、太陽へ向かう反射帯にきらめきを集める表現を作りました。これ自体は綺麗にできたのですが、その過程で妙な事故が続きます。発光表現のためのブルームという後処理を入れた途端、画面全体が真っ黒になるのです。 原因究明は探偵ものみたいで面白かった。AIは容疑者を一つずつ消していきました。花粉のパーティクルの計算に数値エラー(NaN、非数)を見つけて直す──まだ黒い。水面を消してみる──黒いまま。後処理を切る──直った。つまりブルームが、シェーダーのどこかで生まれるNaNを画面全体に塗り広げていた。数値のエラーが一画素でもあると、ぼかし処理がそれを全画面に拡散してしまうのです。結局ブルームは諦めて、きらめきはシェーダー側で表現することになりました。 海との戦い その二 ── 水際のチートは、ぜんぶバレる 次は波打ち際です。最初の実装は、砂浜と海の境界に白い帯を一本置いただけのもので、「境界線が一直線。こんなことありえない」と私が却下。ではとAIが持ってきたのが、水面の手前の縁だけをくねくね波打たせる方式でした。 これが今回いちばんの見ものだったかもしれません。スクリーンショットを撮ると、波の先端が蛇のようにうねっているだけで全然波じゃない。しかも波の先端と海本体の間に、なぜか砂浜が見えている。水が分裂しているわけです。私が写真を突きつけると、AIは「チートがバレましたね」と白状して、根本からやり直すことになりました。 やり直しの方針は、ごまかしをやめることでした。砂浜を、実際に海へ向かって下る斜面として作る。波で上下する水面がその斜面と交わる線が、すなわち波打ち際になる。水際線を「描く」のではなく、地形と水面の交差から「生まれさせる」わけです。こうすると、うねりで水面が持ち上がれば水際は自動で砂浜側へ押し寄せ、引けば濡れた斜面が現れる。水と砂の間に隙間ができようがありません。 この方式に変えた直後、もうひとつ豪快なバグが出ました。海と砂浜の間に、幅の広い謎の白帯が現れたのです。調べたら、地面のポリゴンが波打ち際の手前で終わっていて、引き波のときに水際がその先へ下がると、そこには地面が存在せず、透けた水の向こうに背景の空が見えていた。白帯の正体は泡ではなく「世界の穴」でした。地面を海の中まで延長して海底を作り、ついでに浅瀬は水深に応じて水を透明にしたら、砂→濡れ砂→砂が透ける浅瀬→青、という本物の海辺のグラデーションになりました。 おまけに、海底ができたことで副産物がありました。それまでキャラは波打ち際までしか歩けなかったのですが、地面が海中まで続いたので、そのまま海に入って腰まで浸かれるようになったのです。 ...







