Yup, like a particle system? That’s what i currently have. Its mostly working as expected. Basically im trying to create a pillow that has deformation around surface contact edges. However i get a lot of particle penetration occurring which doesn’t restore to the original shape. I’m currently trying to find a way to ensure the shape returns to its original shape, currently without success. … Sub poly tessellation maybe …

class Player {
constructor(scene, x, y) {
this.scene = scene;
const Matter = Phaser.Physics.Matter.Matter;
const { Composites, Composite, Body, Bodies, Constraint } = Matter;
const particleOptions = {
friction: 0.05,
frictionStatic: 0.5,
render: { visible: false },
restitution: 1.0
};
const constraintOptions = {
render: { visible: false },
stiffness: 0.2,
damping: 0.06,
angularStiffness: 0.1
};
// Create the soft body
this.softBody = Composites.softBody(x, y, 4, 4, 10, 10, true, 9, particleOptions, constraintOptions);
scene.matter.world.add(this.softBody);
// Adjust the stiffness for the outer particles
Composite.allConstraints(this.softBody).forEach(constraint => {
const { bodyA, bodyB } = constraint;
const isOuterA = this.isOuterParticle(bodyA.position, x, y, 4, 4);
const isOuterB = this.isOuterParticle(bodyB.position, x, y, 4, 4);
if (isOuterA || isOuterB) {
constraint.stiffness = 0.3;
}
});
// Create a graphics object for the character mesh.
this.cube = scene.add.graphics({ fillStyle: { color: 0x0000ff } });
this.updateCubeGraphics(); // Initial call to set graphics
// Initialize graphics for debugging and make circles visible
this.graphics = this.softBody.bodies.map(part => {
const circle = scene.add.circle(part.position.x, part.position.y, 3, 0x00ff00);
circle.setAlpha(1);
return circle;
});
// Create a target cube for the camera to follow
this.targetCube = scene.add.rectangle(x, y, 10, 10, 0xff0000);
this.targetCube.setAlpha(0);
this.canJump = true;
this.isOnGround = false;
this.lastKeyTime = {
left: 0,
right: 0
};
this.keyHeld = {
left: false,
right: false
};
this.isBoosting = false;
this.doubleTapDelay = 250;
scene.matter.world.setGravity(0, 1.1);
scene.matter.world.on('collisionactive', this.handleCollision, this);
// Debugging information
console.log('Player created at:', x, y);
}
isOuterParticle(position, x, y, rows, cols) {
const margin = 15;
const left = x - margin;
const right = x + (cols - 1) * 10 + margin;
const top = y - margin;
const bottom = y + (rows - 1) * 10 + margin;
return (
position.x <= left || position.x >= right || position.y <= top || position.y >= bottom
);
}
handleCollision(event) {
event.pairs.forEach(pair => {
const { bodyA, bodyB } = pair;
if (this.softBody.bodies.includes(bodyA) || this.softBody.bodies.includes(bodyB)) {
if (bodyA.isStatic || bodyB.isStatic) {
this.isOnGround = true;
}
}
});
}
checkDoubleTap(direction) {
const currentTime = this.scene.time.now;
if (currentTime - this.lastKeyTime[direction] < this.doubleTapDelay && !this.keyHeld[direction]) {
this.isBoosting = true;
this.lastKeyTime[direction] = 0;
} else {
this.isBoosting = false;
}
this.lastKeyTime[direction] = currentTime;
}
update(cursors) {
const { Body } = Phaser.Physics.Matter.Matter;
const baseSpeed = 2.5;
const speedBoost = 1.6;
const baseJumpVelocity = -11;
let playerSpeed = baseSpeed;
let jumpVelocity = baseJumpVelocity;
if (cursors.left.isDown) {
if (!this.keyHeld.left) {
this.checkDoubleTap('left');
}
this.keyHeld.left = true;
} else {
this.keyHeld.left = false;
}
if (cursors.right.isDown) {
if (!this.keyHeld.right) {
this.checkDoubleTap('right');
}
this.keyHeld.right = true;
} else {
this.keyHeld.right = false;
}
if (cursors.shift.isDown || this.isBoosting) {
playerSpeed *= speedBoost;
this.cube.fillStyle(0xff0000); // Set fill color to red for speed boost
} else {
this.cube.fillStyle(0x0000ff); // Set fill color to blue when not boosting
}
if (cursors.left.isDown) {
this.softBody.bodies.forEach(part => Body.setVelocity(part, { x: -playerSpeed, y: part.velocity.y }));
} else if (cursors.right.isDown) {
this.softBody.bodies.forEach(part => Body.setVelocity(part, { x: playerSpeed, y: part.velocity.y }));
} else {
this.softBody.bodies.forEach(part => Body.setVelocity(part, { x: 0, y: part.velocity.y }));
}
if ((cursors.space.isDown || cursors.up.isDown) && this.isOnGround && this.canJump) {
this.softBody.bodies.forEach(part => Body.setVelocity(part, { x: part.velocity.x, y: jumpVelocity }));
if (cursors.shift.isDown || this.isBoosting) {
this.softBody.bodies.forEach(part => Body.setVelocity(part, { x: part.velocity.x * speedBoost, y: part.velocity.y }));
}
this.canJump = false;
this.isOnGround = false;
}
if (!cursors.space.isDown && !cursors.up.isDown) {
this.canJump = true;
}
if (!cursors.left.isDown && !cursors.right.isDown) {
this.isBoosting = false;
}
// Update the graphics positions
this.softBody.bodies.forEach((part, index) => {
this.graphics[index].setPosition(part.position.x, part.position.y);
});
// Update the target cube position
const targetPosition = this.softBody.bodies[4].position;
this.targetCube.setPosition(targetPosition.x, targetPosition.y);
// Update the cube graphics without distortion
this.updateCubeGraphics();
}
updateCubeGraphics() {
this.cube.clear();
this.cube.fillStyle(0x0000ff, 1.0);
// Get positions of the soft body vertices
const positions = this.softBody.bodies.map(body => ({ x: body.position.x, y: body.position.y }));
// Draw individual small cubes for the polygon mesh structure.
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
const topLeft = positions[i * 4 + j];
const topRight = positions[i * 4 + j + 1];
const bottomRight = positions[(i + 1) * 4 + j + 1];
const bottomLeft = positions[(i + 1) * 4 + j];
this.cube.beginPath();
this.cube.moveTo(topLeft.x, topLeft.y);
this.cube.lineTo(topRight.x, topRight.y);
this.cube.lineTo(bottomRight.x, bottomRight.y);
this.cube.lineTo(bottomLeft.x, bottomLeft.y);
this.cube.closePath();
this.cube.fillPath();
}
}
// Log position for debugging
const centerX = positions.reduce((sum, pos) => sum + pos.x, 0) / positions.length;
const centerY = positions.reduce((sum, pos) => sum + pos.y, 0) / positions.length;
console.log('Cube position:', centerX, centerY);
}
}
window.Player = Player;