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.draw(ctx);
|
||||
|
||||
for (let id in robots) {
|
||||
robots[id].update(ctx, gameWorld);
|
||||
gameWorld.checkAndResolveCollision(robots[id]);
|
||||
}
|
||||
|
||||
}
|
||||
requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
|
|
|||
104
gameworld.js
104
gameworld.js
|
|
@ -5,7 +5,7 @@ export class GameWorld {
|
|||
|
||||
this.engine = Matter.Engine.create();
|
||||
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);
|
||||
|
||||
let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
|
||||
|
|
@ -15,19 +15,21 @@ export class GameWorld {
|
|||
this.obstacles = [];
|
||||
this.robots = [];
|
||||
|
||||
|
||||
this.addObstacle([
|
||||
{ x: 80 + 100, y: 0 }, // Vertex 1
|
||||
{ x: 250 + 100, y: 370 }, // Vertex 2
|
||||
{ x: 200 + 100, y: 370 }, // Vertex 3
|
||||
{ x: 150 + 100, y: 200 } // Vertex 4
|
||||
]);
|
||||
{ x: -100, y: -100 }, // Vertex 1
|
||||
{ x: 100, y: -100 }, // Vertex 2
|
||||
{ x: 100, y: 100 }, // Vertex 3
|
||||
{ x: -100, y: 100 } // Vertex 4
|
||||
], { x: 400, y: 300 });
|
||||
|
||||
|
||||
this.addObstacle([
|
||||
{ x: 300, y: 380 }, // Vertex 1
|
||||
{ x: 420, y: 380 }, // Vertex 2
|
||||
{ x: 350, y: 550 }, // Vertex 3
|
||||
{ x: 280, y: 420 } // Vertex 4
|
||||
]);
|
||||
], { x: 400, y: 0 });
|
||||
|
||||
|
||||
|
||||
|
|
@ -35,14 +37,19 @@ export class GameWorld {
|
|||
|
||||
update() {
|
||||
// Update the physics simulation
|
||||
|
||||
for (let id in this.robots) {
|
||||
this.robots[id].update(this);
|
||||
}
|
||||
|
||||
Matter.Engine.update(this.engine);
|
||||
|
||||
|
||||
}
|
||||
|
||||
addObstacle(vertices) {
|
||||
addObstacle(vertices, position = { x: 0, y: 0 }) {
|
||||
// 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
|
||||
});
|
||||
|
||||
|
|
@ -56,12 +63,14 @@ export class GameWorld {
|
|||
// Create the robot's Matter.js body
|
||||
let robotBody = Matter.Bodies.fromVertices(robot.x, robot.y, [robot.hull], {
|
||||
friction: 0.05,
|
||||
frictionAir: 0.02, // Air resistance slows it down naturally
|
||||
restitution: 0.2, // Slight bounce
|
||||
});
|
||||
|
||||
// Add to the world
|
||||
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
|
||||
|
|
@ -86,9 +95,11 @@ export class GameWorld {
|
|||
ctx.closePath();
|
||||
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";
|
||||
|
||||
let vertices = body.vertices;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vertices[0].x, vertices[0].y);
|
||||
|
|
@ -97,22 +108,81 @@ export class GameWorld {
|
|||
}
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
robot.draw(ctx); // Draw the robot's hull and sensors
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
rayCast(startX, startY, endX, endY) {
|
||||
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
|
||||
for (let i = 0; i < this.obstacles.length; i++) {
|
||||
let obstacle = this.obstacles[i];
|
||||
// return this.lineIntersectsPolygon(startX, startY, endX, endY, obstacle);
|
||||
// }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
let denom = (line1Start.x - line1End.x) * (line2Start.y - line2End.y) -
|
||||
(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.y = y;
|
||||
this.velocity = 0;
|
||||
this.angle = 0; // Degrees
|
||||
this.thrust = 0;
|
||||
this.angularThrust = 0
|
||||
this.width = 20;
|
||||
this.height = 20;
|
||||
this.color = color;
|
||||
|
|
@ -25,12 +26,24 @@ export class Robot {
|
|||
this.addSensor(new RaycastSensor(this, 40, 13, 45, 60));
|
||||
this.addSensor(new FloorColorSensor(this, 0, 0));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
update(ctx, gameWorld) {
|
||||
this.update_position();
|
||||
update(gameWorld) {
|
||||
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);
|
||||
}
|
||||
|
||||
draw(ctx){
|
||||
this.draw(ctx);
|
||||
}
|
||||
|
||||
|
|
@ -45,60 +58,59 @@ export class Robot {
|
|||
}
|
||||
|
||||
update_position() {
|
||||
const radians = (this.angle * Math.PI) / 180;
|
||||
this.prevX = this.x;
|
||||
this.prevY = this.y;
|
||||
this.x += Math.cos(radians) * this.velocity;
|
||||
this.y += Math.sin(radians) * this.velocity;
|
||||
// const radians = (this.angle * Math.PI) / 180;
|
||||
// this.prevX = this.x;
|
||||
// this.prevY = this.y;
|
||||
// this.x += Math.cos(radians) * this.velocity;
|
||||
// this.y += Math.sin(radians) * this.velocity;
|
||||
|
||||
|
||||
}
|
||||
|
||||
move(velocity) {
|
||||
this.velocity = velocity;
|
||||
move(thrust) {
|
||||
this.thrust = thrust;
|
||||
}
|
||||
|
||||
turn(degrees) {
|
||||
this.angle += degrees;
|
||||
this.angularThrust += degrees;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
return ;
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.save();
|
||||
ctx.translate(this.x, this.y);
|
||||
ctx.rotate((this.angle * Math.PI) / 180);
|
||||
|
||||
// ctx.fillStyle = this.color;
|
||||
// ctx.save();
|
||||
// ctx.translate(this.x, this.y);
|
||||
// ctx.rotate((this.angle * Math.PI) / 180);
|
||||
|
||||
// Draw the rectangle (tank body)
|
||||
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
|
||||
// // Draw the rectangle (tank body)
|
||||
// ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
|
||||
|
||||
// Draw the triangle (direction indicator)
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
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 / 2, this.height / 2); // Bottom right of triangle
|
||||
ctx.closePath();
|
||||
// // Draw the triangle (direction indicator)
|
||||
// ctx.strokeStyle = "black";
|
||||
// ctx.lineWidth = 2;
|
||||
// ctx.beginPath();
|
||||
// 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 / 2, this.height / 2); // Bottom right of triangle
|
||||
// ctx.closePath();
|
||||
|
||||
ctx.fill(); // Fill both the rectangle and the triangle
|
||||
// ctx.fill(); // Fill both the rectangle and the triangle
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.hull[0].x, this.hull[0].y); // Start at the first vertex
|
||||
// ctx.beginPath();
|
||||
// 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
|
||||
for (let i = 1; i < this.hull.length; i++) {
|
||||
ctx.lineTo(this.hull[i].x, this.hull[i].y);
|
||||
}
|
||||
// // Loop through the rest of the vertices and draw lines
|
||||
// for (let i = 1; i < this.hull.length; i++) {
|
||||
// ctx.lineTo(this.hull[i].x, this.hull[i].y);
|
||||
// }
|
||||
|
||||
// Close the path to form a closed polygon
|
||||
ctx.closePath();
|
||||
// // Close the path to form a closed polygon
|
||||
// ctx.closePath();
|
||||
|
||||
ctx.stroke(); // Draw the outline
|
||||
// ctx.stroke(); // Draw the outline
|
||||
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// ctx.restore();
|
||||
this.sensors.forEach(sensor => sensor.draw(ctx));
|
||||
}
|
||||
|
||||
|
|
@ -109,10 +121,10 @@ export class Robot {
|
|||
this.transformedHull = this.hull.map(point => {
|
||||
let xRotated = point.x * Math.cos(angle) - point.y * Math.sin(angle);
|
||||
let yRotated = point.x * Math.sin(angle) + point.y * Math.cos(angle);
|
||||
|
||||
|
||||
return {
|
||||
x: this.x + xRotated, // Translate to robot's position
|
||||
y: this.y + yRotated
|
||||
x: this.body.position.x + xRotated, // Translate to robot's position
|
||||
y: this.body.position.y + yRotated
|
||||
};
|
||||
});
|
||||
//console.log(this.transformedHull );
|
||||
|
|
@ -121,11 +133,10 @@ export class Robot {
|
|||
|
||||
draw_sensors(ctx) {
|
||||
ctx.fillStyle = "yellow";
|
||||
|
||||
this.sensors.forEach(sensor => {
|
||||
const radians = ((this.angle + sensor.angleOffset) * Math.PI) / 180;
|
||||
const sensorX = this.x + Math.cos(radians) * 15;
|
||||
const sensorY = this.y + Math.sin(radians) * 15;
|
||||
const sensorX = this.body.position.x + Math.cos(radians) * 15;
|
||||
const sensorY = this.body.position.y + Math.sin(radians) * 15;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(sensorX, sensorY, 3, 0, 2 * Math.PI);
|
||||
|
|
|
|||
18
sensor.js
18
sensor.js
|
|
@ -11,10 +11,11 @@ export class Sensor {
|
|||
}
|
||||
|
||||
updatePosition() {
|
||||
const robotAngle = this.robot.angle * (Math.PI / 180);
|
||||
const robotAngle = this.robot.body.angle;
|
||||
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
|
||||
this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
||||
this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
||||
this.startX = this.robot.body.position.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
||||
this.startY = this.robot.body.position.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
||||
|
||||
}
|
||||
|
||||
read(robot, gameWorld) {
|
||||
|
|
@ -35,10 +36,10 @@ export class RaycastSensor extends Sensor {
|
|||
}
|
||||
|
||||
updatePosition() {
|
||||
const robotAngle = this.robot.angle * (Math.PI / 180);
|
||||
const robotAngle = this.robot.body.angle;
|
||||
const offsetAngleRad = this.offsetAngle * (Math.PI / 180);
|
||||
this.startX = this.robot.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
||||
this.startY = this.robot.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
||||
this.startX = this.robot.body.position.x + this.offsetDistance * Math.cos(robotAngle + offsetAngleRad);
|
||||
this.startY = this.robot.body.position.y + this.offsetDistance * Math.sin(robotAngle + offsetAngleRad);
|
||||
const sensorAngle = robotAngle + (this.angle * (Math.PI / 180));
|
||||
this.endX = this.startX + Math.cos(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(gameWorld);
|
||||
this.updatePosition();
|
||||
|
||||
|
||||
const angleInRadians = (this.robot.direction + this.angle) * Math.PI / 180;
|
||||
const x = this.robot.x + Math.cos(angleInRadians) * this.distance;
|
||||
const y = this.robot.y + Math.sin(angleInRadians) * this.distance;
|
||||
|
|
@ -71,7 +72,7 @@ export class RaycastSensor extends Sensor {
|
|||
}
|
||||
|
||||
draw(ctx) {
|
||||
|
||||
|
||||
|
||||
ctx.strokeStyle = "lime";
|
||||
ctx.fillStyle = "green"
|
||||
|
|
@ -98,7 +99,6 @@ export class RaycastSensor extends Sensor {
|
|||
ctx.moveTo(this.startX, this.startY);
|
||||
ctx.lineTo(this.endX, this.endY);
|
||||
ctx.stroke();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue