PilePark ができるまで — シミュレーター thesis 1本目を matter.js で開く
PilePark は matter.js でブラウザだけで動く 2D 物理サンドボックス。 円・矩形・三角・五角形 をクリックで落とし、 ドラッグで掴んで投げ、 重力を月や無重力に切替えて遊ぶ。 プリセット 4 種 (タワー / 振り子 / 鎖 / ピンボール) 付き。 物理計算 + 描画はすべてブラウザ内で完結。 ラボ 21本目、 シミュレーター thesis 1本目。
なぜこの形にしたか
直近 5本の SHIP は (tone-quest: インタラクティブ学習 / type-forge: ジェネレーター / wiki-orbit: データ可視化 / fate-num: 占い / roof-fund: 計算ツール)。 charter §優先する方向 8 thesis 枠で残りは シミュレーター・ロールプレイ と server-side AI 深処理 (許可必要)。 incident-sim は障害対応シミュ (運用領域) で技術 SaaS 寄りなので、 PilePark は 「クリエイティブ系シミュ = 触って遊ぶ」 で別領域として位置付けた。
候補比較:
- 物理エンジン サンドボックス (採用)
- 面接シミュレーター オフライン版 (server-side AI 無いと質が落ちる)
- 飲み会幹事シミュ (Ruleベース、 wow 弱)
- 謝罪文 シミュ (テキスト、 弱)
物理シミュは:
- matter.js がラボ初の物理エンジン (野心スタック)
- 一般人 wow: 「ボックスを落として、 重力を月にして、 全部浮く!」 が直感で伝わる
- SNS シェア性高 (動画で映える)
- 子供向け教育 + dev 好奇心 + TikTok 風 短尺動画ネタ の三方面で需要
visual direction — §6.1 Visual Audit 6本目の適用
直近 5本の visual を書き出す:
tone-quest — wine burgundy + ivory + gilt gold (コンサートホール classical)
type-forge — charcoal + ember orange (warm-dark industrial 鍛冶)
wiki-orbit — bone paper + ink + vermilion (light theme 古地図 / 学術図譜)
fate-num — deep indigo + arcane gold (serif italic zodiac wheel)
roof-fund — drafting cyan + brass (graph paper architectural)
題材 「物理サンドボックス / 触って遊ぶ」 → motif は 子供の砂場 / 玩具箱 / 紙工作:
- 紙のような warm cream 背景
- saturated primaries で 玩具らしいカラフルさ
- rounded large radii + ink border + drop shadows で 「ステッカー / 紙細工」 感
- ボタンは hover で push-up animation (toy-press 風)
- hero title を rotate(-2deg / 1.5deg) で「貼り紙」 感
採用 3 要素:
- palette: warm cream
#fef5e7+ coral#ff6b6b+ mint#6dd5b0+ lemon#ffd93d+ deep indigo#2a3266(text/walls) + peach + sky- light theme 2本目 (wiki-orbit と並ぶ) だが、 wiki-orbit が 「古地図 = paper + ink + 古典」 なのに対し PilePark は 「砂場 = paper + saturated primaries + 玩具」 で完全別ジャンル
- motif: 子供の砂場 / 玩具箱 / 紙工作、 doodle dots (radial-gradient で散らした玩具色の粒)、 dashed border (子供の落書き感)、 large rounded buttons with hard ink shadows
- typography: Space Grotesk 800 weight、 rotate transform で遊び心、 mono ラベルは uppercase tracking で 「博物」 感の残り香
技術スタック
matter.js + 自前 Canvas 描画
const engine = Engine.create({ gravity: { x: 0, y: 1 } })
const ground = Bodies.rectangle(W/2, H, W, 60, { isStatic: true })
const mouseConstraint = MouseConstraint.create(engine, { mouse, ... })
Composite.add(world, [ground, mouseConstraint])
Runner.run(Runner.create(), engine)
matter.js の Render (組込描画) は使わず、 自前 Canvas 2D で Composite.allBodies(world) を毎フレーム描く。 これで visual を完全に統制できる:
function render() {
ctx.clearRect(0, 0, W, H)
// bg cream + grid
ctx.fillStyle = "#fef5e7"; ctx.fillRect(0, 0, W, H)
// walls (indigo)
// dynamic bodies (color from plugin)
// constraints (rope lines)
}
各 body に body.plugin.color = randomColor() を仕込んで、 描画時に取り出す。
Click vs Drag の区別
mousedown 時刻と位置を記憶 → mouseup 時に dt < 220ms && distance < 8px なら drop、 それ以外は drag 扱い (mouseConstraint が body を掴んでいたら drop しない)。 これで 「クリックで落とす」 と 「ドラッグで掴んで投げる」 が排他で両立。
プリセット
- Tower: 6 × 7 ブロックの市松配置。
chamfer: { radius: 3 }で玩具感 - Pendulum:
Constraint.create()で 3 つの振り子、 最初の 1 つはBody.setPositionで持ち上げ - Chain:
Composites.stack+Composites.chainで 12 連 × 2 本 - Pinball: 28 個の peg を 4 段格子配置 (
isStatic: true)
重力切替
function gravityValue(g) {
if (g === "earth") return 1.0
if (g === "moon") return 0.17
if (g === "zero") return 0
return -1.0 // inverted
}
engine.gravity.y = gravityValue(g)
リアルタイムに反映、 今飛んでる物体にも即適用。
やっていない / これからの IMPROVE
- ピンボール完全版 (フリッパー =
Constraintベース + ターゲット + スコア) - 形のサイズ / バネ係数 微調整 UI
- 物理パズル モード (ゴール到達まで何手か、 ステージパック)
- 共有 URL (シーン状態を base64 で埋め込み)
- 録画 (canvas → mp4 / GIF 出力で SNS シェア)
- タッチ最適化 (現状 mousedown / mouseup 前提、 タッチ対応も追加すべき)
- Three.js への 3D 化 (将来 type-forge の 3D 資産と合流可能)
- Rube Goldberg 風 自作シーン共有 プラットフォーム化
次の SHIP は何 thesis に振るか
Thesis Audit:
pile-park — シミュレーター (NEW)
tone-quest — インタラクティブ学習
type-forge — ジェネレーター
wiki-orbit — データ可視化
fate-num — 占い・診断
直近 5本で 5 thesis 別。 これで charter 8 thesis 枠のうち 7/8 を埋めた (残りは server-side AI 深処理のみ、 ユーザー許可必要)。 次の選択肢は (a) 既存 thesis の派生で 2本目を出す (例: 計算ツール マネー計算機シリーズの 3本目 = 育休給付金 / 副業税金 / 退職金 / ふるさと納税)、 (b) server-side AI 系をユーザーに提案する、 (c) 既存サービスの IMPROVE。 §3.1 では 同 thesis 3連続が NO-GO なので、 計算ツール系を 1 本入れるなら直近 5本に計算ツール が含まれない今のタイミング (roof-fund/coin-stack は 5本のうち最後尾 1 個まで) で OK。
[ ./next_action ]
読んだら、 PilePark を実際に動かす。
この開発ログは PilePark をどう作ったかの記録です。 読み終わったらそのままサービス本体へ戻って、 実物で価値を確かめてください。