victor HF Staff commited on
Commit
e4d85e6
·
1 Parent(s): 7fc2258
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
- // Start at top edge (slightly left of center)
169
- new THREE.Vector3(-6 * GRID_CELL_SIZE, 0, HALF), // (-12, 36)
170
- // Move inside from top edge
171
- new THREE.Vector3(-6 * GRID_CELL_SIZE, 0, HALF - PATH_MARGIN), // (-12, 28)
172
- // Horizontal to right (top band), reduced width
173
- new THREE.Vector3(8 * GRID_CELL_SIZE, 0, HALF - PATH_MARGIN), // (16, 28)
174
- // Down to upper-middle (tighter vertical spacing)
175
- new THREE.Vector3(8 * GRID_CELL_SIZE, 0, 4 * GRID_CELL_SIZE), // (16, 8)
176
- // Back left (first U-turn), reduced width
177
- new THREE.Vector3(-5 * GRID_CELL_SIZE, 0, 4 * GRID_CELL_SIZE), // (-10, 8)
178
- // Down to lower-middle (tighter)
179
- new THREE.Vector3(-5 * GRID_CELL_SIZE, 0, -5 * GRID_CELL_SIZE), // (-10, -10)
180
- // Across right (second U-turn), reduced width
181
- new THREE.Vector3(7 * GRID_CELL_SIZE, 0, -5 * GRID_CELL_SIZE), // (14, -10)
182
- // Exit on bottom edge
183
- new THREE.Vector3(7 * GRID_CELL_SIZE, 0, -HALF), // (14, -36)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- PATH_POINTS,
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
- // Clone and snap path points to the grid to ensure alignment
12
- this.pathPoints = PATH_POINTS.map((p) => {
13
- const snapped = p.clone
14
- ? p.clone()
15
- : new THREE.Vector3(p.x, p.y ?? 0, p.z);
16
- snapped.x = Math.round(snapped.x / GRID_CELL_SIZE) * GRID_CELL_SIZE;
17
- snapped.z = Math.round(snapped.z / GRID_CELL_SIZE) * GRID_CELL_SIZE;
18
- return snapped;
19
- });
 
 
 
20
  this.roadMeshes = [];
21
 
22
  // Materials
@@ -28,44 +32,68 @@ export class PathBuilder {
28
  }
29
 
30
  buildPath() {
31
- // Visualize path line
32
- this.createPathLine();
33
 
34
- // Build straight road segments only (no bevels or rounded corners)
35
- for (let i = 0; i < this.pathPoints.length - 1; i++) {
36
- this.addSegment(this.pathPoints[i], this.pathPoints[i + 1]);
37
- }
 
 
 
 
 
 
 
38
 
39
- // Add square fill tiles at right-angle corners for perfect joins
40
- for (let i = 1; i < this.pathPoints.length - 1; i++) {
41
- const prev = this.pathPoints[i - 1];
42
- const cur = this.pathPoints[i];
43
- const next = this.pathPoints[i + 1];
 
 
 
 
 
 
 
44
 
45
- const v1 = new THREE.Vector3().subVectors(cur, prev);
46
- const v2 = new THREE.Vector3().subVectors(next, cur);
47
- // Consider only x/z for pathing (top-down)
48
- v1.y = 0;
49
- v2.y = 0;
 
 
50
 
51
- // Normalize direction to axis-aligned unit vectors when possible
52
- const isV1H = Math.abs(v1.x) > Math.abs(v1.z);
53
- const isV2H = Math.abs(v2.x) > Math.abs(v2.z);
54
- const isTurn = (isV1H && !isV2H) || (!isV1H && isV2H);
55
- if (isTurn) {
56
- this.addCornerTile(cur);
 
 
 
 
 
 
 
 
57
  }
58
  }
59
  }
60
 
61
- createPathLine() {
62
  const pathLineMat = new THREE.LineBasicMaterial({ color: 0xffff00 });
63
- const pathLineGeo = new THREE.BufferGeometry().setFromPoints(
64
- this.pathPoints
65
- );
66
- const pathLine = new THREE.Line(pathLineGeo, pathLineMat);
67
- pathLine.position.y = 0.01;
68
- this.scene.add(pathLine);
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];