Spaces:
Running
Running
ok
Browse files- src/config/gameConfig.js +43 -16
- src/main.js +8 -1
- src/scene/PathBuilder.js +66 -38
- src/utils/utils.js +13 -1
src/config/gameConfig.js
CHANGED
@@ -164,23 +164,50 @@ export const GRID_CELL_SIZE = 2;
|
|
164 |
// from edges to keep tower placement meaningful across the board.
|
165 |
const HALF = GROUND_SIZE / 2;
|
166 |
const PATH_MARGIN = 4 * GRID_CELL_SIZE; // 4 squares (8 units)
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
export const PATH_POINTS = [
|
168 |
-
//
|
169 |
-
new THREE.Vector3(
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
//
|
181 |
-
|
182 |
-
|
183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
];
|
185 |
|
186 |
// Visual settings
|
|
|
164 |
// from edges to keep tower placement meaningful across the board.
|
165 |
const HALF = GROUND_SIZE / 2;
|
166 |
const PATH_MARGIN = 4 * GRID_CELL_SIZE; // 4 squares (8 units)
|
167 |
+
// Legacy single path kept for backward-compat. Now we support branching paths.
|
168 |
+
// Center lane mirrors the middle branch of PATHS below.
|
169 |
+
const SPLIT_Z = HALF - 12; // branch a bit further from edge (12u)
|
170 |
+
const REJOIN_Z = -HALF + 12; // rejoin a bit further from edge (12u)
|
171 |
+
const LANE_OFFSET_X = 20; // slightly narrower than before
|
172 |
+
|
173 |
export const PATH_POINTS = [
|
174 |
+
// Entry straight down the middle
|
175 |
+
new THREE.Vector3(0, 0, HALF), // (0, 36)
|
176 |
+
new THREE.Vector3(0, 0, SPLIT_Z),
|
177 |
+
// Middle lane continues straight through the split area
|
178 |
+
new THREE.Vector3(0, 0, REJOIN_Z),
|
179 |
+
// Exit to bottom edge
|
180 |
+
new THREE.Vector3(0, 0, -HALF), // (0, -36)
|
181 |
+
];
|
182 |
+
|
183 |
+
// New 3-lane path layout: long split zone and wide lateral separation.
|
184 |
+
// Enemies will be assigned one of these at spawn.
|
185 |
+
export const PATHS = [
|
186 |
+
// Left lane: go left at split, travel down, then rejoin center
|
187 |
+
[
|
188 |
+
new THREE.Vector3(0, 0, HALF), // start top center
|
189 |
+
new THREE.Vector3(0, 0, SPLIT_Z), // approach split
|
190 |
+
new THREE.Vector3(-LANE_OFFSET_X, 0, SPLIT_Z), // branch left
|
191 |
+
new THREE.Vector3(-LANE_OFFSET_X, 0, REJOIN_Z), // long split zone
|
192 |
+
new THREE.Vector3(0, 0, REJOIN_Z), // rejoin center
|
193 |
+
new THREE.Vector3(0, 0, -HALF), // exit bottom center
|
194 |
+
],
|
195 |
+
// Middle lane: continue straight through the split
|
196 |
+
[
|
197 |
+
new THREE.Vector3(0, 0, HALF),
|
198 |
+
new THREE.Vector3(0, 0, SPLIT_Z),
|
199 |
+
new THREE.Vector3(0, 0, REJOIN_Z),
|
200 |
+
new THREE.Vector3(0, 0, -HALF),
|
201 |
+
],
|
202 |
+
// Right lane: go right at split, travel down, then rejoin center
|
203 |
+
[
|
204 |
+
new THREE.Vector3(0, 0, HALF),
|
205 |
+
new THREE.Vector3(0, 0, SPLIT_Z),
|
206 |
+
new THREE.Vector3(LANE_OFFSET_X, 0, SPLIT_Z), // branch right
|
207 |
+
new THREE.Vector3(LANE_OFFSET_X, 0, REJOIN_Z), // long split zone
|
208 |
+
new THREE.Vector3(0, 0, REJOIN_Z), // rejoin center
|
209 |
+
new THREE.Vector3(0, 0, -HALF),
|
210 |
+
],
|
211 |
];
|
212 |
|
213 |
// Visual settings
|
src/main.js
CHANGED
@@ -8,6 +8,7 @@ import { Tower } from "./entities/Tower.js";
|
|
8 |
import {
|
9 |
TOWER_TYPES,
|
10 |
PATH_POINTS,
|
|
|
11 |
PROJECTILE_SPEED,
|
12 |
GRID_CELL_SIZE,
|
13 |
} from "./config/gameConfig.js";
|
@@ -244,11 +245,17 @@ function resetGame() {
|
|
244 |
}
|
245 |
|
246 |
function spawnEnemy(wave) {
|
|
|
|
|
|
|
|
|
|
|
|
|
247 |
const enemy = new Enemy(
|
248 |
wave.hp,
|
249 |
wave.speed,
|
250 |
wave.reward,
|
251 |
-
|
252 |
sceneSetup.scene
|
253 |
);
|
254 |
gameState.addEnemy(enemy);
|
|
|
8 |
import {
|
9 |
TOWER_TYPES,
|
10 |
PATH_POINTS,
|
11 |
+
PATHS,
|
12 |
PROJECTILE_SPEED,
|
13 |
GRID_CELL_SIZE,
|
14 |
} from "./config/gameConfig.js";
|
|
|
245 |
}
|
246 |
|
247 |
function spawnEnemy(wave) {
|
248 |
+
// Choose a random path if branching paths are defined; otherwise use legacy PATH_POINTS
|
249 |
+
let path = PATH_POINTS;
|
250 |
+
if (Array.isArray(PATHS) && PATHS.length > 0) {
|
251 |
+
const idx = Math.floor(Math.random() * PATHS.length);
|
252 |
+
path = PATHS[idx];
|
253 |
+
}
|
254 |
const enemy = new Enemy(
|
255 |
wave.hp,
|
256 |
wave.speed,
|
257 |
wave.reward,
|
258 |
+
path,
|
259 |
sceneSetup.scene
|
260 |
);
|
261 |
gameState.addEnemy(enemy);
|
src/scene/PathBuilder.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import * as THREE from "three";
|
2 |
import {
|
3 |
PATH_POINTS,
|
|
|
4 |
ROAD_HALF_WIDTH,
|
5 |
GRID_CELL_SIZE,
|
6 |
} from "../config/gameConfig.js";
|
@@ -8,15 +9,18 @@ import {
|
|
8 |
export class PathBuilder {
|
9 |
constructor(scene) {
|
10 |
this.scene = scene;
|
11 |
-
//
|
12 |
-
|
13 |
-
const
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
20 |
this.roadMeshes = [];
|
21 |
|
22 |
// Materials
|
@@ -28,44 +32,68 @@ export class PathBuilder {
|
|
28 |
}
|
29 |
|
30 |
buildPath() {
|
31 |
-
// Visualize path
|
32 |
-
this.
|
33 |
|
34 |
-
//
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
-
|
40 |
-
for (
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
50 |
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
}
|
58 |
}
|
59 |
}
|
60 |
|
61 |
-
|
62 |
const pathLineMat = new THREE.LineBasicMaterial({ color: 0xffff00 });
|
63 |
-
const
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
}
|
70 |
|
71 |
addSegment(a, b) {
|
|
|
1 |
import * as THREE from "three";
|
2 |
import {
|
3 |
PATH_POINTS,
|
4 |
+
PATHS,
|
5 |
ROAD_HALF_WIDTH,
|
6 |
GRID_CELL_SIZE,
|
7 |
} from "../config/gameConfig.js";
|
|
|
9 |
export class PathBuilder {
|
10 |
constructor(scene) {
|
11 |
this.scene = scene;
|
12 |
+
// Prepare one or many paths, snapping points to grid
|
13 |
+
const snap = (v) => {
|
14 |
+
const out = v.clone ? v.clone() : new THREE.Vector3(v.x, v.y ?? 0, v.z);
|
15 |
+
out.x = Math.round(out.x / GRID_CELL_SIZE) * GRID_CELL_SIZE;
|
16 |
+
out.z = Math.round(out.z / GRID_CELL_SIZE) * GRID_CELL_SIZE;
|
17 |
+
out.y = 0;
|
18 |
+
return out;
|
19 |
+
};
|
20 |
+
const hasBranches = Array.isArray(PATHS) && PATHS.length > 0;
|
21 |
+
this.paths = hasBranches
|
22 |
+
? PATHS.map((path) => path.map(snap))
|
23 |
+
: [PATH_POINTS.map(snap)];
|
24 |
this.roadMeshes = [];
|
25 |
|
26 |
// Materials
|
|
|
32 |
}
|
33 |
|
34 |
buildPath() {
|
35 |
+
// Visualize path lines
|
36 |
+
this.createPathLines();
|
37 |
|
38 |
+
// Deduplicate segments across paths
|
39 |
+
const segKey = (a, b) => {
|
40 |
+
// direction-agnostic key
|
41 |
+
const ax = a.x.toFixed(4);
|
42 |
+
const az = a.z.toFixed(4);
|
43 |
+
const bx = b.x.toFixed(4);
|
44 |
+
const bz = b.z.toFixed(4);
|
45 |
+
return ax < bx || (ax === bx && az <= bz)
|
46 |
+
? `${ax},${az}|${bx},${bz}`
|
47 |
+
: `${bx},${bz}|${ax},${az}`;
|
48 |
+
};
|
49 |
|
50 |
+
const seenSegs = new Set();
|
51 |
+
for (const pts of this.paths) {
|
52 |
+
for (let i = 0; i < pts.length - 1; i++) {
|
53 |
+
const a = pts[i];
|
54 |
+
const b = pts[i + 1];
|
55 |
+
const key = segKey(a, b);
|
56 |
+
if (!seenSegs.has(key)) {
|
57 |
+
seenSegs.add(key);
|
58 |
+
this.addSegment(a, b);
|
59 |
+
}
|
60 |
+
}
|
61 |
+
}
|
62 |
|
63 |
+
// Corner tiles at turns; deduplicate by center
|
64 |
+
const seenCorners = new Set();
|
65 |
+
for (const pts of this.paths) {
|
66 |
+
for (let i = 1; i < pts.length - 1; i++) {
|
67 |
+
const prev = pts[i - 1];
|
68 |
+
const cur = pts[i];
|
69 |
+
const next = pts[i + 1];
|
70 |
|
71 |
+
const v1 = new THREE.Vector3().subVectors(cur, prev);
|
72 |
+
const v2 = new THREE.Vector3().subVectors(next, cur);
|
73 |
+
v1.y = 0;
|
74 |
+
v2.y = 0;
|
75 |
+
const isV1H = Math.abs(v1.x) > Math.abs(v1.z);
|
76 |
+
const isV2H = Math.abs(v2.x) > Math.abs(v2.z);
|
77 |
+
const isTurn = (isV1H && !isV2H) || (!isV1H && isV2H);
|
78 |
+
if (isTurn) {
|
79 |
+
const key = `${cur.x.toFixed(4)},${cur.z.toFixed(4)}`;
|
80 |
+
if (!seenCorners.has(key)) {
|
81 |
+
seenCorners.add(key);
|
82 |
+
this.addCornerTile(cur);
|
83 |
+
}
|
84 |
+
}
|
85 |
}
|
86 |
}
|
87 |
}
|
88 |
|
89 |
+
createPathLines() {
|
90 |
const pathLineMat = new THREE.LineBasicMaterial({ color: 0xffff00 });
|
91 |
+
for (const pts of this.paths) {
|
92 |
+
const pathLineGeo = new THREE.BufferGeometry().setFromPoints(pts);
|
93 |
+
const pathLine = new THREE.Line(pathLineGeo, pathLineMat);
|
94 |
+
pathLine.position.y = 0.01;
|
95 |
+
this.scene.add(pathLine);
|
96 |
+
}
|
97 |
}
|
98 |
|
99 |
addSegment(a, b) {
|
src/utils/utils.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import * as THREE from "three";
|
2 |
-
import { PATH_POINTS, GRID_CELL_SIZE } from "../config/gameConfig.js";
|
3 |
|
4 |
// Snap value to grid (to nearest grid line)
|
5 |
export function snapToGrid(value, size = GRID_CELL_SIZE) {
|
@@ -22,6 +22,18 @@ export function cellToWorldCenter(col, row, size = GRID_CELL_SIZE) {
|
|
22 |
// Check if point is on road
|
23 |
export function isOnRoad(p, pathPoints = PATH_POINTS) {
|
24 |
const halfWidth = 1.6;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
for (let i = 0; i < pathPoints.length - 1; i++) {
|
26 |
const a = pathPoints[i];
|
27 |
const b = pathPoints[i + 1];
|
|
|
1 |
import * as THREE from "three";
|
2 |
+
import { PATH_POINTS, PATHS, GRID_CELL_SIZE } from "../config/gameConfig.js";
|
3 |
|
4 |
// Snap value to grid (to nearest grid line)
|
5 |
export function snapToGrid(value, size = GRID_CELL_SIZE) {
|
|
|
22 |
// Check if point is on road
|
23 |
export function isOnRoad(p, pathPoints = PATH_POINTS) {
|
24 |
const halfWidth = 1.6;
|
25 |
+
// If branching paths are defined, check against all of them
|
26 |
+
if (Array.isArray(PATHS) && PATHS.length > 0) {
|
27 |
+
for (const pts of PATHS) {
|
28 |
+
for (let i = 0; i < pts.length - 1; i++) {
|
29 |
+
if (pointSegmentDistance2D(p, pts[i], pts[i + 1]) <= halfWidth) {
|
30 |
+
return true;
|
31 |
+
}
|
32 |
+
}
|
33 |
+
}
|
34 |
+
return false;
|
35 |
+
}
|
36 |
+
// Fallback to single path
|
37 |
for (let i = 0; i < pathPoints.length - 1; i++) {
|
38 |
const a = pathPoints[i];
|
39 |
const b = pathPoints[i + 1];
|