diff --git a/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs b/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs index 29cd90e..7c4662d 100644 --- a/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs +++ b/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs @@ -601,9 +601,9 @@ namespace TD.Gameplay } Vector3 spawnPos = GridCoordinates.GetFootprintCenterWorld(anchor, def.FootprintSize); - // Towers sit on the buildable plane (Y=0); raise slightly so the mesh base - // sits flush rather than half-clipped. A cube of scale 1 needs +0.5 on Y. - spawnPos.y = 0.5f; + // Place on the buildable plane (Y=0); SeatOnGround below corrects the final Y + // from the mesh bounds so the tower rests flush regardless of its pivot. + spawnPos.y = 0f; var go = Instantiate(def.TowerPrefab, spawnPos, Quaternion.identity); var instance = go.GetComponent(); @@ -626,9 +626,32 @@ namespace TD.Gameplay Destroy(go); return; } + + // Seat the tower flush on the ground from its mesh bounds — BEFORE Spawn so the + // corrected transform is captured in the spawn sync. Handles any pivot. + SeatOnGround(go, groundY: 0f); + netObj.Spawn(destroyWithScene: true); } + /// + /// Shifts vertically so the lowest point of its combined + /// renderer bounds rests at . Lets towers be authored with + /// any mesh pivot (center, base, etc.) and still sit flush on the ground — no + /// per-prefab Y tweaking. No-op if the prefab has no renderers. + /// + private static void SeatOnGround(GameObject go, float groundY) + { + var renderers = go.GetComponentsInChildren(); + if (renderers.Length == 0) return; + + Bounds b = renderers[0].bounds; + for (int i = 1; i < renderers.Length; i++) + b.Encapsulate(renderers[i].bounds); + + go.transform.position += Vector3.up * (groundY - b.min.y); + } + // ----- Utility ---------------------------------------------------- private static bool TryGetDefinition(int typeId, out TowerDefinition def)