ゲームメカニクス徹底解説

Pixel Balanceがどのように動いているのか、物理法則とゲームシステムの内側を開発者自身の言葉で詳しく解説します。単なる仕様書ではなく、「なぜこの数値にしたのか」「どのような試行錯誤を経たのか」まで含めた、物理パズルが好きな方に楽しんでいただけるドキュメントです。

1. 物理エンジンMatter.jsと重力設定

Pixel BalanceはJavaScript製の2D物理エンジン「Matter.js」を使用しています。Matter.jsは剛体シミュレーション、衝突検知、制約(ジョイント)処理などをすべて内蔵したライブラリで、ブラウザ上でそのまま動くため、追加のプラグインやネイティブコードを必要としません。

重力加速度はMatter.jsのデフォルト値(1.0単位)よりも若干強めに設定しています。これは、現実の9.8 m/s²に近い感覚ではなく、「落下の衝撃がしっかりと塔に伝わる」ゲーム的な手応えを生み出すための調整です。重力を強くしすぎると塔がすぐ崩れ、弱くすると緊張感がなくなる——このスイートスポットを見つけるまでに数十回のパラメータ調整を繰り返しました。

また、重力は「上から下」の一方向のみで、ゲーム中に変化しません。これにより、プレイヤーは常に同じ物理ルールの上で戦略を組み立てられ、運の要素を最小限に抑えられています。

2. 摩擦係数と反発係数(restitution)

ブロック表面の摩擦係数は0.4前後に設定されています。これは、ブロックが滑り落ちず積み上げやすい一方で、完全に滑らない(=積み上げが簡単すぎない)絶妙な数値です。もし摩擦を1.0にすると、どんなに傾いたブロックでも吸い付くように固定されてしまい、バランスゲームとしての面白さが失われます。逆に0.1にすると、真っ平らな面に置いたブロックでも勝手に滑り落ち、プレイが成立しません。

反発係数(restitution)は0に近い値を使っています。これは、ブロック同士が衝突しても跳ね返らず、ピクセルアートらしい「しっとり」とした着地感を実現するためです。高反発(例:0.8)だとボールのように跳ねてしまい、レトロゲーム的な重厚感が損なわれます。

ただし、厳密にゼロにすると完全非弾性衝突となり、今度は塔が揺れなくなって面白みが消えます。実際には0.05程度の微量な反発を許容することで、軽い揺れや小さな振動が発生するようにしています。この微調整がプレイフィールの肝です。

3. 衝突判定と接触イベント

Matter.jsの衝突システムは、broad-phase / narrow-phaseの2段階で動作します。broad-phaseでは、物体の境界ボックス(AABB)同士の交差を高速にチェックして、衝突の可能性がある組み合わせを絞り込みます。narrow-phaseでは、SAT(Separating Axis Theorem、分離軸定理)を用いて正確な衝突検知と接触点の計算を行います。

衝突が発生すると、Matter.jsはそれぞれのボディに対して衝突イベントを発行します。Pixel Balanceではこのイベントをフックして、以下の処理を実行します:

  • マッチ検出:衝突した2つのブロックが同じ色なら、その場所に接触ペアを記録し、以降の「同色グループ判定」の入力とします。
  • 効果音再生:衝突の相対速度に応じて、軽い着地音から重い衝撃音まで段階的に鳴らし分けます。
  • 振動フィードバック:モバイル環境では、Vibration APIを通じて短い振動を発生させます(対応端末のみ)。

4. マッチ3アルゴリズム

同色ブロックのマッチ検出は、本作の中で最も技術的に面白い部分のひとつです。基本的な流れは以下の通りです:

  1. 接触グラフの構築:物理エンジンの衝突ペアから、同色ブロックのみを抽出して無向グラフを作ります。
  2. 連結成分の抽出:深さ優先探索(DFS)またはUnion-Find法で連結成分を列挙します。
  3. 閾値判定:連結成分のサイズが3以上なら「マッチ成立」と判定し、該当ブロックを削除対象としてマークします。
  4. 削除とエフェクト:マークされたブロックはフェードアウトアニメーションの後にworldから削除され、同時にスコアボーナスを加算します。

ここで難しいのは、マッチ処理を毎フレーム実行するとCPU負荷が跳ね上がることです。Pixel Balanceでは、接触状態が変化した(新しい接触が発生した、または既存の接触が解消された)ときにのみマッチ検出を走らせることで、計算量を大幅に削減しています。

また、削除後に上のブロックが落下して新たなマッチを誘発する「連鎖」を検出するため、削除処理直後は短い遅延(約300ミリ秒)を挟んでから再度マッチ検出を走らせています。この遅延時間は、塔の再配置が自然に見える最小値を実験で割り出したものです。

5. ゲームオーバー判定

ゲームオーバーは「ブロックが画面下部の指定ラインよりも下に落ちた」ことで判定しています。厳密には、各フレームの終わりにすべてのアクティブなブロックの位置をチェックし、下端ライン以下にあるブロックを検出したらゲーム終了処理を呼び出す、というシンプルな実装です。

ただし、ブロックが一瞬だけラインを超えて戻ってくる(物理演算の過渡状態)こともあるため、実際には「ライン以下に3フレーム連続で存在する」ことを条件にしています。この工夫がないと、激しく揺れる塔で誤ゲームオーバーが頻発します。

6. スコアリングシステム

スコアは大きく3つの要素から構成されています。

  • 高さスコア:積み上げた塔の最高点に応じて、1ピクセルごとに加算されるベーススコア。
  • マッチボーナス:同色3つ以上のマッチが成立するたびに加算されるボーナス点。消えたブロック数が多いほどボーナスも増えます。
  • 連鎖ボーナス:マッチが連続して発生するたびに、連鎖回数に応じた倍率がかかります。2連鎖で1.5倍、3連鎖で2倍、というように報酬が急増します。

この3層構造により、「安定して積み上げるスタイル」と「マッチ狙いのアグレッシブスタイル」のどちらもハイスコアの可能性があり、プレイスタイルの多様性が担保されています。

7. レンダリングパイプライン

描画は、下層から順にレイヤー化された構造になっています。

  1. 背景レイヤー:グラデーションや装飾的な星のパーティクル。ゲーム進行に影響せず、視覚的な雰囲気を担当。
  2. ステッカーレイヤー:ユーザーが装着したステッカーが配置される装飾レイヤー。
  3. 物理レイヤー:ブロックそのものをレンダリング。Matter.jsのワールド情報をCanvas APIで描画。
  4. UIレイヤー:スコア、ランキング表示、ボタンなどのインターフェース要素。

物理レイヤーとUIレイヤーを分離することで、UIの更新が物理計算に影響しないようにしています。これは地味ですが、安定したゲーム体験を支える重要な設計です。

8. 乱数の扱い

次に落とすブロックの色と形状は、疑似乱数で決定されます。ただし、完全にランダムだとプレイヤーが「運が悪い」と感じやすいため、いくつかの補正を入れています。例えば、直前の数手で同じ色が3連続しないように重み付けしたり、長方形ばかりが連続しないようにしています。これにより、ランダム性を保ちつつ、理不尽な展開を減らしています。

← トップに戻る

Game Mechanics — A Deep Dive

A developer-written walkthrough of how Pixel Balance actually works under the hood — the physics laws, the algorithms, and the decisions that shaped them. This is more than a spec sheet: it explains why each number was chosen and what trade-offs happened along the way. If you like physics puzzles, I think you'll enjoy this.

1. Matter.js and Gravity

Pixel Balance is built on Matter.js, a JavaScript 2D physics engine that handles rigid body simulation, collision detection, and constraint solving entirely in the browser. No native plugins, no extra servers — just JavaScript.

Gravity is tuned slightly higher than the Matter.js default. The goal wasn't real-world accuracy (9.8 m/s²) but to make impacts feel weighty. Too much gravity and towers collapse instantly; too little and there's no tension. Finding the sweet spot took dozens of iterations of hand-tuning.

Gravity is also strictly one-directional (top to bottom) and never changes mid-game. This guarantees players always reason about the same physics, minimizing luck and maximizing skill expression.

2. Friction and Restitution

Block surface friction sits around 0.4 — high enough that blocks don't slide off each other casually, but low enough that placements still require precision. Crank friction to 1.0 and blocks glue themselves in place regardless of angle; drop it to 0.1 and even perfectly flat placements slip out from under you. Neither extreme is fun.

Restitution (bounciness) is kept near zero. Pixel Balance should feel heavy, not bouncy. At 0.8 restitution, every block would bounce like a basketball, killing the retro-game weightiness we want.

That said, exactly zero restitution creates perfectly inelastic collisions with no residual motion — which is boring. So restitution is set to roughly 0.05: a trace of bounce that lets towers sway subtly and keeps the world feeling alive. That tiny knob is a huge part of the game's feel.

3. Collision Detection and Contact Events

Matter.js uses a two-phase collision system: broad phase and narrow phase. The broad phase uses cheap AABB (axis-aligned bounding box) intersection tests to produce a short list of possible collisions. The narrow phase runs SAT (Separating Axis Theorem) on that short list to compute exact collision points and normals.

When a collision is confirmed, Matter.js fires a collision event on each participating body. Pixel Balance hooks into this event to do several things:

  • Match detection: if the two colliding blocks share a color, the contact pair is recorded as input for the "same-color group" analyzer.
  • Sound effects: impact volume and pitch scale with the relative velocity of the collision, giving everything from a soft "tap" to a heavy "thud."
  • Haptic feedback: on supported mobile devices, a brief vibration is fired through the Vibration API.

4. The Match-3 Algorithm

Same-color matching is one of the most interesting technical pieces of the game. The algorithm:

  1. Build the contact graph: filter physics collision pairs to only those where both bodies share a color, and treat them as edges in an undirected graph.
  2. Find connected components: use DFS or Union-Find to enumerate connected components of that graph.
  3. Apply the threshold: components with size ≥ 3 are flagged as a "match" and their blocks are queued for removal.
  4. Remove and animate: flagged blocks play a fade-out animation, then get removed from the physics world. Score is awarded at the same moment.

The catch is that running match detection every frame is expensive. Pixel Balance only re-runs the algorithm when the contact graph actually changes — a new contact forms, or an existing one breaks. This cuts the work by an order of magnitude.

To detect chain reactions — where blocks falling into the gap left by a cleared group trigger another match — the match detector is re-run after a short delay (~300 ms) following any removal. That delay was hand-tuned to the smallest value that still lets the tower "settle visibly" before the next check.

5. Game Over Detection

Game over is triggered when a block falls below the designated "death line" at the bottom of the screen. The implementation is simple: at the end of every physics step, the positions of all active blocks are checked, and any below the line counts as a game-over signal.

But blocks can briefly dip below the line as a transient of the physics solver and then bounce back up. To avoid false game-overs, a block must stay below the line for three consecutive frames before it counts. Without this, violently oscillating towers would trigger phantom game-overs all the time.

6. Scoring System

The score is composed of three ingredients.

  • Height score: a base score that ticks up per pixel of maximum tower height.
  • Match bonus: awarded whenever three or more same-colored blocks are cleared. Larger clears award larger bonuses.
  • Chain bonus: triggered when one match cascades into another, with a multiplier that grows with chain length (1.5× for a 2-chain, 2× for a 3-chain, and so on).

This three-layer structure means both "safe stacking" and "aggressive match hunting" can produce competitive scores, protecting the diversity of play styles.

7. Rendering Pipeline

Rendering is layered from the bottom up:

  1. Background layer: gradients and decorative star particles. Purely cosmetic; never affects gameplay.
  2. Sticker layer: player-selected decorative stickers that dress up the playfield.
  3. Physics layer: the blocks themselves, drawn by pulling state from Matter.js and rendering to Canvas.
  4. UI layer: score, leaderboard preview, buttons, and other interface elements.

Keeping the UI layer separate from the physics layer means UI updates never perturb physics calculations. It sounds minor, but it's a critical pillar of the game's stability.

8. Randomness and Weighting

The color and shape of the next block are chosen with a pseudo-random number generator, but not uniformly. Pure uniform randomness feels unfair — players notice and complain when the same color shows up three times in a row. Pixel Balance applies weight corrections: the recent history of colors and shapes biases the next draw away from repetition. This preserves the feel of randomness while removing the most frustrating patterns.

← Back to Top