この記事では、2D物理エンジン「Matter.js」を使ってブラウザで動く物理ゲームを作る方法を解説します。内容は机上の紹介ではなく、当サイトで公開中の物理パズルゲーム「Pixel Balance」を実際に開発・運用した経験に基づくものです。JavaScriptの基本文法が分かる方なら、この記事だけで「ブロックが落ちて積み重なる」ところまで到達できる構成にしています。
最終更新: 2026年8月11日
Matter.jsは、JavaScriptで書かれたオープンソースの2D物理エンジンです。剛体(変形しない物体)どうしの衝突、重力による落下、摩擦や反発といった物理現象を、毎フレーム自動で計算してくれます。ライブラリを読み込んで「幅100・高さ50の長方形をここに置く」と宣言するだけで、あとはエンジンが勝手に落として、ぶつけて、転がしてくれる――というのが基本的な世界観です。
物理エンジンを自作すると、衝突判定・貫通防止・回転の計算だけで膨大な作業になります。Matter.jsはこれらを引き受けてくれるため、開発者はゲームのルール作りに集中できます。導入はCDNから1行で済みます。
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
Matter.jsの主要モジュールは4つ覚えれば十分です。Engine(物理計算の本体)、Runner(計算を毎フレーム回す装置)、Bodies(物体を作る工場)、Composite(物体をワールドに追加する入れ物)です。
const { Engine, Render, Runner, Bodies, Composite } = Matter;
// 1. エンジン(物理世界)を作る
const engine = Engine.create();
// 2. 描画装置を作る(開発中は組み込みのRenderが手軽)
const render = Render.create({
element: document.body,
engine: engine,
options: { width: 400, height: 600, wireframes: false }
});
// 3. 物体を作る: Bodies.rectangle(x, y, 幅, 高さ, オプション)
const box = Bodies.rectangle(200, 50, 80, 40);
const ground = Bodies.rectangle(200, 580, 400, 40, { isStatic: true });
// 4. ワールドに追加して実行
Composite.add(engine.world, [box, ground]);
Render.run(render);
Runner.run(Runner.create(), engine);
これだけで、箱がひとつ落下して地面に着地します。座標は「中心点」を指定する点に注意してください(左上基準ではありません)。
上のコードで地面に指定した isStatic: true は「物理法則の影響を受けない固定物体」を意味します。地面や壁のほか、実はゲームの演出にも使えます。
Pixel Balanceでは、画面上部で左右に揺れている「落下前のブロック」を静的ボディとして生成しています。静的なので重力で落ちず、位置を毎フレーム書き換えることで揺れを演出できます。プレイヤーがタップした瞬間に Body.setStatic(block, false) で動的ボディへ切り替えると、そこから物理エンジンの管理下に入って落下が始まります。
// 落下待ちブロック:静的として生成し、位置を手動で動かす
const block = Bodies.rectangle(x, y, w, h, { isStatic: true });
// タップされたら物理エンジンに引き渡す
function drop() {
Matter.Body.setStatic(block, false); // ここから先はエンジン任せ
}
「操作中は手動制御、離した瞬間から物理任せ」という切り替えは、物理ゲーム全般で使い回せる基本パターンです。
ゲームの「手触り」は、ほぼ3つのパラメータで決まります。
engine.world.gravity.y で世界全体の重力を設定します。既定値は1.0。値を上げると落下が速くなり、キビキビした操作感になる一方、着地の衝撃も強くなります。Pixel Balanceでは1.2に設定し、テンポと安定性のバランスを取っています。engine.world.gravity.y = 1.2;
const block = Bodies.rectangle(x, y, w, h, {
friction: 0.8, // 積んだときにズレにくく
restitution: 0.05 // 着地でほぼ弾まない
});
パラメータ調整に「正解」はありません。おすすめは、値を一つだけ変えて30秒遊ぶ、を繰り返すことです。複数を同時に変えると、どの変更が効いたのか分からなくなります。
「ブロックが着地した」「同じ色が触れ合った」といったゲームルールは、衝突イベントを入り口に実装します。Matter.jsでは Events.on でエンジンにリスナーを登録します。
const { Events } = Matter;
Events.on(engine, 'collisionStart', (event) => {
for (const pair of event.pairs) {
// pair.bodyA と pair.bodyB が衝突した2物体
console.log(pair.bodyA.label, 'と', pair.bodyB.label, 'が接触');
}
});
実装上の注意点がひとつあります。衝突イベントのコールバック内で重い処理(全物体の走査など)を直接実行すると、物理計算のステップを圧迫してカクつきの原因になります。Pixel Balanceでは、衝突時には「チェックが必要」というフラグを立てるだけにして、実際の判定処理は物理更新後の afterUpdate イベントで1回だけ実行する方式にしています。同一フレームに衝突が10件起きても、判定処理は1回で済みます。
let needsCheck = false;
Events.on(engine, 'collisionStart', () => { needsCheck = true; });
Events.on(engine, 'afterUpdate', () => {
if (needsCheck) { runGameLogic(); needsCheck = false; }
});
組み込みの Matter.Render は手軽ですが、見た目の自由度に限界があります。ブロックの角を消えるアニメーションで縮ませたい、背景に模様を敷きたい、といった演出をやり始めると、自前のCanvas描画に移行することになります。
考え方はシンプルで、物理計算はMatter.jsに任せ、描画は毎フレーム自分で行うという分業です。エンジンが持つ各物体の位置(body.position)と角度(body.angle)を読み取り、Canvasに描くだけです。
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const bodies = Matter.Composite.allBodies(engine.world);
for (const body of bodies) {
ctx.save();
ctx.translate(body.position.x, body.position.y);
ctx.rotate(body.angle);
const w = body.bounds.max.x - body.bounds.min.x;
const h = body.bounds.max.y - body.bounds.min.y;
ctx.fillStyle = body.render.fillStyle || '#999';
ctx.fillRect(-w / 2, -h / 2, w, h);
ctx.restore();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
この方式ならMatter.Renderは不要になり、消滅アニメーションや光沢のハイライトなど、好きな演出を差し込めます。Pixel Balanceの描画もこの構成です。
物理ゲームの処理落ちは体験を直撃します。スマートフォンでも60fpsを保つために効いたのは、次のような地味な工夫でした。
frameCounter++ % 30 === 0)で簡単に書けます。Composite.remove(または World.remove)でワールドから取り除いてください。「見えないだけでワールドに残っている」物体は典型的なパフォーマンスバグです。setInterval での描画はタブが非アクティブでも走り続け、ブラウザのフレームタイミングともずれます。描画ループは必ず requestAnimationFrame で書きましょう。ブラウザゲームのアクセスの多くはスマートフォンからです。PCで完成させてからスマホ対応を始めると手戻りが大きいので、最初から次の点を押さえておくことをおすすめします。
click と touchstart の両方を待ち受けます。タッチ端末では1回のタップで両イベントが発火することがあるため、touchstart 側で event.preventDefault() を呼んで二重発火を防ぎます。Matter.jsを使えば、物理計算の難所を丸ごとエンジンに任せて、数百行のJavaScriptで物理ゲームを完成させられます。押さえるべき要点は、(1) 静的/動的ボディの切り替えで操作を作る、(2) friction・restitution・gravityの3つで手触りを調整する、(3) 衝突イベントは軽く保つ、(4) 描画は自前Canvasに分離する、の4点です。
実際にこの構成で作られたゲームがどう動くかは、Pixel Balance本体で確認できます。また、本記事で触れたパラメータ設計の背景は仕組み解説で、開発全体の意思決定の記録は開発コラムで詳しく書いています。
← トップに戻る