Только 3D-поселение (решение 05.11): Золотой рудник, Кристалл и Терем в 3D, перенос 2D-города, перспективная камера, MVP1 вне сборки

- Золотой рудник, Кристалл, Терем: цены, уровни Ратуши и компоненты как в 2D
- Перенос: нет 3D-города в сохранении, есть 2D -- Ратуша с уровнем и улучшением, здания с уровнями и стройками; секция settlement не меняется
- Камера перспективная (City3DCamera.perspective, поле зрения 40 -- ТЕСТ)
- Город -- City3D; MVP1 выключена в Build Settings, код и сцена 2D остаются
- Меню стройки в два столбца

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-05 17:31:25 +01:00
co-authored by Claude Opus 5.5
parent 4fe2dced76
commit 65f5039f74
9 changed files with 128 additions and 9 deletions
+77 -4
View File
@@ -49,6 +49,10 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
new BuildingType { Name = "Амбар", W = 2, H = 3, Height = 1.8f, Color = new Color(0.8f, 0.65f, 0.3f) },
new BuildingType { Name = "Казармы", W = 3, H = 3, Height = 2.0f, Color = new Color(0.75f, 0.3f, 0.3f) },
new BuildingType { Name = "Госпиталь", W = 3, H = 3, Height = 1.8f, Color = new Color(0.85f, 0.95f, 1f) },
// Из 2D-города (решение 05.11): золото, кристаллы, дом героя. Новые типы -- только в конец: сохранение хранит имя
new BuildingType { Name = "Золотой рудник", W = 2, H = 2, Height = 1.5f, Color = new Color(0.95f, 0.8f, 0.3f) },
new BuildingType { Name = "Кристалл", W = 2, H = 2, Height = 1.2f, Color = new Color(0.5f, 0.8f, 0.95f) },
new BuildingType { Name = "Терем", W = 3, H = 3, Height = 2.4f, Color = new Color(0.9f, 0.75f, 0.45f) },
};
enum Mode { View, Edit, Place, Move }
@@ -186,12 +190,64 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
if (type == 0) { hasTownHall = true; townHallLevel = Mathf.Clamp(e.level, 1, 5); }
}
}
if (!hasTownHall) PlaceNew(0, c - 2, c - 2, 0); // новый город: Ратуша в центре
if (!hasTownHall)
{
PlaceNew(0, c - 2, c - 2, 0); // новый город: Ратуша в центре
if (d == null) MoveFrom2D(); // 3D-города в сохранении ещё нет, а 2D есть -- перенести постройки игрока
}
UpdateGrass();
RefreshGround();
if (CitySync.Instance != null) CitySync.Instance.SyncNow();
}
// ---------------- Перенос 2D-города (решение 05.11: 2D-город больше не развиваем) ----------------
// Один раз, когда в сохранении нет 3D-города: Ратуша получает уровень (и улучшение, которое идёт) из 2D,
// остальные здания встают на ближайшие свободные места от центра с теми же уровнем и стройкой.
// 2D-данные в файле не меняются. Здание, которому не нашлось места или типа, пишется в Console
void MoveFrom2D()
{
var old = SettlementModule.Saved2D;
if (old == null || (old.townHallLevel <= 1 && (old.placedBuildings == null || old.placedBuildings.Count == 0))) return;
string hallId = null;
foreach (var pair in buildingType) if (pair.Value == 0) hallId = pair.Key;
var th = hallId != null ? buildingViews[hallId].GetComponent<TownHall>() : null;
if (th != null)
{
th.currentLevel = Mathf.Clamp(old.townHallLevel, 1, th.maxLevel);
ApplyTownHallLevel(th.currentLevel); // зоны -- до расстановки, чтобы здания поместились
if (old.townHallTimerEndMs > 0 && old.townHallTimerTargetLevel > th.currentLevel)
BuildTimer.Begin(th.gameObject, BuildTimer.Kind.Upgrade, old.townHallTimerTargetLevel, old.townHallTimerStartMs, old.townHallTimerEndMs);
}
int c = City3DLayout.Size / 2, moved = 0, lost = 0;
foreach (var e in old.placedBuildings ?? new List<SettlementModule.PlacedBuildingSaveEntry>())
{
string type3d = City3DGameplay.Type3DFor(e.buildingName);
int type = type3d != null ? System.Array.FindIndex(Types, t => t.Name == type3d) : -1;
if (type <= 0) { Debug.LogWarning($"[3D] Перенос 2D-города: здания «{e.buildingName}» в 3D нет -- пропущено"); lost++; continue; }
var t = Types[type];
if (!FindFreeSpot(t, c - t.W / 2, c - t.H / 2, out int x, out int y, out int rot) || !PlaceNew(type, x, y, rot))
{
Debug.LogWarning($"[3D] Перенос 2D-города: для «{e.buildingName}» нет свободного места -- пропущено");
lost++;
continue;
}
string id = "b_" + (nextBuildingId - 1);
var view = buildingViews[id];
var leveled = view.GetComponent<ILeveledBuilding>();
if (leveled != null) leveled.Level = Mathf.Max(1, e.level);
City3DSave.ReadTimer(new City3DSave.Entry
{
level = Mathf.Max(1, e.level), timerKind = e.timerKind, timerStartMs = e.timerStartMs,
timerEndMs = e.timerEndMs, timerTargetLevel = e.timerTargetLevel,
}, view);
moved++;
}
Debug.Log($"[3D] Перенос 2D-города: Ратуша {old.townHallLevel} ур., зданий перенесено {moved}" + (lost > 0 ? $", пропущено {lost}" : ""));
if (moved > 0 || lost > 0) Say(lost == 0 ? $"Город перенесён в 3D: {moved} зданий" : $"Город перенесён в 3D: {moved} зданий, {lost} не поместились");
}
// Кнопка "Перенести" в окне здания
void MoveFromWindow(GameObject view)
{
@@ -425,7 +481,10 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
case "Госпиталь": return new Color(0.45f, 0.55f, 0.65f);
case "Казармы": return new Color(0.4f, 0.15f, 0.12f);
case "Каменоломня":
case "Шахта": return new Color(0.35f, 0.35f, 0.38f);
case "Шахта":
case "Золотой рудник": return new Color(0.35f, 0.35f, 0.38f);
case "Кристалл": return new Color(0.25f, 0.4f, 0.6f);
case "Терем": return new Color(0.75f, 0.2f, 0.15f);
default: return Color.Lerp(Types[type].Color, new Color(0.3f, 0.15f, 0.1f), 0.55f);
}
}
@@ -465,6 +524,19 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
case "Шахта":
Unit(body, PrimitiveType.Cube, new Vector3(0, 0.2f, -0.505f), new Vector3(0.35f, 0.4f, 0.02f), new Color(0.1f, 0.1f, 0.12f));
break;
case "Золотой рудник":
Unit(body, PrimitiveType.Cube, new Vector3(0, 0.2f, -0.505f), new Vector3(0.35f, 0.4f, 0.02f), new Color(0.1f, 0.1f, 0.12f));
Unit(body, PrimitiveType.Cube, new Vector3(0.32f, 0.08f, -0.62f), new Vector3(0.18f, 0.16f, 0.18f), new Color(1f, 0.82f, 0.2f)); // самородок у входа
break;
case "Кристалл":
var gem = Map3D.Shape(body, "Gem", Map3D.Pyramid, new Color(0.35f, 0.85f, 1f), Vector3.zero, Vector2.one * 0.45f, 0.7f, 45f).transform;
gem.localPosition = new Vector3(0, 1.0f, 0);
break;
case "Терем":
Unit(body, PrimitiveType.Cube, new Vector3(-0.3f, 0.75f, -0.3f), new Vector3(0.3f, 1.5f, 0.3f), walls);
var dome = Map3D.Shape(body, "Dome", Map3D.Pyramid, new Color(0.95f, 0.75f, 0.2f), Vector3.zero, Vector2.one * 0.42f, 0.45f, 45f).transform;
dome.localPosition = new Vector3(-0.3f, 1.5f, -0.3f);
break;
case "Ферма":
// грядки перед амбаром
for (int i = 0; i < 3; i++)
@@ -1008,10 +1080,11 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
// Слева, правее кнопок HUD: что построить -- открывается кнопкой "СТРОЙКА" общего HUD
if (buildMenuOpen && (mode == Mode.View || mode == Mode.Edit))
{
float y = HudTop;
float y = HudTop, x = HudLeft;
for (int i = 1; i < Types.Length; i++)
{
if (Button(new Rect(HudLeft, y, 200, 40), $"{Types[i].Name} {Types[i].W}×{Types[i].H}")) { buildMenuOpen = false; StartPlacing(i); }
if (y + 40 > h - HudBottom) { y = HudTop; x += 206; } // не помещается до нижней панели -- следующий столбец
if (Button(new Rect(x, y, 200, 40), $"{Types[i].Name} {Types[i].W}×{Types[i].H}")) { buildMenuOpen = false; StartPlacing(i); }
y += 44;
}
}