Game Development 18 min read

How to Build a Water‑Pressure Ring H5 Game with CreateJS and Matter.js

This article walks through the design and implementation of a water‑pressure ring H5 game, covering technology selection, code architecture, integration of CreateJS with Matter.js physics, handling of ring rendering, needle collision detection, skill effects, performance optimizations, and practical tips for overcoming common challenges in 2D‑to‑3D simulation.

Aotu Lab
Aotu Lab
Aotu Lab
How to Build a Water‑Pressure Ring H5 Game with CreateJS and Matter.js

Technology Selection

A lightweight 2D stack was chosen to meet a one‑week research and two‑week development schedule. CreateJS provides simple 2D rendering, while Matter.js supplies gravity, collision and force handling.

Overall Code Layout

The game is organized with an object‑oriented structure. The main class Waterful exposes lifecycle methods and public APIs; Ring encapsulates each ring’s sprite and physics body.

<!-- index.html -->
<!-- Canvas placeholder (id="waterfulGameCanvas", 660×570) -->
// game.js
class Waterful {
  init() {}
  eventBinding() {}
  score() {}
  restart() {}
  pause() {}
  resume() {}
  skillX() {}
}

class Ring {
  update() {}
  afterCollision() {}
}
// main.js
const waterful = new Waterful();
waterful.init({/* parameters */});

Initialization

The init routine performs four steps: parameter setup, CreateJS display object layout, Matter.js rigid‑body layout, and event binding.

CreateJS + Matter.js Integration

Instead of using Matter.Render, each ring’s sprite is rendered by CreateJS. On every tick the sprite position is synchronized with its physics body.

createjs.Ticker.addEventListener('tick', e => {
  sprite.x = body.position.x;
  sprite.y = body.position.y;
});

A debug mode can enable Matter.Render to visualise bodies.

Ring Representation

Each ring consists of a CreateJS Sprite (visual texture) and a Matter.js circular body whose radius is slightly smaller than the texture radius. This creates a subtle overlap that improves visual stacking.

class Ring {
  constructor() {
    this.texture = new createjs.Sprite(...);
    this.body = Matter.Bodies.circle(x, y, r, {
      frictionAir: 0.02,
      restitution: 0.15
    });
  }
}

Texture Rotation and Position

Because CreateJS can only rotate in 2D, frame‑by‑frame animation simulates 3‑D rotation. The sprite’s rotation follows the body’s angle; when the ring becomes stationary the animation is paused.

const x1 = Math.round(texture.x);
const x2 = Math.round(body.position.x);
const y1 = Math.round(texture.y);
const y2 = Math.round(body.position.y);
if (x1 !== x2 || y1 !== y2) {
  texture.paused && texture.play();
  texture.rotation = body.angle * 180 / Math.PI;
} else {
  !texture.paused && texture.stop();
}
texture.x = body.position.x;
texture.y = body.position.y;

Stage Setup

The stage contains the physics world, background, walls and the needle.

Gravity Adjustment

engine.world.gravity.y = 0.2; // weaker gravity to mimic water

Device Orientation Control

window.addEventListener('deviceorientation', e => {
  let gamma = Math.max(-70, Math.min(70, e.gamma));
  engine.world.gravity.x = (gamma / 70) * 0.4; // sensitivity 0.4
});

Walls and Needle

Walls are offset by the difference between visual radius (R) and physics radius (r) so the sprite never protrudes. The needle is modelled as a rectangle plus a circle to match its outline.

Needle Insertion Logic

The core challenge is detecting when a ring successfully passes the needle. A visual‑offset technique uses a short detection line near the needle tip. Each tick the inner horizontal diameter of the ring is computed and tested for intersection with two vertical line segments.

Collision Detection Using a Detection Line

// Inside Ring.update(waterful)
const x0 = texture.x;
const y0 = texture.y;
const angle = texture.rotation; // degrees
const r = waterful.enlarging ? 16 * 1.5 : 16;
const startPoint = {
  x: x0 - r * Math.cos(angle * Math.PI / 180),
  y: y0 - r * Math.sin(angle * Math.PI / 180)
};
const endPoint = {
  x: x0 + r * Math.cos(-angle * Math.PI / 180),
  y: y0 + r * Math.sin(angle * Math.PI / 180)
};
const m = {x:206, y:216}, n = {x:206, y:400};
const u = {x:455, y:216}, v = {x:455, y:400};
if (segmentsIntr(startPoint, endPoint, m, n) ||
    segmentsIntr(startPoint, endPoint, u, v)) {
  this.afterCollision(waterful);
}

If the detection succeeds, the ring’s physics body is removed, the sprite is tweened to a flat frame, and it is moved under the needle.

// afterCollision implementation
createjs.Tween.get(this.texture).to({y: targetY}, duration);
Matter.World.remove(waterful.engine.world, this.body);
this.body = null;
// Simplified post‑collision update
this.update = function() {
  const tex = this.texture;
  if (tex.currentFrame === 0) {
    tex.gotoAndStop(0);
  } else if (frameCount % 5 === 0) {
    tex.prevFrame();
  }
  // Align sprite with needle (example offsets)
  if (tex.x < 200) tex.x++;
  else if (tex.x > 213 && tex.x < 300) tex.x--;
  else if (tex.x > 462) tex.x--;
  else if (tex.x > 400 && tex.x < 448) tex.x++;
  // Gradually rotate to 0°
  let rot = Math.round(tex.rotation) % 180;
  if (rot < 0) rot += 180;
  if (rot > 0 && rot <= 90) tex.rotation = rot - 1;
  else if (rot > 90 && rot < 180) tex.rotation = rot + 1;
  else if (frame === 0) this.update = function() {};
};
waterful.score();

Insertion Conditions

The ring must reach the needle tip.

The sprite’s current frame must be within the first six vertical frames (when the ring is near the top).

Rotation must satisfy 0 ≤ rotation ≤ 65 or 115 ≤ rotation ≤ 180 degrees.

Optimizations

Resource Pool for Bubble Sprites

To avoid creating a new sprite for every bubble animation, an idle‑sprite pool is maintained. When a bubble is needed the pool is checked first; if empty a new sprite is created. Sprites are returned to the pool on animationend.

// In Waterful object
getBubble = throttle(function() {
  if (this._idleBubbles.length) return this._idleBubbles.shift();
  const bubble = new createjs.Sprite(...);
  bubble.on('animationend', () => {
    this._stage.removeChild(bubble);
    this._idleBubbles.push(bubble);
  });
  return bubble;
}, 300);

Handling Fast Ring Speed (CCD Issue)

Because Matter.js lacks continuous collision detection, a ring moving too fast can tunnel through walls. Two mitigations are applied:

Throttle the force‑applying button to once per 300 ms.

Set a flag on touch start and apply the force only once per physics tick.

// Button handling
btn.addEventListener('touchstart', e => {
  this.addForce = true;
});
Events.on(this._engine, 'beforeUpdate', e => {
  if (!this.addForce) return;
  this.addForce = false;
  this._rings.forEach(ring => {
    Matter.Body.applyForce(ring.body, {x, y}, {x:0.02, y:-0.03});
    Matter.Body.setAngularVelocity(ring.body, Math.PI/24);
  });
});

Conclusion

The workflow demonstrates how to combine CreateJS rendering with Matter.js physics, model rings with offset bodies, detect needle insertion via a detection line, and apply practical performance optimizations such as sprite pooling and tick‑based force control. These techniques are applicable to other HTML5 physics‑driven games.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

OptimizationH5CreateJSMatter.js
Aotu Lab
Written by

Aotu Lab

Aotu Lab, founded in October 2015, is a front-end engineering team serving multi-platform products. The articles in this public account are intended to share and discuss technology, reflecting only the personal views of Aotu Lab members and not the official stance of JD.com Technology.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.