'Implementing bullet collisions in Matter.js for a shooting game
I am trying to make a shooting game in matter.js but can't find a way to shoot bullets from the player's exact location and how to count the collision between player and bullet but not with the walls.
I want to fire a bullet from player1 and then on pressing D again it should fire another bullet from the player1's last position.
let p1= Matter.Bodies.polygon(200, 200, 3, 40, {
chamfer: {
radius: [15,10,15]
},
isStatic: false,
inertia: Infinity,
friction: 0.9,
render: {
fillStyle: '#F9ED69'
},
mass:1
});
let p2 = Matter.Bodies.polygon(1100, 200, 3, 40, {
chamfer: {
radius: [15,10,15]
},
isStatic: false,
inertia: Infinity,
friction: 0.9,
render: {
fillStyle: '#11999E'
},
mass:1
});
let bullet1 = Matter.Bodies.polygon(400, 300, 3, 7, {
chamfer: {
radius: [4,2,4]
},
isStatic: false,
inertia: Infinity,
friction: 0.9,
render: {
fillStyle: '#F9ED69'
},
mass:0
});
const keyHandlers = {
KeyS: () => {
Matter.Body.applyForce(p1, {
x: p1.position.x,
y: p1.position.y
}, {x: 0.0, y: 0.001})
},
KeyW: () => {
Matter.Body.applyForce(p1, {
x: p1.position.x,
y: p1.position.y
}, {x: 0.0, y: -0.002})
},
KeyD:()=>{
Matter.Body.applyForce(bullet1, {
x: p1.position.x,
y: p1.position.y
}, {x: 0.001, y: 0.0})
},
};
const keysDown = new Set();
document.addEventListener("keydown", event => {
keysDown.add(event.code);
});
document.addEventListener("keyup", event => {
keysDown.delete(event.code);
});
Matter.Events.on(engine, "beforeUpdate", event => {
[...keysDown].forEach(k => {
keyHandlers[k]?.();
});
});
// on collision of a bullet with wall and other bodies remove the bullet from the world after some delay and add the score
let score1 = 0;
let score2 = 0;
let health
Matter.Events.on(engine, "collisionStart", event => {
for (let i = 0; i < event.pairs.length; i++) {
const pair = event.pairs[i];
if (pair.bodyA === bullet1 || pair.bodyB === bullet1) {
Matter.World.remove(engine.world, bullet1);
alert('1');
}
if (pair.bodyA === bullet2 || pair.bodyB === bullet2) {
Matter.World.remove(engine.world, bullet2);
alert('2');
}
}
score1++;
alert(`SCore1 is ${score1}`); // these alerts are just to confirm the collision
});
Solution 1:[1]
You're on the right track, but if you hardcode bullet1 and bullet2 you're stuck with just those two bullets. Even with a fixed number of bullets and re-using the bodies (good for performance but maybe premature optimization), I'd probably use an array to store these bullets, which is almost always the correct move after you catch yourself doing thing1, thing2...
Here's a proof of concept. I'm creating and destroying bullets here to keep the coding easier, but it'd be more performant to keep a pool of objects and re-use/re-position them.
I'm also using sets to keep track of the types of the bodies, but you might want to use labels. Most of the code here could go in many different directions, specific to your use case.
const engine = Matter.Engine.create();
engine.gravity.y = 0; // enable top-down
const map = {width: 300, height: 300};
const render = Matter.Render.create({
element: document.body,
engine,
options: {...map, wireframes: false},
});
const player = {
score: 0,
body: Matter.Bodies.polygon(
map.width / 2, map.height / 2, 3, 15, {
frictionAir: 0.06,
density: 0.9,
render: {fillStyle: "red"},
},
),
lastShot: Date.now(),
cooldown: 150,
fireForce: 0.1,
rotationAngVel: 0.03,
rotationAmt: 0.03,
rotateLeft() {
Matter.Body.rotate(this.body, -this.rotationAmt);
Matter.Body.setAngularVelocity(
this.body, -this.rotationAngVel
);
},
rotateRight() {
Matter.Body.rotate(this.body, this.rotationAmt);
Matter.Body.setAngularVelocity(
this.body, this.rotationAngVel
);
},
fire() {
if (Date.now() - this.lastShot < this.cooldown) {
return;
}
// move the bullet away from the player a bit
const {x: bx, y: by} = this.body.position;
const x = bx + (Math.cos(this.body.angle) * 10);
const y = by + (Math.sin(this.body.angle) * 10);
const bullet = Matter.Bodies.circle(
x, y, 4, {
frictionAir: 0.006,
density: 0.1,
render: {fillStyle: "yellow"},
},
);
bullets.add(bullet);
Matter.Composite.add(engine.world, bullet);
Matter.Body.applyForce(
bullet, this.body.position, {
x: Math.cos(this.body.angle) * this.fireForce,
y: Math.sin(this.body.angle) * this.fireForce,
},
);
this.lastShot = Date.now();
},
};
const bullets = new Set();
const makeEnemy = () => Matter.Bodies.polygon(
(Math.random() * (map.width - 40)) + 20,
(Math.random() * (map.height - 40)) + 20,
5, 6, {
render: {
fillStyle: "transparent",
strokeStyle: "white",
lineWidth: 1,
},
},
);
const enemies = new Set([...Array(100)].map(makeEnemy));
const walls = new Set([
Matter.Bodies.rectangle(
0, map.height / 2, 20, map.height, {isStatic: true}
),
Matter.Bodies.rectangle(
map.width / 2, 0, map.width, 20, {isStatic: true}
),
Matter.Bodies.rectangle(
map.width, map.height / 2, 20, map.height, {isStatic: true}
),
Matter.Bodies.rectangle(
map.width / 2, map.height, map.width, 20, {isStatic: true}
),
]);
Matter.Composite.add(engine.world, [
player.body, ...walls, ...enemies
]);
const keyHandlers = {
ArrowLeft: () => player.rotateLeft(),
ArrowRight: () => player.rotateRight(),
Space: () => player.fire(),
};
const validKeys = new Set(Object.keys(keyHandlers));
const keysDown = new Set();
document.addEventListener("keydown", e => {
if (validKeys.has(e.code)) {
e.preventDefault();
keysDown.add(e.code);
}
});
document.addEventListener("keyup", e => {
if (validKeys.has(e.code)) {
e.preventDefault();
keysDown.delete(e.code);
}
});
Matter.Events.on(engine, "beforeUpdate", event => {
[...keysDown].forEach(k => {
keyHandlers[k]?.();
});
if (enemies.size < 100 && Math.random() > 0.95) {
const enemy = makeEnemy();
enemies.add(enemy);
Matter.Composite.add(engine.world, enemy);
}
});
Matter.Events.on(engine, "collisionStart", event => {
for (const {bodyA, bodyB} of event.pairs) {
const [a, b] = [bodyA, bodyB].sort((a, b) =>
bullets.has(a) ? -1 : 1
);
if (bullets.has(a) && walls.has(b)) {
Matter.Composite.remove(engine.world, a);
bullets.delete(a);
}
else if (bullets.has(a) && enemies.has(b)) {
Matter.Composite.remove(engine.world, a);
Matter.Composite.remove(engine.world, b);
bullets.delete(a);
enemies.delete(b);
document.querySelector("span").textContent = ++player.score;
}
}
});
Matter.Render.run(render);
Matter.Runner.run(Matter.Runner.create(), engine);
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
<div>press left/right arrow keys to rotate and space to shoot</div>
<div>score: <span>0</span></div>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
