implementing matter.js, added obstacles and tank, need to convert to rigidbody driven rather than just moving pixels for movement
parent
0674e5ca2f
commit
c7a0c9c834
5
game.js
5
game.js
|
|
@ -9,6 +9,8 @@ const gameWorld = new GameWorld();
|
|||
|
||||
let pyodideWorker = startPyodideWorker();
|
||||
let robots = createInitialRobots();
|
||||
console.log(robots);
|
||||
gameWorld.addRobot(robots["player"]);
|
||||
let paused = false;
|
||||
|
||||
let scale = 1; // Zoom level
|
||||
|
|
@ -113,11 +115,12 @@ function gameLoop() {
|
|||
ctx.translate(offsetX, offsetY); // Apply panning
|
||||
ctx.scale(scale, scale); // Apply zooming
|
||||
|
||||
|
||||
gameWorld.update();
|
||||
gameWorld.draw(ctx);
|
||||
|
||||
for (let id in robots) {
|
||||
robots[id].update(ctx, gameWorld);
|
||||
gameWorld.checkAndResolveCollision(robots[id]);
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(gameLoop);
|
||||
|
|
|
|||
211
gameworld.js
211
gameworld.js
|
|
@ -1,33 +1,67 @@
|
|||
import Matter from "https://cdn.jsdelivr.net/npm/matter-js@0.19.0/+esm";
|
||||
|
||||
export class GameWorld {
|
||||
constructor() {
|
||||
|
||||
let polygon1 = [
|
||||
{ x: 80, y: 100 }, // Vertex 1
|
||||
{ x: 200, y: 100 }, // Vertex 2
|
||||
{ x: 200, y: 200 }, // Vertex 3
|
||||
{ x: 100, y: 200 } // Vertex 4
|
||||
];
|
||||
this.engine = Matter.Engine.create();
|
||||
this.world = this.engine.world;
|
||||
this.engine.world.gravity.y = 0.01;
|
||||
Matter.Runner.run(this.engine);
|
||||
|
||||
let polygon2 = [
|
||||
{ x: 300, y: 400 }, // Vertex 1
|
||||
{ x: 320, y: 450 }, // Vertex 2
|
||||
{ x: 250, y: 550 }, // Vertex 3
|
||||
let ground = Matter.Bodies.rectangle(400, 600, 800, 50, { isStatic: true });
|
||||
Matter.World.add(this.world, ground);
|
||||
|
||||
|
||||
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
|
||||
]);
|
||||
|
||||
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
|
||||
];
|
||||
]);
|
||||
|
||||
|
||||
|
||||
this.obstacles = [
|
||||
polygon1, // Example obstacle
|
||||
polygon2 // Another obstacle
|
||||
];
|
||||
}
|
||||
|
||||
// Check if a point (x, y) intersects with any obstacles
|
||||
isObstacle(x, y) {
|
||||
// return this.obstacles.some(
|
||||
// obj => x > obj.x && x < obj.x + obj.width && y > obj.y && y < obj.y + obj.height
|
||||
// );
|
||||
return true;
|
||||
update() {
|
||||
// Update the physics simulation
|
||||
Matter.Engine.update(this.engine);
|
||||
|
||||
|
||||
}
|
||||
|
||||
addObstacle(vertices) {
|
||||
// Convert the polygon points into a Matter.js body
|
||||
let body = Matter.Bodies.fromVertices(-30, 300, [vertices], {
|
||||
isStatic: true, // Obstacles shouldn't move
|
||||
});
|
||||
|
||||
// Add body to world and store it
|
||||
Matter.World.add(this.world, body);
|
||||
this.obstacles.push(body);
|
||||
}
|
||||
|
||||
addRobot(robot) {
|
||||
console.log("added robot");
|
||||
// Create the robot's Matter.js body
|
||||
let robotBody = Matter.Bodies.fromVertices(robot.x, robot.y, [robot.hull], {
|
||||
friction: 0.05,
|
||||
restitution: 0.2, // Slight bounce
|
||||
});
|
||||
|
||||
// Add to the world
|
||||
Matter.World.add(this.world, robotBody);
|
||||
this.robots.push(robotBody);
|
||||
}
|
||||
|
||||
// Return the floor color based on (x, y) coordinates
|
||||
|
|
@ -41,19 +75,28 @@ export class GameWorld {
|
|||
ctx.strokeStyle = "gray"; // Obstacle outline color
|
||||
ctx.lineWidth = 2; // Optional: to make the outline thicker
|
||||
|
||||
this.obstacles.forEach(obstacle => {
|
||||
// Draw obstacles
|
||||
this.obstacles.forEach(body => {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(obstacle[0].x, obstacle[0].y); // Start at the first vertex
|
||||
|
||||
// Loop through the rest of the vertices and draw lines
|
||||
for (let i = 1; i < obstacle.length; i++) {
|
||||
ctx.lineTo(obstacle[i].x, obstacle[i].y);
|
||||
let vertices = body.vertices;
|
||||
ctx.moveTo(vertices[0].x, vertices[0].y);
|
||||
for (let i = 1; i < vertices.length; i++) {
|
||||
ctx.lineTo(vertices[i].x, vertices[i].y);
|
||||
}
|
||||
|
||||
// Close the path to form a closed polygon
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
ctx.stroke(); // Draw the outline
|
||||
this.robots.forEach(body => {
|
||||
ctx.strokeStyle = "blue";
|
||||
let vertices = body.vertices;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vertices[0].x, vertices[0].y);
|
||||
for (let i = 1; i < vertices.length; i++) {
|
||||
ctx.lineTo(vertices[i].x, vertices[i].y);
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -110,4 +153,112 @@ export class GameWorld {
|
|||
return null; // No intersection with any edge of the polygon
|
||||
}
|
||||
|
||||
lineSegmentIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||
let den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
||||
if (den === 0) return null; // Parallel lines
|
||||
|
||||
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
|
||||
let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
|
||||
|
||||
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
|
||||
return { x: x1 + t * (x2 - x1), y: y1 + t * (y2 - y1) };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
polygonsIntersect(poly1, poly2) {
|
||||
for (let i = 0; i < poly1.length; i++) {
|
||||
let p1 = poly1[i];
|
||||
let p2 = poly1[(i + 1) % poly1.length]; // Next point, wrap around
|
||||
|
||||
// Loop through each edge of the second polygon
|
||||
for (let j = 0; j < poly2.length; j++) {
|
||||
let p3 = poly2[j];
|
||||
let p4 = poly2[(j + 1) % poly2.length]; // Next point, wrap around
|
||||
|
||||
let intersection = this.lineIntersection(p1, p2, p3, p4);
|
||||
if (intersection) {
|
||||
// Calculate the normal of the intersecting edge
|
||||
let normal = this.calculateNormal(p1, p2);
|
||||
return { intersection, normal };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null; // No intersection
|
||||
}
|
||||
|
||||
|
||||
calculateNormal(p1, p2) {
|
||||
// Vector of the edge (from p1 to p2)
|
||||
let dx = p2.x - p1.x;
|
||||
let dy = p2.y - p1.y;
|
||||
|
||||
// The normal is the perpendicular vector to the edge
|
||||
// We can rotate the vector 90 degrees counterclockwise (for a counterclockwise normal)
|
||||
let normal = { x: -dy, y: dx };
|
||||
|
||||
// Normalize the normal vector
|
||||
let length = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
|
||||
normal.x /= length;
|
||||
normal.y /= length;
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
|
||||
polygonLineIntersection(polygon, p1, p2) {
|
||||
for (let i = 0; i < polygon.length; i++) {
|
||||
let p3 = polygon[i];
|
||||
let p4 = polygon[(i + 1) % polygon.length]; // Next point, wrap around
|
||||
|
||||
let intersection = this.lineIntersection(p1, p2, p3, p4);
|
||||
if (intersection) {
|
||||
return intersection;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
checkAndResolveCollision(robot) {
|
||||
for (let obstacle of this.obstacles) {
|
||||
let result = this.polygonsIntersect(robot.get_hull(), obstacle);
|
||||
if (result) {
|
||||
let { intersection, normal } = result;
|
||||
//console.log("Intersection point:", intersection);
|
||||
//console.log("Normal vector:", normal);
|
||||
|
||||
// Resolve the collision by sliding the robot along the normal
|
||||
this.resolveSlide(robot, normal);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
resolveSlide(robot, normal) {
|
||||
const radians = (robot.angle * Math.PI) / 180;
|
||||
let vx = Math.cos(radians) * robot.velocity;
|
||||
let vy = Math.sin(radians) * robot.velocity;
|
||||
|
||||
|
||||
let dot = vx * normal.x + vy * normal.y;
|
||||
|
||||
// Subtract the normal component from velocity to make it slide
|
||||
vx -= 2 * dot * normal.x; // Reflect velocity along the normal (away from the surface)
|
||||
vy -= 2 * dot * normal.y; // Reflect velocity along the normal (away from the surface)
|
||||
|
||||
// Apply the adjusted movement
|
||||
robot.x = robot.prevX;
|
||||
robot.y = robot.prevY;
|
||||
robot.x += vx;
|
||||
robot.y += vy;
|
||||
//robot.updateHull();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pyodide Real-Time Python Execution with Console</title>
|
||||
<script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
|
||||
|
||||
<style>
|
||||
#console {
|
||||
width: 100%;
|
||||
|
|
|
|||
49
robot.js
49
robot.js
|
|
@ -13,12 +13,22 @@ export class Robot {
|
|||
this.color = color;
|
||||
this.sensors = [];
|
||||
|
||||
this.hull = [
|
||||
{ x: -this.width / 2, y: -this.height / 2 }, // Vertex 1
|
||||
{ x: this.width / 2, y: -this.height / 2 }, // Vertex 2
|
||||
{ x: this.width, y: 0 }, // Vertex 2
|
||||
{ x: this.width / 2, y: this.height / 2 }, // Vertex 3
|
||||
{ x: -this.width / 2, y: this.height / 2 } // Vertex 4
|
||||
];
|
||||
|
||||
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));
|
||||
|
||||
|
||||
}
|
||||
|
||||
update(ctx, gameWorld){
|
||||
update(ctx, gameWorld) {
|
||||
this.update_position();
|
||||
this.update_sensors(gameWorld);
|
||||
this.draw(ctx);
|
||||
|
|
@ -36,8 +46,12 @@ 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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
move(velocity) {
|
||||
|
|
@ -49,6 +63,7 @@ export class Robot {
|
|||
}
|
||||
|
||||
draw(ctx) {
|
||||
return ;
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.save();
|
||||
ctx.translate(this.x, this.y);
|
||||
|
|
@ -58,6 +73,8 @@ export class Robot {
|
|||
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
|
||||
|
|
@ -66,12 +83,42 @@ export class Robot {
|
|||
|
||||
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
|
||||
|
||||
// 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();
|
||||
|
||||
ctx.stroke(); // Draw the outline
|
||||
|
||||
|
||||
ctx.restore();
|
||||
|
||||
this.sensors.forEach(sensor => sensor.draw(ctx));
|
||||
}
|
||||
|
||||
// Takes the polygon hull and updates it to local position and rotation.
|
||||
get_hull() {
|
||||
let angle = this.angle * (Math.PI / 180); // Convert degrees to radians
|
||||
//console.log(angle);
|
||||
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
|
||||
};
|
||||
});
|
||||
//console.log(this.transformedHull );
|
||||
return this.transformedHull;
|
||||
}
|
||||
|
||||
draw_sensors(ctx) {
|
||||
ctx.fillStyle = "yellow";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue