3D: стройка видна (серое здание + «Стройка 0:45»), «Войска» знают о строящихся Казармах, кнопки зума +/−

- BuildTimer затемнял только 2D-спрайты: в 3D строящиеся Казармы выглядели готовыми, а окно «Войска» писало «казарм: 0»
- «Войска»: «Казармы строятся, осталось …»; перерисовка, когда достроились; «Построить Казармы» в 3D ставит призрак
- «+ / −» справа: в Device Simulator колесо мыши не доходит до игры

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-05 17:05:59 +01:00
co-authored by Claude Opus 5.5
parent f307c83352
commit 5502489c07
3 changed files with 133 additions and 5 deletions
+91
View File
@@ -207,8 +207,30 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
void OnTownHallLevel(TownHallLevelChangedEvent e) => ApplyTownHallLevel(e.Level);
// Сцена 3D-города открыта (для окон HUD: «Построить Казармы» ставит призрак здесь, а не в 2D-режиме стройки)
public static City3DController Instance { get; private set; }
void OnDestroy()
{
if (Instance == this) Instance = null;
}
// Начать ставить здание по имени 3D-типа ("Казармы"). false -- такого типа нет
public bool PlaceBuilding(string type3d)
{
int type = System.Array.FindIndex(Types, t => t.Name == type3d);
if (type <= 0) return false;
buildMenuOpen = false;
if (UIPanelManager.Instance != null) UIPanelManager.Instance.CloseAll();
string error = City3DGameplay.CanBuild(type3d);
if (error != null) Say(error);
StartPlacing(type);
return true;
}
void Start()
{
Instance = this;
cam = Camera.main;
Map3D.UseRenderer3D(cam); // обычный 3D-рендерер URP (на случай, если сцену собрали вручную)
cameraRig = cam.GetComponent<City3DCamera>() ?? cam.gameObject.AddComponent<City3DCamera>();
@@ -674,6 +696,7 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
{
if (!stateApplied && map != null) ApplySaved();
if (cam == null) return;
UpdateConstructionViews();
bool down, held, up;
Vector3 pos;
if (Input.touchCount > 0)
@@ -845,6 +868,69 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
float guiScale = 1f;
GUIStyle button, label, box;
// ---------------- Стройка по времени в 3D ----------------
// BuildTimer затемняет только 2D-спрайты, поэтому в 3D здание, которое ещё строится, выглядело готовым
// (Казармы «построены», а нанимать нельзя). Здесь: здание темнее и над ним -- «Стройка 0:45» (05.11)
readonly Dictionary<string, TextMeshPro> constructionLabels = new Dictionary<string, TextMeshPro>();
static readonly int BaseColorId = Shader.PropertyToID("_BaseColor"), ColorId = Shader.PropertyToID("_Color");
MaterialPropertyBlock constructionBlock;
void UpdateConstructionViews()
{
foreach (var pair in buildingViews)
{
var view = pair.Value;
if (view == null) continue;
var timer = BuildTimer.Of(view);
bool building = timer != null && BuildTimer.IsConstructing(view.transform);
constructionLabels.TryGetValue(pair.Key, out var label);
if (building)
{
if (label == null) label = constructionLabels[pair.Key] = StartConstructionLook(view);
label.text = "Стройка " + GameData.FormatTime(timer.SecondsLeft);
label.transform.rotation = cam.transform.rotation;
}
else if (label != null)
{
Destroy(label.gameObject);
constructionLabels.Remove(pair.Key);
foreach (var r in view.GetComponentsInChildren<Renderer>()) if (!(r is TMPro.TMP_SubMesh)) r.SetPropertyBlock(null);
}
}
}
TextMeshPro StartConstructionLook(GameObject view)
{
if (constructionBlock == null) constructionBlock = new MaterialPropertyBlock();
Bounds bounds = new Bounds(view.transform.position, Vector3.zero);
bool first = true;
foreach (var r in view.GetComponentsInChildren<Renderer>())
{
if (first) { bounds = r.bounds; first = false; } else bounds.Encapsulate(r.bounds);
var m = r.sharedMaterial;
if (m == null) continue;
Color c = m.HasProperty(BaseColorId) ? m.GetColor(BaseColorId) : m.HasProperty(ColorId) ? m.GetColor(ColorId) : Color.white;
Color grey = Color.Lerp(c, new Color(0.45f, 0.42f, 0.38f), 0.6f) * 0.75f;
grey.a = 1f;
r.GetPropertyBlock(constructionBlock);
constructionBlock.SetColor(BaseColorId, grey);
constructionBlock.SetColor(ColorId, grey);
r.SetPropertyBlock(constructionBlock);
}
var go = new GameObject("ConstructionLabel");
go.transform.SetParent(view.transform, false);
go.transform.position = new Vector3(bounds.center.x, bounds.max.y + 0.7f, bounds.center.z);
var label = go.AddComponent<TextMeshPro>();
label.fontSize = 7f;
label.alignment = TextAlignmentOptions.Center;
label.rectTransform.sizeDelta = new Vector2(8f, 1.5f);
label.color = new Color(1f, 0.85f, 0.4f);
label.outlineWidth = 0.25f;
label.outlineColor = Color.black;
return label;
}
bool OverGui(Vector3 screen)
{
var p = new Vector2(screen.x / guiScale, (Screen.height - screen.y) / guiScale);
@@ -900,6 +986,11 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
}
}
// Справа по центру: приближение / отдаление. На телефоне есть щипок двумя пальцами, но в редакторе
// (Device Simulator) колесо мыши не доходит до игры, а щипок мышью не сделать -- без кнопок карту не отдалить (05.11)
if (Button(new Rect(w - 70, h / 2 - 62, 60, 56), "+")) cameraRig.Zoom(0.2f);
if (Button(new Rect(w - 70, h / 2 + 2, 60, 56), "−")) cameraRig.Zoom(-0.25f);
// Слева, правее кнопок HUD: что построить -- открывается кнопкой "СТРОЙКА" общего HUD
if (buildMenuOpen && (mode == Mode.View || mode == Mode.Edit))
{