using UnityEngine; // МОДУЛЬ ПОСЕЛЕНИЯ. Документ: Docs/Modules/Settlement.md // Призрак здания в режиме стройки: полупрозрачная копия картинки здания и пятно его клеток под ней. // Пятно зелёное -- сюда можно ставить, красное -- клетки заняты или за границей. // Только картинка: скриптов и коллайдеров префаба у призрака нет, экономика его не видит. public class BuildGhost : MonoBehaviour { public BuildingData Data { get; private set; } public BuildFootprint Footprint { get; private set; } public Vector2Int Cell { get; private set; } // левая нижняя клетка на сетке public bool CellsFree { get; private set; } public static readonly Color FreeColor = new Color(0.25f, 1f, 0.35f, 0.45f); public static readonly Color BlockedColor = new Color(1f, 0.2f, 0.2f, 0.5f); SpriteRenderer pad; static Sprite white; public static BuildGhost Create(BuildingData data) { var go = new GameObject("BuildGhost_" + data.buildingName); var g = go.AddComponent(); g.Data = data; g.Footprint = BuildFootprint.Of(data); g.Build(); return g; } void Build() { // пятно клеток: белый квадрат 1×1, растянутый на размер здания var padGo = new GameObject("Cells"); padGo.transform.SetParent(transform, false); pad = padGo.AddComponent(); pad.sprite = WhiteSprite(); pad.sortingOrder = 3; padGo.transform.localScale = new Vector3(Footprint.size.x, Footprint.size.y, 1f); // копия картинки здания: позиция центра пятна = позиция префаба + anchorOffset var art = new GameObject("Art").transform; art.SetParent(transform, false); art.localPosition = -(Vector3)Footprint.anchorOffset; var prefab = Data.prefab; if (prefab == null) return; Vector3 root = prefab.transform.position; int minOrder = int.MaxValue; var renderers = prefab.GetComponentsInChildren(true); foreach (var sr in renderers) if (sr.sprite != null) minOrder = Mathf.Min(minOrder, sr.sortingOrder); foreach (var sr in renderers) { if (sr.sprite == null) continue; var copy = new GameObject(sr.name).transform; copy.SetParent(art, false); copy.localPosition = sr.transform.position - root; copy.localRotation = sr.transform.rotation; copy.localScale = sr.transform.lossyScale; var r = copy.gameObject.AddComponent(); r.sprite = sr.sprite; r.flipX = sr.flipX; r.flipY = sr.flipY; r.drawMode = sr.drawMode; if (sr.drawMode != SpriteDrawMode.Simple) r.size = sr.size; var c = sr.color; c.a *= 0.65f; r.color = c; r.sortingOrder = 4 + (sr.sortingOrder - minOrder); } } static Sprite WhiteSprite() { if (white != null) return white; var tex = new Texture2D(4, 4, TextureFormat.RGBA32, false); var px = new Color32[16]; for (int i = 0; i < px.Length; i++) px[i] = new Color32(255, 255, 255, 255); tex.SetPixels32(px); tex.Apply(); white = Sprite.Create(tex, new Rect(0, 0, 4, 4), new Vector2(0.5f, 0.5f), 4); return white; } // Поставить призрак на клетку (левая нижняя) и проверить, свободно ли public void MoveToCell(Vector2Int cell) { var grid = BuildGrid.Instance; Cell = cell; Vector2 center = grid.RectCenter(cell, Footprint.size); transform.position = new Vector3(center.x, center.y, 0f); Recheck(); } // Подвинуть так, чтобы центр пятна был ближе всего к точке мира public void MoveNear(Vector2 worldCenter) { MoveToCell(BuildGrid.Instance.SnapCell(worldCenter, Footprint.size)); } public void Recheck() { CellsFree = BuildGrid.Instance.CanPlace(Cell, Footprint.size); if (pad != null) pad.color = CellsFree ? FreeColor : BlockedColor; } // Точка мира попадает в пятно призрака (с запасом под палец) public bool Contains(Vector2 world, float margin = 0.4f) { Vector2 c = transform.position; Vector2 half = (Vector2)Footprint.size * 0.5f + Vector2.one * margin; return Mathf.Abs(world.x - c.x) <= half.x && Mathf.Abs(world.y - c.y) <= half.y; } // Куда ставить префаб, чтобы здание встало ровно на клетки призрака public Vector3 PrefabPosition => transform.position - (Vector3)Footprint.anchorOffset; }