Adding 9 Player level

This commit is contained in:
Matt F 2026-05-21 23:36:19 -07:00
parent fdada6f132
commit a7be12fa9b
30 changed files with 45984 additions and 300 deletions

View file

@ -157,13 +157,13 @@ namespace TD.Levels
if (!initialized) return;
// Convert tile-corner indices to world-space corners. Tiles are center-based with
// TILE_SIZE = 1, so a tile at (x,y) spans world XZ from (x-0.5, y-0.5) to (x+0.5, y+0.5).
float halfTile = GridCoordinates.TILE_SIZE * 0.5f;
Vector3 sw = new Vector3(minTile.x - halfTile, MapBoundsY, minTile.y - halfTile);
Vector3 se = new Vector3(maxTile.x + halfTile, MapBoundsY, minTile.y - halfTile);
Vector3 ne = new Vector3(maxTile.x + halfTile, MapBoundsY, maxTile.y + halfTile);
Vector3 nw = new Vector3(minTile.x - halfTile, MapBoundsY, maxTile.y + halfTile);
// Convert tile-corner indices to world-space corners. Tiles are edge-aligned with
// TILE_SIZE = 1, so a tile at (x,y) spans world XZ from (x, y) to (x+1, y+1).
float tileSize = GridCoordinates.TILE_SIZE;
Vector3 sw = new Vector3(minTile.x * tileSize, MapBoundsY, minTile.y * tileSize);
Vector3 se = new Vector3((maxTile.x + 1) * tileSize, MapBoundsY, minTile.y * tileSize);
Vector3 ne = new Vector3((maxTile.x + 1) * tileSize, MapBoundsY, (maxTile.y + 1) * tileSize);
Vector3 nw = new Vector3(minTile.x * tileSize, MapBoundsY, (maxTile.y + 1) * tileSize);
Color prev = Gizmos.color;
Gizmos.color = new Color(1f, 1f, 1f, 0.6f); // muted white
@ -219,8 +219,9 @@ namespace TD.Levels
VolumeBase.RasterizeBoundsToTiles(col.bounds, t => set.Add(t));
}
// For each owner, draw perimeter using the general algorithm.
float halfTile = GridCoordinates.TILE_SIZE * 0.5f;
// For each owner, draw perimeter using the general algorithm. Tile (x,y) spans
// world XZ from (x, y) to (x+1, y+1) (edge-aligned, TILE_SIZE = 1).
float tileSize = GridCoordinates.TILE_SIZE;
Color prev = Gizmos.color;
foreach (var kv in perOwner)
@ -231,10 +232,10 @@ namespace TD.Levels
foreach (var tile in kv.Value)
{
// World-space corners of this tile.
Vector3 sw = new Vector3(tile.x - halfTile, CombinedZoneOutlineY, tile.y - halfTile);
Vector3 se = new Vector3(tile.x + halfTile, CombinedZoneOutlineY, tile.y - halfTile);
Vector3 ne = new Vector3(tile.x + halfTile, CombinedZoneOutlineY, tile.y + halfTile);
Vector3 nw = new Vector3(tile.x - halfTile, CombinedZoneOutlineY, tile.y + halfTile);
Vector3 sw = new Vector3(tile.x * tileSize, CombinedZoneOutlineY, tile.y * tileSize);
Vector3 se = new Vector3((tile.x + 1) * tileSize, CombinedZoneOutlineY, tile.y * tileSize);
Vector3 ne = new Vector3((tile.x + 1) * tileSize, CombinedZoneOutlineY, (tile.y + 1) * tileSize);
Vector3 nw = new Vector3(tile.x * tileSize, CombinedZoneOutlineY, (tile.y + 1) * tileSize);
// For each of the four edges, draw it ONLY if the neighbor across that edge
// is not in the covered set (i.e., the edge is on the perimeter).

View file

@ -38,6 +38,15 @@ namespace TD.Levels
[Tooltip("Path to the scene this LevelData was baked from. Auto-populated by bake.")]
public string ScenePath;
/// <summary>
/// Just the scene name (no path, no extension) — what NetworkManager.SceneManager.LoadScene
/// expects. Derived from <see cref="ScenePath"/>; returns empty string if ScenePath is unset.
/// </summary>
public string SceneName =>
string.IsNullOrEmpty(ScenePath)
? string.Empty
: System.IO.Path.GetFileNameWithoutExtension(ScenePath);
// -------------------------------------------------------------------
// Bake metadata (used for dirty-detection and diagnostic display).
// -------------------------------------------------------------------

View file

@ -75,21 +75,28 @@ namespace TD.Levels
if (!TryGetTightTileRect(out Vector2Int minTile, out Vector2Int maxTile)) return;
const float thickness = 0.04f;
float halfTile = GridCoordinates.TILE_SIZE * 0.5f;
float tileSize = GridCoordinates.TILE_SIZE;
// Three concentric rectangles: the original edge, slightly inset, slightly outset.
DrawOutlineAtInset(minTile, maxTile, halfTile, yLevel, 0f, outlineColor);
DrawOutlineAtInset(minTile, maxTile, halfTile, yLevel, +thickness, outlineColor);
DrawOutlineAtInset(minTile, maxTile, halfTile, yLevel, -thickness, outlineColor);
DrawOutlineAtInset(minTile, maxTile, tileSize, yLevel, 0f, outlineColor);
DrawOutlineAtInset(minTile, maxTile, tileSize, yLevel, +thickness, outlineColor);
DrawOutlineAtInset(minTile, maxTile, tileSize, yLevel, -thickness, outlineColor);
}
private static void DrawOutlineAtInset(Vector2Int minTile, Vector2Int maxTile, float halfTile,
// Tile (x, y) spans world XZ [x, x+1] (edge-aligned). The outline corners are at the
// tile rect's outer edges; `inset` shifts them outward (positive) or inward (negative)
// to draw the concentric thickness rectangles.
private static void DrawOutlineAtInset(Vector2Int minTile, Vector2Int maxTile, float tileSize,
float yLevel, float inset, Color color)
{
Vector3 sw = new Vector3(minTile.x - halfTile - inset, yLevel, minTile.y - halfTile - inset);
Vector3 se = new Vector3(maxTile.x + halfTile + inset, yLevel, minTile.y - halfTile - inset);
Vector3 ne = new Vector3(maxTile.x + halfTile + inset, yLevel, maxTile.y + halfTile + inset);
Vector3 nw = new Vector3(minTile.x - halfTile - inset, yLevel, maxTile.y + halfTile + inset);
float minX = minTile.x * tileSize - inset;
float maxX = (maxTile.x + 1) * tileSize + inset;
float minZ = minTile.y * tileSize - inset;
float maxZ = (maxTile.y + 1) * tileSize + inset;
Vector3 sw = new Vector3(minX, yLevel, minZ);
Vector3 se = new Vector3(maxX, yLevel, minZ);
Vector3 ne = new Vector3(maxX, yLevel, maxZ);
Vector3 nw = new Vector3(minX, yLevel, maxZ);
Color prev = Gizmos.color;
Gizmos.color = color;

View file

@ -91,8 +91,12 @@ namespace TD.Levels
// -------------------------------------------------------------------
// Static rasterization helper. Single source of truth for converting a Bounds to the
// set of tiles its rasterization covers. The bake will use the same primitive (a tile
// is "covered" iff bounds.Contains(tileCenter)) so gizmos and bake stay in lock-step.
// set of tiles its rasterization covers. Uses a half-open interval (min inclusive,
// max exclusive) on XZ so that:
// - a volume sized to N whole tiles rasterizes to exactly N tiles (no off-by-one),
// - two volumes that share a boundary (e.g., one ending at X=20, next starting at
// X=20) do not double-claim the boundary tile.
// Gizmos and bake share this helper so they stay in lock-step.
// -------------------------------------------------------------------
/// <summary>
@ -100,8 +104,8 @@ namespace TD.Levels
/// <paramref name="onTile"/> for each one. The candidate range is computed via
/// <see cref="GridCoordinates.WorldToGrid"/> with a one-tile padding on each side to guard
/// against rounding surprises (WorldToGrid uses round-to-nearest, which can over- or
/// under-shoot by one when bounds align with tile edges); the per-tile
/// <c>bounds.Contains(tileCenter)</c> test inside the loop guarantees correctness.
/// under-shoot by one when bounds align with tile edges); the per-tile half-open
/// interval test inside the loop guarantees correctness.
/// </summary>
public static void RasterizeBoundsToTiles(Bounds bounds, System.Action<Vector2Int> onTile)
{
@ -120,8 +124,11 @@ namespace TD.Levels
for (int y = yLo; y <= yHi; y++)
{
Vector3 tileCenter = GridCoordinates.GridToWorld(new Vector2Int(x, y));
Vector3 testPoint = new Vector3(tileCenter.x, bounds.center.y, tileCenter.z);
if (bounds.Contains(testPoint))
// Half-open interval on XZ: min inclusive, max exclusive. Y axis is implicitly
// satisfied because we test at bounds.center.y, which is always within Y range.
bool xIn = tileCenter.x >= bounds.min.x && tileCenter.x < bounds.max.x;
bool zIn = tileCenter.z >= bounds.min.z && tileCenter.z < bounds.max.z;
if (xIn && zIn)
{
onTile(new Vector2Int(x, y));
}
@ -226,12 +233,12 @@ namespace TD.Levels
if (!TryGetTightTileRect(out Vector2Int minTile, out Vector2Int maxTile)) return;
// Convert tile-corner indices to world-space corners. A tile at (x, y) spans world XZ
// from (x - 0.5, y - 0.5) to (x + 0.5, y + 0.5) (TILE_SIZE = 1, center-based).
float halfTile = GridCoordinates.TILE_SIZE * 0.5f;
Vector3 swCorner = new Vector3(minTile.x - halfTile, yLevel, minTile.y - halfTile);
Vector3 seCorner = new Vector3(maxTile.x + halfTile, yLevel, minTile.y - halfTile);
Vector3 neCorner = new Vector3(maxTile.x + halfTile, yLevel, maxTile.y + halfTile);
Vector3 nwCorner = new Vector3(minTile.x - halfTile, yLevel, maxTile.y + halfTile);
// from (x, y) to (x+1, y+1) (TILE_SIZE = 1, edge-aligned).
float tileSize = GridCoordinates.TILE_SIZE;
Vector3 swCorner = new Vector3(minTile.x * tileSize, yLevel, minTile.y * tileSize);
Vector3 seCorner = new Vector3((maxTile.x + 1) * tileSize, yLevel, minTile.y * tileSize);
Vector3 neCorner = new Vector3((maxTile.x + 1) * tileSize, yLevel, (maxTile.y + 1) * tileSize);
Vector3 nwCorner = new Vector3(minTile.x * tileSize, yLevel, (maxTile.y + 1) * tileSize);
Color prev = Gizmos.color;
Gizmos.color = outlineColor;