javascriptp5.jsmatter.js

How to freeze/stop bodies using pause button and resume bodies after in Matter.js?


I have created a pause button for a game called Pirate Invasion. I'm trying to pause/freeze bodies at their place and resume them from the position that they are in.

I have used setStatic and isStatic using the function mousePressed(). However, they both show an error Uncaught TypeError: Matter.Bodies.setStatic is not a function.

Am I missing out on something or is this got to do with saving the positions or something similar?

Boats are the bodies that have to "pause/freeze".

function mousePressed(playButton) { // this is my play button which works perfectly
  gameState = "play"
  World.remove(world, playButton)
  World.remove(world, playImage)
}

function mousePressed(pauseButton) { // pause button which gives an error
  Matter.Bodies.setStatic(boats, true)
}

Solution

  • First of all, World is deprecated so you should be using Composite.remove instead of World.remove.

    As for the error, setStatic is a Body method, not a Bodies method. You could loop over each body and call setStatic on it: boats.forEach(e => Matter.Body.setStatic(e, true));, but according to this issue, you won't be able to toggle staticness safely without implementing caching for the bodies or other workaround. See How do I make a matter body isStatic false after pressing a key in matter.js? for more potential options.

    Are you sure you want to implement pausing by setting bodies to static? This doesn't really pause the simulation--time keeps ticking and various properties might continue to change in unexpected ways.

    Since you're using your own renderer (p5.js), pausing the simulation as a whole is straightforward. You can stop calling your MJS code in the rendering loop and the simulation will pause. In p5.js, this can be done with a boolean check at the beginning of draw, or by setting draw temporarily to a function that doesn't run MJS updates (and probably instead shows a menu or "paused" screen). See Transitioning from one scene to the next with p5.js for details on that approach.

    Here's a basic example of toggling pause via an early return in the draw() loop based on the value of a checkbox:

    class Ball {
      constructor(x, y, r) {
        const options = {
          restitution: 0.1,
          density: 1.5,
          friction: 1,
        };
        this.body = Matter.Bodies.circle(x, y, r, options);
      }
    
      draw() {
        const {position: {x, y}, circleRadius: r} = this.body;
        fill("white");
        ellipse(x, y, r * 2);
      }
    }
    
    const engine = Matter.Engine.create();
    const balls = [...Array(20)].map((_, i) =>
      new Ball(
        50 + ~~(Math.random() * 450),
        50 + ~~(Math.random() * 150),
        ~~(Math.random() * 5) + 10,
      )
    );
    const walls = [
      Matter.Bodies.rectangle(
        250, 200, 500, 50, {isStatic: true}
      ),
      Matter.Bodies.rectangle(
        250, 0, 500, 50, {isStatic: true}
      ),
      Matter.Bodies.rectangle(
        0, 100, 50, 200, {isStatic: true}
      ),
      Matter.Bodies.rectangle(
        500, 100, 50, 200, {isStatic: true}
      ),
    ];
    const bodies = [...walls, ...balls.map(e => e.body)];
    Matter.Composite.add(engine.world, bodies);
    let checkbox;
    
    function setup() {
      checkbox = createCheckbox("running?", true);
      checkbox.position(0, 0);
      createCanvas(500, 200);
      noStroke();
    }
    
    function draw() {
      if (!checkbox.checked()) { // check for pause
        return;
      }
    
      background(30, 30, 30, 60);
      balls.forEach(e => e.draw());
      fill(160);
    
      for (const e of walls) {
        beginShape();
        e.vertices.forEach(({x, y}) => vertex(x, y));
        endShape();
      }
    
      if (random(2)) {
        Matter.Body.applyForce(
          balls[~~random(balls.length)].body,
          {
            x: random(0, 500),
            y: random(0, 200),
          },
          {
            x: random(-3, 3),
            y: random(-3, 3),
          }
        );
      }
    
      Matter.Engine.update(engine);
    }
    body {
      margin: 0;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.20.0/matter.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.4/p5.min.js"></script>

    If you want to use the mouse to pause/unpause, replace the checkbox with

    let running = true;
    
    function mousePressed() {
      running = !running;
    }
    

    and test running in the draw function:

    function draw() {
      if (!running) { // check for pause
        return;
      }
    
      // ... rerender ...
    }
    

    I'm not sure if your code is exactly as it is in your project, but as shown in the original post, the second function will overwrite the first if they're in the same scope.

    Another option in p5 is calling noLoop() to pause the draw loop and loop() to resume it. You can use isLooping() to figure out which to call.


    If you're using the built-in renderer, create a Runner and use runner.enabled to toggle pause. According to the docs for Matter.runner.stop():

    If you wish to only temporarily pause the engine, see engine.enabled instead.

    ...although I believe engine.enabled is a typo referring to runner.enabled. I have a PR that fixes this mistake.

    See this answer for an example of pausing the internal renderer.