line sensors now drawn correctly and work with matter.js physics system
parent
c7a0c9c834
commit
938d5790ae
5
game.js
5
game.js
|
|
@ -118,10 +118,7 @@ function gameLoop() {
|
||||||
gameWorld.update();
|
gameWorld.update();
|
||||||
gameWorld.draw(ctx);
|
gameWorld.draw(ctx);
|
||||||
|
|
||||||
for (let id in robots) {
|
|
||||||
robots[id].update(ctx, gameWorld);
|
|
||||||
gameWorld.checkAndResolveCollision(robots[id]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
requestAnimationFrame(gameLoop);
|
requestAnimationFrame(gameLoop);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
gameworld.js
104
gameworld.js
|
|
@ -5,7 +5,7 @@ export class GameWorld {
|
||||||
|
|
||||||
this.engine = Matter.Engine.create();
|
this.engine = Matter.Engine.create();
|
||||||
this.world = this.engine.world;
|
this.world = this.engine.world;
|
||||||
this.engine.world.gravity.y = 0.01;
|
this.engine.world.gravity.y = 0;//0.01;
|
||||||
Matter.Runner.run(this.engine);
|
Matter.Runner.run(this.engine);
|
||||||
|
|
||||||
let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
|
let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
|
||||||
|
|
@ -15,19 +15,21 @@ export class GameWorld {
|
||||||
this.obstacles = [];
|
this.obstacles = [];
|
||||||
this.robots = [];
|
this.robots = [];
|
||||||
|
|
||||||
|
|
||||||
this.addObstacle([
|
this.addObstacle([
|
||||||
{ x: 80 + 100, y: 0 }, // Vertex 1
|
{ x: -100, y: -100 }, // Vertex 1
|
||||||
{ x: 250 + 100, y: 370 }, // Vertex 2
|
{ x: 100, y: -100 }, // Vertex 2
|
||||||
{ x: 200 + 100, y: 370 }, // Vertex 3
|
{ x: 100, y: 100 }, // Vertex 3
|
||||||
{ x: 150 + 100, y: 200 } // Vertex 4
|
{ x: -100, y: 100 } // Vertex 4
|
||||||
]);
|
], { x: 400, y: 300 });
|
||||||
|
|
||||||
|
|
||||||
this.addObstacle([
|
this.addObstacle([
|
||||||
{ x: 300, y: 380 }, // Vertex 1
|
{ x: 300, y: 380 }, // Vertex 1
|
||||||
{ x: 420, y: 380 }, // Vertex 2
|
{ x: 420, y: 380 }, // Vertex 2
|
||||||
{ x: 350, y: 550 }, // Vertex 3
|
{ x: 350, y: 550 }, // Vertex 3
|
||||||
{ x: 280, y: 420 } // Vertex 4
|
{ x: 280, y: 420 } // Vertex 4
|
||||||
]);
|
], { x: 400, y: 0 });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,14 +37,19 @@ export class GameWorld {
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
// Update the physics simulation
|
// Update the physics simulation
|
||||||
|
|
||||||
|
for (let id in this.robots) {
|
||||||
|
this.robots[id].update(this);
|
||||||
|
}
|
||||||
|
|
||||||
Matter.Engine.update(this.engine);
|
Matter.Engine.update(this.engine);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addObstacle(vertices) {
|
addObstacle(vertices, position = { x: 0, y: 0 }) {
|
||||||
// Convert the polygon points into a Matter.js body
|
// Convert the polygon points into a Matter.js body
|
||||||
let body = Matter.Bodies.fromVertices(-30, 300, [vertices], {
|
let body = Matter.Bodies.fromVertices(position.x, position.y, [vertices], {
|
||||||
isStatic: true, // Obstacles shouldn't move
|
isStatic: true, // Obstacles shouldn't move
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -56,12 +63,14 @@ export class GameWorld {
|
||||||
// Create the robot's Matter.js body
|
// Create the robot's Matter.js body
|
||||||
let robotBody = Matter.Bodies.fromVertices(robot.x, robot.y, [robot.hull], {
|
let robotBody = Matter.Bodies.fromVertices(robot.x, robot.y, [robot.hull], {
|
||||||
friction: 0.05,
|
friction: 0.05,
|
||||||
|
frictionAir: 0.02, // Air resistance slows it down naturally
|
||||||
restitution: 0.2, // Slight bounce
|
restitution: 0.2, // Slight bounce
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to the world
|
// Add to the world
|
||||||
Matter.World.add(this.world, robotBody);
|
Matter.World.add(this.world, robotBody);
|
||||||
this.robots.push(robotBody);
|
robot.body = robotBody;
|
||||||
|
this.robots.push(robot);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the floor color based on (x, y) coordinates
|
// Return the floor color based on (x, y) coordinates
|
||||||
|
|
@ -86,9 +95,11 @@ export class GameWorld {
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.robots.forEach(body => {
|
this.robots.forEach(robot => {
|
||||||
|
let body = robot.body; // Get the Matter.js body from the robot object
|
||||||
ctx.strokeStyle = "blue";
|
ctx.strokeStyle = "blue";
|
||||||
|
|
||||||
let vertices = body.vertices;
|
let vertices = body.vertices;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(vertices[0].x, vertices[0].y);
|
ctx.moveTo(vertices[0].x, vertices[0].y);
|
||||||
|
|
@ -97,22 +108,81 @@ export class GameWorld {
|
||||||
}
|
}
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
robot.draw(ctx); // Draw the robot's hull and sensors
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rayCast(startX, startY, endX, endY) {
|
rayCast(startX, startY, endX, endY) {
|
||||||
let closestIntersection = null;
|
let closestIntersection = null;
|
||||||
|
let startPoint = { x: startX, y: startY };
|
||||||
|
let endPoint = { x: endX, y: endY };
|
||||||
|
// // Loop through all obstacles
|
||||||
|
// for (let i = 0; i < this.obstacles.length; i++) {
|
||||||
|
// let obstacle = this.obstacles[i];
|
||||||
|
|
||||||
// Loop through all obstacles
|
// return this.lineIntersectsPolygon(startX, startY, endX, endY, obstacle);
|
||||||
for (let i = 0; i < this.obstacles.length; i++) {
|
// }
|
||||||
let obstacle = this.obstacles[i];
|
|
||||||
|
|
||||||
return this.lineIntersectsPolygon(startX, startY, endX, endY, obstacle);
|
// Get all bodies in the world
|
||||||
|
let allBodies = Matter.Composite.allBodies(this.engine.world);
|
||||||
|
|
||||||
|
let hit = this.getRayHitPoint(startPoint, endPoint, allBodies);
|
||||||
|
if (hit) {
|
||||||
|
closestIntersection = hit.point;
|
||||||
}
|
}
|
||||||
|
|
||||||
return closestIntersection;
|
return closestIntersection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRayHitPoint(start, end, bodies) {
|
||||||
|
let closestHit = null;
|
||||||
|
let minDistance = Infinity;
|
||||||
|
|
||||||
|
bodies.forEach(body => {
|
||||||
|
let vertices = body.vertices;
|
||||||
|
|
||||||
|
// Loop through each edge of the polygon body
|
||||||
|
for (let i = 0; i < vertices.length; i++) {
|
||||||
|
let v1 = vertices[i];
|
||||||
|
let v2 = vertices[(i + 1) % vertices.length]; // Wrap around to close the shape
|
||||||
|
|
||||||
|
let intersection = this.getLineIntersection(start, end, v1, v2);
|
||||||
|
if (intersection) {
|
||||||
|
let distance = Matter.Vector.magnitude(Matter.Vector.sub(intersection, start));
|
||||||
|
|
||||||
|
// Keep track of the closest intersection
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
closestHit = { point: intersection, body };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return closestHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line intersection helper function
|
||||||
|
getLineIntersection(p1, p2, p3, p4) {
|
||||||
|
let denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
|
||||||
|
|
||||||
|
if (denom === 0) return null; // Parallel lines, no intersection
|
||||||
|
|
||||||
|
let ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;
|
||||||
|
let ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;
|
||||||
|
|
||||||
|
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
|
||||||
|
return {
|
||||||
|
x: p1.x + ua * (p2.x - p1.x),
|
||||||
|
y: p1.y + ua * (p2.y - p1.y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // No valid intersection
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
lineIntersection(line1Start, line1End, line2Start, line2End) {
|
lineIntersection(line1Start, line1End, line2Start, line2End) {
|
||||||
let denom = (line1Start.x - line1End.x) * (line2Start.y - line2End.y) -
|
let denom = (line1Start.x - line1End.x) * (line2Start.y - line2End.y) -
|
||||||
(line1Start.y - line1End.y) * (line2Start.x - line2End.x);
|
(line1Start.y - line1End.y) * (line2Start.x - line2End.x);
|
||||||
|
|
|
||||||
101
robot.js
101
robot.js
|
|
@ -7,7 +7,8 @@ export class Robot {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.velocity = 0;
|
this.velocity = 0;
|
||||||
this.angle = 0; // Degrees
|
this.thrust = 0;
|
||||||
|
this.angularThrust = 0
|
||||||
this.width = 20;
|
this.width = 20;
|
||||||
this.height = 20;
|
this.height = 20;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
|
|
@ -25,12 +26,24 @@ export class Robot {
|
||||||
this.addSensor(new RaycastSensor(this, 40, 13, 45, 60));
|
this.addSensor(new RaycastSensor(this, 40, 13, 45, 60));
|
||||||
this.addSensor(new FloorColorSensor(this, 0, 0));
|
this.addSensor(new FloorColorSensor(this, 0, 0));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(ctx, gameWorld) {
|
update(gameWorld) {
|
||||||
this.update_position();
|
let angle = this.body.angle; // Get current rotation in radians
|
||||||
|
|
||||||
|
let force = {
|
||||||
|
x: Math.cos(angle) * this.thrust,
|
||||||
|
y: Math.sin(angle) * this.thrust
|
||||||
|
};
|
||||||
|
|
||||||
|
Matter.Body.applyForce(this.body, this.body.position, force);
|
||||||
|
Matter.Body.setAngularVelocity(this.body, this.angularThrust);
|
||||||
|
//console.log(angle);
|
||||||
this.update_sensors(gameWorld);
|
this.update_sensors(gameWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(ctx){
|
||||||
this.draw(ctx);
|
this.draw(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,60 +58,59 @@ export class Robot {
|
||||||
}
|
}
|
||||||
|
|
||||||
update_position() {
|
update_position() {
|
||||||
const radians = (this.angle * Math.PI) / 180;
|
// const radians = (this.angle * Math.PI) / 180;
|
||||||
this.prevX = this.x;
|
// this.prevX = this.x;
|
||||||
this.prevY = this.y;
|
// this.prevY = this.y;
|
||||||
this.x += Math.cos(radians) * this.velocity;
|
// this.x += Math.cos(radians) * this.velocity;
|
||||||
this.y += Math.sin(radians) * this.velocity;
|
// this.y += Math.sin(radians) * this.velocity;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
move(velocity) {
|
move(thrust) {
|
||||||
this.velocity = velocity;
|
this.thrust = thrust;
|
||||||
}
|
}
|
||||||
|
|
||||||
turn(degrees) {
|
turn(degrees) {
|
||||||
this.angle += degrees;
|
this.angularThrust += degrees;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(ctx) {
|
draw(ctx) {
|
||||||
return ;
|
|
||||||
ctx.fillStyle = this.color;
|
// ctx.fillStyle = this.color;
|
||||||
ctx.save();
|
// ctx.save();
|
||||||
ctx.translate(this.x, this.y);
|
// ctx.translate(this.x, this.y);
|
||||||
ctx.rotate((this.angle * Math.PI) / 180);
|
// ctx.rotate((this.angle * Math.PI) / 180);
|
||||||
|
|
||||||
// Draw the rectangle (tank body)
|
// // Draw the rectangle (tank body)
|
||||||
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
|
// ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
|
||||||
|
|
||||||
// Draw the triangle (direction indicator)
|
// // Draw the triangle (direction indicator)
|
||||||
ctx.strokeStyle = "black";
|
// ctx.strokeStyle = "black";
|
||||||
ctx.lineWidth = 2;
|
// ctx.lineWidth = 2;
|
||||||
ctx.beginPath();
|
// ctx.beginPath();
|
||||||
ctx.moveTo(this.width / 2, -this.height / 2); // Tip of the triangle (front)
|
// ctx.moveTo(this.width / 2, -this.height / 2); // Tip of the triangle (front)
|
||||||
ctx.lineTo(this.width, 0); // Bottom left of triangle
|
// ctx.lineTo(this.width, 0); // Bottom left of triangle
|
||||||
ctx.lineTo(this.width / 2, this.height / 2); // Bottom right of triangle
|
// ctx.lineTo(this.width / 2, this.height / 2); // Bottom right of triangle
|
||||||
ctx.closePath();
|
// ctx.closePath();
|
||||||
|
|
||||||
ctx.fill(); // Fill both the rectangle and the triangle
|
// ctx.fill(); // Fill both the rectangle and the triangle
|
||||||
|
|
||||||
ctx.beginPath();
|
// ctx.beginPath();
|
||||||
ctx.moveTo(this.hull[0].x, this.hull[0].y); // Start at the first vertex
|
// ctx.moveTo(this.hull[0].x, this.hull[0].y); // Start at the first vertex
|
||||||
|
|
||||||
// Loop through the rest of the vertices and draw lines
|
// // Loop through the rest of the vertices and draw lines
|
||||||
for (let i = 1; i < this.hull.length; i++) {
|
// for (let i = 1; i < this.hull.length; i++) {
|
||||||
ctx.lineTo(this.hull[i].x, this.hull[i].y);
|
// ctx.lineTo(this.hull[i].x, this.hull[i].y);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Close the path to form a closed polygon
|
// // Close the path to form a closed polygon
|
||||||
ctx.closePath();
|
// ctx.closePath();
|
||||||
|
|
||||||
ctx.stroke(); // Draw the outline
|
// ctx.stroke(); // Draw the outline
|
||||||
|
|
||||||
|
|
||||||
ctx.restore();
|
// ctx.restore();
|
||||||
|
|
||||||
this.sensors.forEach(sensor => sensor.draw(ctx));
|
this.sensors.forEach(sensor => sensor.draw(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,10 +121,10 @@ export class Robot {
|
||||||
this.transformedHull = this.hull.map(point => {
|
this.transformedHull = this.hull.map(point => {
|
||||||
let xRotated = point.x * Math.cos(angle) - point.y * Math.sin(angle);
|
let xRotated = point.x * Math.cos(angle) - point.y * Math.sin(angle);
|
||||||
let yRotated = point.x * Math.sin(angle) + point.y * Math.cos(angle);
|
let yRotated = point.x * Math.sin(angle) + point.y * Math.cos(angle);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: this.x + xRotated, // Translate to robot's position
|
x: this.body.position.x + xRotated, // Translate to robot's position
|
||||||
y: this.y + yRotated
|
y: this.body.position.y + yRotated
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
//console.log(this.transformedHull );
|
//console.log(this.transformedHull );
|
||||||
|
|
@ -121,11 +133,10 @@ export class Robot {
|
||||||
|
|
||||||
draw_sensors(ctx) {
|
draw_sensors(ctx) {
|
||||||
ctx.fillStyle = "yellow";
|
ctx.fillStyle = "yellow";
|
||||||
|
|
||||||
this.sensors.forEach(sensor => {
|
this.sensors.forEach(sensor => {
|
||||||
const radians = ((this.angle + sensor.angleOffset) * Math.PI) / 180;
|
const radians = ((this.angle + sensor.angleOffset) * Math.PI) / 180;
|
||||||
const sensorX = this.x + Math.cos(radians) * 15;
|
const sensorX = this.body.position.x + Math.cos(radians) * 15;
|
||||||
const sensorY = this.y + Math.sin(radians) * 15;
|
const sensorY = this.body.position.y + Math.sin(radians) * 15;
|
||||||
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(sensorX, sensorY, 3, 0, 2 * Math.PI);
|
ctx.arc(sensorX, sensorY, 3, 0, 2 * Math.PI);
|
||||||
|
|
|
||||||
18
sensor.js
18
sensor.js
|
|
@ -11,10 +11,11 @@ export class Sensor {
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition() {
|
updatePosition() {
|
||||||
const robotAngle = this.robot.angle * (Math.PI / 180);
|
const robotAngle = this.robot.body.angle;
|
||||||
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
|
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
|
||||||
this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
this.startX = this.robot.body.position.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
||||||
this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
this.startY = this.robot.body.position.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
read(robot, gameWorld) {
|
read(robot, gameWorld) {
|
||||||
|
|
@ -35,10 +36,10 @@ export class RaycastSensor extends Sensor {
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition() {
|
updatePosition() {
|
||||||
const robotAngle = this.robot.angle * (Math.PI / 180);
|
const robotAngle = this.robot.body.angle;
|
||||||
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
|
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
|
||||||
this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
this.startX = this.robot.body.position.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
||||||
this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
this.startY = this.robot.body.position.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
||||||
const sensorAngle = robotAngle + (this.angle * (Math.PI / 180));
|
const sensorAngle = robotAngle + (this.angle * (Math.PI / 180));
|
||||||
this.endX = this.startX + Math.cos(sensorAngle) * this.range;
|
this.endX = this.startX + Math.cos(sensorAngle) * this.range;
|
||||||
this.endY = this.startY + Math.sin(sensorAngle) * this.range;
|
this.endY = this.startY + Math.sin(sensorAngle) * this.range;
|
||||||
|
|
@ -48,7 +49,7 @@ export class RaycastSensor extends Sensor {
|
||||||
//console.log(robot);
|
//console.log(robot);
|
||||||
//console.log(gameWorld);
|
//console.log(gameWorld);
|
||||||
this.updatePosition();
|
this.updatePosition();
|
||||||
|
|
||||||
const angleInRadians = (this.robot.direction + this.angle) * Math.PI / 180;
|
const angleInRadians = (this.robot.direction + this.angle) * Math.PI / 180;
|
||||||
const x = this.robot.x + Math.cos(angleInRadians) * this.distance;
|
const x = this.robot.x + Math.cos(angleInRadians) * this.distance;
|
||||||
const y = this.robot.y + Math.sin(angleInRadians) * this.distance;
|
const y = this.robot.y + Math.sin(angleInRadians) * this.distance;
|
||||||
|
|
@ -71,7 +72,7 @@ export class RaycastSensor extends Sensor {
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(ctx) {
|
draw(ctx) {
|
||||||
|
|
||||||
|
|
||||||
ctx.strokeStyle = "lime";
|
ctx.strokeStyle = "lime";
|
||||||
ctx.fillStyle = "green"
|
ctx.fillStyle = "green"
|
||||||
|
|
@ -98,7 +99,6 @@ export class RaycastSensor extends Sensor {
|
||||||
ctx.moveTo(this.startX, this.startY);
|
ctx.moveTo(this.startX, this.startY);
|
||||||
ctx.lineTo(this.endX, this.endY);
|
ctx.lineTo(this.endX, this.endY);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue