Skip to main content

Blog Blog

Unwritten Chapters

ブラウン管を再現したくて 10 日溶かしました—— Mnemecha RETRO アップデートと、生まれた jgame CLI の話

User Author:Joshua Folkken
ブラウン管を再現したくて 10 日溶かしました—— Mnemecha RETRO アップデートと、生まれた jgame CLI の話

まずは、応援チップありがとうございます!

朝起きてみたら、また応援チップが届いていました。本当にありがとうございます!おかげで エアコンをつけて快適に開発ができております 。汗だくでキーボード叩いてたあの頃の自分に、いまの自分の涼しさを自慢したい気分です。

そして X でいいねやリポストをしてくれている皆さんにも、改めてお礼を言わせてください。 前回の Mnemecha リリース告知 のあと、しばらく進捗ポストができていなかったんですが、 X のタイムラインや、友達から「触ったよ!」「ハマってる」と言ってくれる声がずっと支えになっていました。本当に。

「あれから 10 日も何してたんや」と思った方、はい、 CRT フィルターを詰めていました 。あと、 game-kit を本気で切り出していました。あと、 @joshuafolkken/kit の AI ワークフローを 1 段進化させていました。 やることが多すぎて、 X に書く時間が無かった という、エンジニアあるあるです。

今回も触ってもらえると いちおくまん倍 くらい伝わるので、まずはこちらから → mnemecha.joshuafolkken.com起動した瞬間、 RETRO モードは最初から ON にしてあります。 「画面汚っ!」と思ってもらえたらこの記事の本番です 。スイッチで OFF にすれば現代の絵に戻るので、行ったり来たりして遊んでみてください。

あれから 10 日、ずっと CRT フィルターを詰めていました

前回の記事 を書いてから 10 日。 @joshuafolkken/game-kit には 89 個のコミット が積み上がっています。 @joshuafolkken/kit のほうも 51 個 。 mnemecha 本体に PR を入れた数は実は 6 本 で、ほとんどの作業は 下の階層 で起きていました。 game-kit を「本物の library 」に育てるための、地味で長い 10 日間でした。

その中で一番時間を吸い取ったのが、これから書く RETRO フィルタ でした。

RETRO フィルタ、何重にも「昔っぽさ」を重ねた

前回の記事 で「 CYBER モードだけが遅い」犯人探しをした話を書きました。あのとき同時に、 その逆の方向 —— CYBER と並べて立つ「 RETRO 」モード をちゃんと作り込みたい、というのが残っていたんです。「ノーマルとサイバーがあるなら、ノスタルジーも欲しい」。これが今回の起点。

正直に言います。 RETRO に 10 日溶かしました「自分は何をしてるんだ!ゲーム開発が進んでないじゃないか!」 と何度も自分にツッコみながら、それでも詰めるのを止められなかった。同年代の方には共感してもらえる気がします。若い人には「で?」って言われそうです。それでもやりました。 自己満足するところまでは突き詰めたいんですよ

ちなみに僕のテレビゲーム遍歴は、 カセットビジョン から始まりました。そこからファミコン、スーファミと普通に辿ってきたんですが、 早めにコンシューマを抜け出して、海外ゲームをしたくて DOS/V 機を買った クチです。 DOOMQuake猿のようにプレイ していた時代の、あの 粗いドットと、色数の少ない発色 の記憶が、いまの RETRO フィルタには一番強く投影されていると思います。 10 日溶かしたのは、たぶんこの個人史を 1 個のスイッチに詰め込もうとしていたから。

CRT っぽさを成立させるために、ひとつのスイッチの裏に何枚も加工を重ねています。

256 色は VGA 3-3-2 パレット

色数は 256 色 に絞っています。これ、適当に決めた数字ではなくて、 VGA の 3-3-2 パレット ( R に 3bit 、 G に 3bit 、 B に 2bit を割り当てるあの構成)をそのまま採用しました(game-kit #117 / #120)。まさに昔のグラボの制約そのもの。

色を 256 色に落とす量子化処理は、 WebGL のシェーダ側で計算しています。ただし量子化だけだとバンディング(縞模様)が露骨に出るので、量子化の直前に Bayer ディザ (規則正しい網点パターンで中間色をごまかす技法)を一発噛ませて、空間的に色を混ぜたように見せています。これだけで CRT 越しに見たときのあの「色が足りない感」 がいい具合に出るんです。 Bayer 行列の閾値と 3-3-2 量子化、その 2 段だけで PS1 ムービー画質が戻ってくる 。たまらん。

スキャンライン、湾曲、色のにじみ

CRT っぽさは色だけじゃ出ません。 RETRO モードでは、 WebGL レベルで次のような加工を重ねています(game-kit #74, #75, #78, #92):

効果役割
バレル歪み画面の四隅を内側に引っ張って、 CRT の凸面ガラスを再現
スキャンライン1 ライン置きに少し暗くして、走査線の段差を出す
ビネット四隅をフッと暗くして「画面の縁が暗い」あの感じ
色収差( chromatic )赤と青を 1px くらいずらして、レンズの色のにじみを再現
ドットブレンドサブピクセルを意識した滲ませ方で、 1 ドットを 1 ピクセルにしない

ひとつ加えるたびに「これかよ!」「これは違うわ」を繰り返しました。「もう少し湾曲を強く」「いや、酔うからやめよ」「色収差は 1px だと弱い、 2px だと崩れる」。 ms 単位ならぬ px 単位の往復ビンタ前回 ms 単位で勝利フラッシュを詰めた話 と、ぜんぜん変わらん事をやっています。

スマホで CRT を成立させる

ここが本当にしんどかった。 CRT 表現って テレビや PC モニタの「横長の大画面」を前提に作られた工夫が大半 で、スマホみたいに 縦持ち・小さい画面 にそのまま持ってくると色んなところが破綻するんです。

  • ポートレート(縦向き)ではスキャンラインを縦向きに切り替えるgame-kit #81)。これ、ただ単純に向きを揃えてるわけじゃなくて、 昔の縦画面アーケード筐体 (シューティングや一部の業務用筐体)はブラウン管自体を 90 度回転させて取り付けていて、 スキャンラインも縦方向に走っていた(気がする!) んです。あのリスペクト込みで、縦持ちのときはスキャンラインも縦に走らせています。
  • 高 DPR 端末でスキャンラインがモアレる問題を直すgame-kit #95)。 iPhone のように DPR ( Device Pixel Ratio )が 2 や 3 の高画素端末 では、 CSS で 1px 幅のスキャンラインを描いても、物理画素は CSS の 2 〜 3 倍細かいので、 スキャンラインの周期と物理画素のピッチが合わずにモアレ(干渉縞) が出て、画面全体に粗い波模様が走ってしまう。 DPR を見て描画ピッチを物理画素に合わせる調整を入れました。
  • フルスクリーン時にセーフエリアまでちゃんと描画するgame-kit #80)。ノッチがある端末でも CRT 風の縁取りが画面の本当の端まで届くようにする。

地味な調整ばっかりです。でも、これをサボると「 PC では美しい RETRO 、スマホでは謎のチラつき UI 」になっちゃう。 スマホで触った人に「これ壊れてる?」と思われたら全部終わり なので、ここは妥協できないところでした。 前回モバイル 30 → 60 FPS を詰めた話 と同じ温度感で、また地味な数日が溶けました。

X68000 風のテイストを 1 滴

途中、 game-kit #76「 X68000 風のレトロ感」 にチューニングをかけました。

色まわりは コントラストはあえて低めに落として、彩度とブライトネスは持ち上げる というレシピ。これは X68000 に限らず、 レトロ表現全般 で効くやつ。 目を細めて遠くから見たときの、色が褪せて軽く滲んで見えるあの記憶 を、数字で再現する作業です。

そして X68000 から拝借したいちばんのポイントは、色じゃなくて 縦解像度を 256px に寄せたこと 。当時のファミコンや初期 PC の 縦 200 や縦 240 だと、ちょっと画素が足りない感じ がしたので、 X68000 の縦 256 を参考に少しだけ増やした 、というのが本当のところです。あの少し贅沢な縦比を 1 滴混ぜたら、 RETRO の質感がぐっと好みになりました。

結果として、 ファミコンでもスーファミでも PS1 でも X68000 でも DOS/V でもない僕の脳内にあるノスタルジーをブレンド したような色合いに落ち着いています。同じ世代の方に「あ、この感じ」と思ってもらえたら、それが一番うれしい。

RETRO スイッチの ON / OFF

そして、これ全部を スイッチ 1 つで切り替えられる ようにしました(game-kit #93 / #114)。スイッチの場所は CYBER スイッチの隣。 ON だとブラウン管、 OFF だと今っぽい絵。 「過去」と「現代」をパチンと往復できる のは、触ってもらえると気持ちよさが伝わると思います。

おまけのこだわりですが、 RETRO OFF のときはデスクトップでアンチエイリアスをちゃんと効かせるようにしました(game-kit #116)。「今っぽい絵」を選んだときは、 本当に滑らかな現代 に戻ってほしいから。スイッチひとつで質感が向こう側まで切り替わるのは、いまの僕のお気に入りポイントです。

リリース後、 Pitt さんの一言で「読みやすさ」をもう一段詰めた

ここまで「 RETRO 詰めました!」の話を全力でしてきたんですが、 実はリリース直後、レトロフィルターが効きすぎていて文字が読みにくい状態でした 。自分では「これで完成!」のつもりだったんです。

そんなときに、 ゲーム開発歴 16 年のベテラン、『アルメリアの咲く頃に』を制作中の Pitt さん(@armeria_game から、こんな指摘をいただきました。

エフェクトのかかり方が、カメラとの距離によって UI が見にくいのが気になりました。調整次第で全然いけると思いますよ!

正直、めちゃくちゃ刺さりました 。「自分の絵」に夢中になりすぎて、 プレイヤーが情報を読むための UI(スコア、ラウンド数、 START / GAME OVER) という、ゲームとしていちばん大事なところを見落としていた。レトロにハマって UI を「雰囲気のオブジェ」に格下げしてた、と言われたら反論できない。

ということで、 レトロさは残しつつ、文字だけはちゃんと読めるようにする チューニングを 1 周入れました(game-kit #137 / #140 / mnemecha #255 / #258)。

  • CRT エフェクトを 1 段だけ弱めた —— バレル歪み 0.15 → 0.1 、ドットブレンド 0.4 → 0.2 。 遠くから見ても文字の輪郭が崩れない ところまで戻しました。レトロは効かせる、でも効かせすぎない、のさじ加減です。
  • スコアボードを盤面に寄せて、少しだけ手前に傾けた —— ボードのすぐ上に置いて、 0.4 rad ほど下向きに。 HI / RND / SCORE のフォントサイズも上げて、 RETRO ON のままでもちゃんとスコアが読めるようにしています。
  • GAME OVER と ROUND の見せ方を、状態ごとに作り分けた —— START は単行で大きめ、 ROUND 表示は 数字だけドンと出す 、 GAME OVER は 2 行構成で行間に余裕 を持たせる。状態ごとに必要な情報量が違うので、見せ方もそれに合わせて変えました。
  • 「スイッチ切ったのに GAME OVER のフォントだけ昔のまま」を直したgame-kit #140) —— 実は RETRO スイッチを切り替えると、スコアボードのフォントは変わるのに GAME OVER / ROUND / START のフォントだけ変わらない という地味なバグがありました。 fonts.get_active_font() という「現在の RETRO 状態を自動で読みに行く」ヘルパーを新設して、 そもそも呼び出し側が CRT 状態の配線を忘れる事故そのものを起こせない ように API の形を変えました。

つまり、 「 RETRO の雰囲気」と「ゲームとしての読みやすさ」の両立 を、もう 1 周やり直した話です。前段の「Bayer 行列と 3-3-2 量子化」みたいなドカンとした技は無くて、 バレル歪みは 0.05 、ドットブレンドは半減 、というレベルの小さな詰め。 前回の記事 で書いた ms 単位の往復ビンタ の、また別バージョンが始まったわけです。

ちなみに同じタイミングで、 SHIZUYA さん(@SHIZUYA_122419 ラウンドまで遊んでくれている という報告もいただきました。 SHIZUYA さん、本当にありがとうございます。 ラウンド 19 までいける人がこの世にいる という事実が、開発者として何よりの励みです。 「誰かが本気で遊んでくれている」 という証拠以上に、次の作業を回す燃料になるものは無いです。

そして、 X でいいねやリポストを何度もくださっている皆さんにも、改めて 本当にありがとうございますポチッの 1 個 1 個 が、応援チップと同じくらい、本当に支えになっています。

触り心地もちょこちょこ詰めた

CRT フィルターの作り込みに時間は持っていかれましたが、その横で地味な触り心地調整も並走で進めていました。

  • PC で歩く速度を半分にしたgame-kit #61)。リリース後にあらためて自分で歩いてみたら 「これ早すぎやな」 と気づいたやつ。リリース前は遊ぶというより検証で動かしていたので、 ちゃんと遊ぶ目線になって初めて違和感が見えた タイプの調整です。 SHIFT キーで加速 もちゃんと付けています。「ふだんはゆったり、急ぎたいときだけダッシュ」、これくらいの操作密度がちょうどいい。
  • 操作説明を 2D → 3D に移したgame-kit #62 / #133)。今までは画面に 2D のオーバーレイで「 WASD 移動」を貼り付けていたんですが、これを 3D 空間内のパネル に描き直しました。背景の透過度、画面サイズに合わせた縮尺、キーボードイラストの整列…… またここに微調整地獄が待っていた 。でも、 前回の記事 で書いた「ホロ風 UI 」の続編としてちゃんと地続きにできて、満足です。
  • モバイルの操作説明イラストの位置ずれ も直しました(game-kit #69)。これも触ってくれた方のフィードバックがきっかけ。触ってくれた皆さん、本当にありがとうございます。

「結局はこの 10 日間、ずっと微調整やっていただけやないか?」と問われると、 そうやね、と素直に頷きます 。でもこれこそが UX の品質。 share-buttons でも simplify-ui でも書いてきたことの、また別の表現形に過ぎないです。

game-kit が「ちゃんと library 」になった

ここからが裏側の話。 前回の記事 で「シンプルなゲームをまず 1 本作って、そこから “ゲームコア以外” を抜き出す」と書きました。今回、それが 本当に切り離せた 瞬間がありました。

mnemecha 側の #252 で、ローカルの src/lib/game/ をぜんぶ消して、 @joshuafolkken/game-kit を node_modules から参照する 形にスイッチしたんです。 PR は #253 。 mnemecha のソースから 3D ゲームの「骨格」がきれいに消えて、残ったのは 「 Mnemecha 固有のロジック」 だけ。

切り離してから初めて「これは library と呼べる」になります。これまでは同じ repository に居候していた共通コードでしたが、 npm パッケージとしてバージョン管理されている今、 mnemecha は 使う側 、 game-kit は 使われる側 。ぜんぜん違う重みになります。

そして、 game-kit 側でも内部のリネーム作業を進めました。「 Simon 」(前回記事で書いた、 Mnemecha のベースになったあのおもちゃ)という固有名でディレクトリ・ファイル・シンボルが残っていたところを、ぜんぶ汎用名の game に置き換え(game-kit #105 / #129)。 「ライブラリの中に Mnemecha 固有の名前が残っている」状態を完全に消した わけです。

前回の記事 で「綺麗な抽象は最初から作らない、 1 つ目を作りながら見えてくる」と書きました。今回がまさに 見えてきた瞬間 。 1 つのゲームを作り切ったあとだから「これは固有」「これは共通」が分かる。最初から綺麗な library なんて、絶対に作れません。

game-kit の副産物として「 jgame 」 CLI が生まれた

切り出した game-kit 、せっかくだから 配布まで作りたい前回の記事 でも書いた gk init 構想の続きです。

今回、 CLI 名を gk から jgame に改名しました(game-kit #87)。「 gk 」だと GK ってなんやねん! というセルフ突っ込みが入って(言い訳)。 jgame なら Joshua の Game で、何を作る CLI かが名前で分かる。 名前は外向きの言葉で —— 前回の記事の自分の主張に従いました。

そして、コマンドの中身もしっかり整えました。

コマンドできることissue
jgame init新しいゲームの土台を 1 コマンドで scaffoldgame-kit #87
jgame installjgameラッパースクリプト~/.local/bin/ に配置。以降 jgame … だけで叩けるようになるgame-kit #87
jgame syncテンプレートを最新に保つ。 game-kit を上げたら設定ファイルも自動で同期game-kit #122 / #135
pnpm dlx @joshuafolkken/game-kit …グローバルインストール無しで、その場で叩ける(パッケージ名は @joshuafolkken/game-kit 、 bin 名は jgamegame-kit #103

pnpm dlx @joshuafolkken/game-kit install 一発で、グローバルラッパー( ~/.local/bin/jgame )が入って、以降は jgame init my-game のように どこからでも素のコマンド として呼べるようになります。 jgame sync のほうは、 kit-2 の記事 で書いた josh sync の game-kit 版。 「設定のコピペとサヨナラ」 を、 game レイヤーでも実現するための仕掛けです。これだけで小さなツールとして独立して使えるレベルに育ちました。

kit-package の記事 で書いた「土台を作るほど次が早い」を、 game 側でも実証する番です。次のゲーム作るとき、ほんとに jgame init 一発で始まる。 同じことを 2 回書くのめんどくさいから、ライブラリにする単純作業アレルギー 、健在です。

kit 側のワークフローも 1 段進化—— halfrun の話

@joshuafolkken/kit 側でも、 AI と協業するためのワークフロー自体に手を入れました。

特に大きいのが halfrun という新モードを追加したこと(kit #391 / #392)。

前回の記事 で書いた fullrun は、 計画から PR マージまで全部 AI に任せる モードでした。一方の halfrun は—— 「実装と検証までは自動で走るけど、コミット前で必ず止まる」 モード。

モードスコープ主な用途
kickoff計画立てだけIssue に方針を貼って止まる
halfrun実装+検証して、コミット前停止UI / UX 系の変更を 人間が目視確認
fullrun計画から PR マージまでロジック・テスト・自動化向きの変更

UI 系の変更は 触ってみないと分からない 。 RETRO フィルタみたいに「数字上は正しいが、目で見たら別物」になり得る作業を、 AI さんが緑チェックを返したから即マージ、では絶対にダメなんです。

halfrun を入れたあと、 RETRO フィルタの細かい調整 PR はほぼ全部この経路で回しました。 「自動で走らせるが、最後に必ず自分で触る」 という、当たり前のことを当たり前にできる仕組みです。 AI に全部任せきりにせず、でも任せられるところはちゃんと任せる、というラインを halfrun という名前で固定化 した、と言うとちょっとカッコ良すぎますが、まあそういう試行錯誤の途中です。

前回の kit-2 記事 で書いた queue の世界観の続編 / 補完だと思ってもらえれば、ちょうどいい温度感だと思います。

経済学の更新—— Opus 4.7 にも手伝ってもらうことにした

ここで 前回の Mnemecha 記事 で書いた「 Sonnet / Opus 経済学」の話を、ちょっとだけアップデートさせてください。

前回は 「コストの問題で Sonnet で十分」 と書きました。実は 今回から、 Sonnet 4.6 だけじゃなく、 Opus 4.7 にも手伝ってもらうことにしました

なぜか。 RETRO フィルタのような 「触り心地と設計の両方が絡むテーマ」 に当たったとき、 Opus の じっくり考える力 が効く場面があるな、と感じたから。シェーダの調整、 game-kit の境界線の引き直し、 halfrun の設計—— ここぞというところで Opus 4.7 に依頼するようにしました。普段の作業はもちろん Sonnet 4.6 です。 Sonnet で 80%、 Opus で 20% ぐらいの混ぜ方 で、いまのところちょうど良いリズム。

「結局 Opus 高いんちゃうの?」と問われると、 応援チップのおかげで成立しています 。本当に。サポーターの皆さんがいなかったら、エアコンも Opus も両方ともサヨナラやったかもしれん。 前回 5 時間リミットに引っかかった話 からの、 ちょっとだけ余裕ができた今 。続けて応援してくださっている皆さんに、ちゃんと記事の質で還元したい。

次は、三目並べ

リリース直後でやりたいことは山積みなんですが、 次のゲームに行きたい気持ちが今いちばん強い です。

題材はもう決めています—— 三目並べ (まる・ばつ・ゲーム)。 3 歳から 90 歳まで遊べる という根っこに、これ以上ハマる題材はそうそう無いと思っています。世代も国もスキルレベルも、 ルール 1 行で全員が触れる

ただし、正直に書きます。 三目並べそのままだとシンプルすぎて、ぶっちゃけ面白くない 。なので、そのままは作りません。 他のゲームから持ち込んだアイデアを足して、 Simon と同じように 3D 空間で実現する 予定です。具体的な構想は、次のリリースまで秘密にしておきます。

実はずっと昔、 Godot で同じ題材を 1 度作っていて、それを TypeScript + SvelteKit + Threlte + jgame で作り直したい、というのが本音です。 jgame init tic-tac-toe 一発で始まる土台がもうあるから、 game-kit が成立しているかどうかを 次のゲームで実証する には最高の題材。

もしランキング機能や色覚サポートのような、 前回の記事 で書いた Mnemecha のロードマップを心待ちにしてくださっている方、ごめんなさい。完全には忘れていないんですが、 次のゲームを 1 本通すほうが game-kit の検証になる ので、そちらを優先します。 mnemecha 自体の細かい改善は、 jgame と並走で少しずつ進めます。

おわりに

長くなりました。 10 日間、 X が静かだった裏側でやっていたのは、 CRT フィルターに自分のノスタルジーを、 VGA 256 色のパレット越しに詰め込む作業 と、 game-kit を独立した library として配布できる状態にする作業 、その 2 本でした。 CRT フィルター側は 途中、 Pitt さんから「文字が読みにくい」という指摘をもらって、 RETRO のさじ加減を 一段戻す 作業も挟みました。 詰めて、詰めすぎて、戻す 。 10 日間の体重移動はそんな感じでした。短期と長期の両軸、 前回 と同じ流儀です。

睡眠時間を削ってまでこんなことをやっていた理由は、 僕自身が、この絵を見て「あぁ、これや」と懐かしくなる世代のど真ん中だから 、の一言に尽きます。 RETRO モードの絵を、自分が一番先に体験したかった。 作りたいものを作れている時間 って、本当に幸せです。

実際に触ってもらえるのは mnemecha.joshuafolkken.com起動した瞬間、 RETRO は最初から ON にしてあります。 「画面汚っ!」 と感じてもらえれば成功。そこから OFF にして現代に戻ったり、行ったり来たりして遊んでみてください。リポジトリは github.com/joshuafolkken/mnemecha 、 game-kit は github.com/joshuafolkken/game-kit 、 kit は github.com/joshuafolkken/kit 。「ここの色が変」「ここのスキャンラインが歪んでる」っていう感想、本当に欲しい。 @joshuafolkken にぜひ投げてください。 DM も歓迎です。

次は三目並べ、また告知させてください。 楽しみにしててな!


ボツタイトル供養

  • ブラウン管を再現したくて 10 日溶かしました—— Mnemecha RETRO アップデートと、生まれた jgame CLI の話(採用)
  • 256 色の VGA 3-3-2 パレットで、僕のノスタルジーをぜんぶ詰めました
  • RETRO スイッチに 10 日、 game-kit に 89 コミット——前回 Mnemecha から 10 日間の全記録
  • 「自分は何をしてるんだ!」と叫びながらブラウン管を再現した 10 日間
  • gk から jgame へ、ゴールキーパーじゃないんですよ——次のゲームの土台ができた話
  • Sonnet で 80%、 Opus で 20%——応援チップで経済学が変わった話
  • スマホで CRT を成立させるための、地味すぎる 10 日間
  • Pitt さんに「文字読みにくい」と言われて、効果係数を 0.05 ずつ削った話
  • 9 日詰めて 1 日戻した—— RETRO スイッチの体重移動記録

いいなと思ったら応援しよう!

Support チップで応援する

応援してもらえると最高に嬉しいです!

X
© 1970 Joshua Folkken