monsterplex/src/entities/Terrain.js

76 lines
2.1 KiB
JavaScript

import { TERRAIN_DEPTH, WORLD_SCALE } from '../config.js';
// Ground is stored as left-to-right surface polylines (easy to hand-author),
// each converted into a chain of angled static rectangle bodies extruded
// downward - simpler and more robust than concave polygon decomposition.
export default class Terrain {
constructor(scene, levelData) {
this.scene = scene;
this.bodies = [];
this.graphics = scene.add.graphics();
for (const segment of levelData.terrain) {
this._buildSegment(segment.points);
this._drawSegment(segment.points);
}
if (levelData.obstacles) {
for (const obstacle of levelData.obstacles) {
this._buildObstacle(obstacle);
}
}
}
_buildSegment(points) {
for (let i = 0; i < points.length - 1; i++) {
const a = points[i];
const b = points[i + 1];
const dx = b.x - a.x;
const dy = b.y - a.y;
const length = Math.hypot(dx, dy);
if (length < 1) continue;
const angle = Math.atan2(dy, dx);
const midX = (a.x + b.x) / 2;
const midY = (a.y + b.y) / 2 + (TERRAIN_DEPTH / 2) * Math.cos(angle);
const body = this.scene.matter.add.rectangle(midX, midY, length, TERRAIN_DEPTH, {
isStatic: true,
angle,
friction: 0.95,
label: 'terrain',
});
this.bodies.push(body);
}
}
_buildObstacle(obstacle) {
if (obstacle.type !== 'ramp') return;
const body = this.scene.matter.add.rectangle(obstacle.x, obstacle.y, obstacle.width, obstacle.height, {
isStatic: true,
angle: obstacle.angle || 0,
friction: 0.95,
label: 'terrain',
});
this.bodies.push(body);
}
_drawSegment(points) {
const g = this.graphics;
g.fillStyle(0x3c8f3c, 1);
g.beginPath();
g.moveTo(points[0].x, points[0].y + TERRAIN_DEPTH);
for (const p of points) g.lineTo(p.x, p.y);
const last = points[points.length - 1];
g.lineTo(last.x, last.y + TERRAIN_DEPTH);
g.closePath();
g.fillPath();
g.lineStyle(4 * WORLD_SCALE, 0x2c6e2c, 1);
g.beginPath();
g.moveTo(points[0].x, points[0].y);
for (const p of points) g.lineTo(p.x, p.y);
g.strokePath();
}
}