reactjsgame-developmentgame-physicsp5.jsmatter.js

How to reset ball position if it goes out of bounds, using p5.js and matter.js?


I want to reset the ball back to its initial position if it flies off the screen, or if its position.y < 0 (I'll add in other boundary conditions once this is figured out).

I tried simulating that by removing the ball if it flies off (because I couldn't find a way to reset the position).

But that doesn't remove the circle even though other elements stop colliding with it. It only freezes the circle, which I think is a p5 issue.

Also the test console.log statement I have, logging the y coordinate, keeps infinitely logging a y-coordinate.

CODE:

This is the Ball class.

import Matter from 'matter-js'

export default class Ball {
    constructor(x, y, r, world) {
        this.body = Matter.Bodies.circle(x, y, r);
        Matter.World.add(world, this.body);
        this.r = r;
    }

    show(p) {
        const pos = this.body.position;
        const angle = this.body.angle;
        p.push();
        p.translate(pos.x, pos.y);
        p.rotate(angle);
        p.rectMode(p.CENTER);
        p.fill("white");
        p.strokeWeight(0);
        p.circle(0, 0, this.r);
        p.pop();
    }
}

This is the final App component where everything gets rendered:

    import React, { useRef, useEffect } from 'react';
import p5 from 'p5';
import Matter from 'matter-js'
import './App.css'
import Ball from "./components/Ball.jsx";
import Boundary from "./components/Boundary.jsx";

let Engine = Matter.Engine;
let World = Matter.World;
let Mouse = Matter.Mouse;
let MouseConstraint = Matter.MouseConstraint;
let ground, box, ball, world, engine, mCon, leftWall, rightWall;

function sketch(p) {
    p.setup = function() {
        const canvas = p.createCanvas(p.windowWidth, p.windowHeight);
        engine = Engine.create();
        world = engine.world;

        const canvasMouse = Mouse.create(canvas.elt);
        canvasMouse.pixelRatio = p.pixelDensity();
        const options = {
            mouse: canvasMouse,
        }
        mCon = MouseConstraint.create(engine, options);

        ground = new Boundary(p.width/2, p.height-10, p.width, 20, world);
        leftWall = new Boundary(10, p.height/2, 20, p.height, world);
        rightWall = new Boundary(p.width - 10, p.height/2, 20, p.height, world);
        ball = new Ball(50, p.height - 100, 25, world);

        World.add(world, [mCon,
        ]);
    }

    p.windowResized = function() {
        p.resizeCanvas(p.windowWidth, p.windowHeight);
    }

    p.draw = function () {
        p.background(51);
        Engine.update(engine);
        rightWall.show(p);
        leftWall.show(p);
        ground.show(p);
        ball.show(p);

        if (ball.body.position.y < 0) {
            console.log(ball.body.position.y);
            World.remove(world, ball.body);
        }
    }
}

function App() {
    const p5Container = useRef();

    useEffect(() => {
        const p5Instance = new p5(sketch, p5Container.current);

        return() => {
            p5Instance.remove();
        }
    }, []);

    return (
        <div ref={p5Container} className="container">
        </div>
    );
}

export default App;

DEMONSTRATION OF ERROR

You can see the white ball stuck at the top unaffected by gravity, and y position continuously being logged.

error demo

How do I go about solving this problem?


Solution

  • You're removing the Matter.js body from the MJS world, but your Ball instance still exists, as does its .body property, which still references the MJS body. This body still exists in a detached state and can still be drawn with P5. With World.remove(), you've taken it out of the simulation, but not the visualization--the two are out of sync.

    Instead of World.remove(world, ball.body); try Matter.Body.setPosition(ball.body, 50, p.height - 100) to move it back to its origin, if that's your goal. You may also want to use setAngle, setVelocity and setSpeed to set default values on the body.

    It's also possible to remove the ball as you're doing and add a new one back at its origin, the same way you created the old one:

    this.body = Matter.Bodies.circle(x, y, r);
    Matter.World.add(world, this.body);
    

    This would probably be a method in the Ball class, and you may need to make x and y properties on this., like this.originalX, this.originalY, so you can refer to them outside the constructor.

    As an aside, World is deprecated and replaced by Matter.Composite.