using Bogatyri.Settlement.Placement; using UnityEngine; // ГЕРОЙ В 3D-ПОСЕЛЕНИИ (перенос из 2D, решение 05.11: всё из 2D-города -- в 3D). Как HeroWalker в 2D-городе: // - живёт у Терема богатыря: появляется у его ворот, когда Терем достроен, и исчезает, если Терема нет; // - гуляет по свободным клеткам 3D-карты (земля, открытая зона, без зданий и деревьев) не дальше WalkRadius от Терема: // шаг -- на соседнюю свободную клетку, поэтому сквозь здания и деревья не проходит; после нескольких шагов стоит 1-3 с; // - тап по герою -- фраза и окно героя (окно Терема); в режиме стройки и переноса прячется; // - пока без арта: фигурка из простых форм (тело, голова, шлем, щит, копьё). Арт подставим в BuildFigure. // Только оживление города: на экономику, бой и сохранение не влияет. Создаёт и убирает City3DController. public class City3DHero : MonoBehaviour { public static City3DHero Instance { get; private set; } const float Speed = 1.3f; // клеток в секунду const int WalkRadius = 6; // насколько далеко уходит от Терема static readonly string[] Phrases = { "Город под защитой!", "Веди, воевода, — не подведу.", "Силушка богатырская при мне.", "Дозором обхожу владения.", "Есть ли где ворог? Только скажи.", }; SettlementMap map; System.Func townHallLevel; public string CastleId { get; private set; } public GameObject CastleView { get; private set; } Transform figure; Vector2Int cell; // клетка, к которой идёт (или на которой стоит) Vector2Int previous; // откуда пришёл -- чтобы не топтаться туда-обратно int stepsLeft; float waitLeft; // Видимость: в режиме стройки и переноса герой прячется, чтобы не мешать ставить здания public bool Hidden { get => figure != null && !figure.gameObject.activeSelf; set { if (figure != null && figure.gameObject.activeSelf == value) figure.gameObject.SetActive(!value); } } public static City3DHero Spawn(Transform parent, SettlementMap map, string castleId, GameObject castleView, System.Func townHallLevel) { if (Instance != null) Destroy(Instance.gameObject); var go = new GameObject("Hero3D"); go.transform.SetParent(parent, false); var h = go.AddComponent(); h.map = map; h.CastleId = castleId; h.CastleView = castleView; h.townHallLevel = townHallLevel; h.BuildFigure(); h.cell = h.previous = h.Gate(); go.transform.position = CellWorld(h.cell); h.waitLeft = 1f; Instance = h; return h; } void OnDestroy() { if (Instance == this) Instance = null; } // ---------------- Внешний вид (заглушка) ---------------- void BuildFigure() { figure = new GameObject("Figure").transform; figure.SetParent(transform, false); Part(PrimitiveType.Capsule, new Vector3(0, 0.32f, 0), new Vector3(0.28f, 0.3f, 0.22f), new Color(0.55f, 0.4f, 0.2f)); // кафтан Part(PrimitiveType.Sphere, new Vector3(0, 0.72f, 0), new Vector3(0.2f, 0.2f, 0.2f), new Color(1f, 0.85f, 0.7f)); // голова Part(PrimitiveType.Sphere, new Vector3(0, 0.8f, 0), new Vector3(0.22f, 0.14f, 0.22f), new Color(0.75f, 0.75f, 0.8f)); // шлем Part(PrimitiveType.Cylinder, new Vector3(0, 0.9f, 0), new Vector3(0.03f, 0.06f, 0.03f), new Color(0.75f, 0.75f, 0.8f)); // шишак Part(PrimitiveType.Cube, new Vector3(0.17f, 0.36f, 0.04f), new Vector3(0.04f, 0.26f, 0.2f), new Color(0.8f, 0.15f, 0.15f)); // щит Part(PrimitiveType.Cylinder, new Vector3(-0.17f, 0.5f, 0), new Vector3(0.025f, 0.5f, 0.025f), new Color(0.45f, 0.3f, 0.15f)); // копьё Part(PrimitiveType.Cylinder, new Vector3(0, 0.005f, 0), new Vector3(0.36f, 0.005f, 0.36f), new Color(0.18f, 0.22f, 0.14f)); // тень // Тап по герою: коллайдер чуть больше фигурки (City3DController ищет City3DHero под пальцем) var hit = gameObject.AddComponent(); hit.center = new Vector3(0, 0.5f, 0); hit.size = new Vector3(0.7f, 1.1f, 0.7f); } void Part(PrimitiveType type, Vector3 pos, Vector3 scale, Color color) { var p = GameObject.CreatePrimitive(type); Destroy(p.GetComponent()); p.transform.SetParent(figure, false); p.transform.localPosition = pos; p.transform.localScale = scale; p.GetComponent().sharedMaterial = Map3D.Mat(color); } // ---------------- Прогулка по клеткам ---------------- static Vector3 CellWorld(Vector2Int c) { float x = c.x + 0.5f, z = c.y + 0.5f; return new Vector3(x, City3DLayout.HeightAt(x, z), z); } bool Walkable(int x, int y) { if (map == null || !map.InMap(x, y)) return false; if (map.KindAt(x, y) != CellKind.Ground || !map.IsOpen(x, y, townHallLevel != null ? townHallLevel() : 1)) return false; return map.BuildingAt(x, y) == null && map.ObstacleAt(x, y) == null; } bool CastleRect(out PlacedRect r) { r = default; return map != null && map.TryGetBuilding(CastleId, out r); } bool NearHome(int x, int y) { if (!CastleRect(out var r)) return true; float cx = r.X + r.W / 2f, cy = r.Y + r.H / 2f; return Mathf.Abs(x + 0.5f - cx) <= WalkRadius + r.W / 2f && Mathf.Abs(y + 0.5f - cy) <= WalkRadius + r.H / 2f; } // Ворота: ближайшая свободная клетка вокруг Терема (с нижней стороны -- первой) Vector2Int Gate() { if (!CastleRect(out var r)) return Vector2Int.zero; for (int ring = 1; ring <= WalkRadius; ring++) for (int x = r.X - ring; x < r.X + r.W + ring; x++) foreach (int y in new[] { r.Y - ring, r.Y + r.H - 1 + ring }) if (Walkable(x, y)) return new Vector2Int(x, y); return new Vector2Int(r.X + r.W / 2, r.Y - 1); } static readonly Vector2Int[] Dirs = { Vector2Int.up, Vector2Int.down, Vector2Int.left, Vector2Int.right }; // Следующая клетка: соседняя свободная рядом с Теремом; назад -- только если больше некуда Vector2Int NextCell() { var options = new System.Collections.Generic.List(); foreach (var d in Dirs) { var c = cell + d; if (c != previous && Walkable(c.x, c.y) && NearHome(c.x, c.y)) options.Add(c); } if (options.Count == 0 && Walkable(previous.x, previous.y)) return previous; return options.Count > 0 ? options[Random.Range(0, options.Count)] : cell; } void Update() { if (Hidden) return; if (!Walkable(cell.x, cell.y) && cell != Gate()) { cell = previous = Gate(); transform.position = CellWorld(cell); } // на клетку поставили здание if (waitLeft > 0f) { waitLeft -= Time.deltaTime; figure.localPosition = Vector3.zero; return; } Vector3 goal = CellWorld(cell), pos = transform.position; Vector3 flat = new Vector3(goal.x - pos.x, 0, goal.z - pos.z); if (flat.magnitude < 0.03f) { if (stepsLeft <= 0) { stepsLeft = Random.Range(3, 7); waitLeft = Random.Range(1f, 3f); return; } stepsLeft--; var next = NextCell(); previous = cell; cell = next; return; } Vector3 step = flat.normalized * Speed * Time.deltaTime; if (step.magnitude > flat.magnitude) step = flat; pos += step; pos.y = City3DLayout.HeightAt(pos.x, pos.z); // по земле, через холмы transform.position = pos; transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(flat), Time.deltaTime * 10f); // смотрит, куда идёт figure.localPosition = new Vector3(0f, Mathf.Abs(Mathf.Sin(Time.time * 10f)) * 0.04f, 0f); // покачивание при ходьбе } // ---------------- Тап ---------------- public void OnTapped() { UIKit.Toast("Богатырь: " + Phrases[Random.Range(0, Phrases.Length)]); waitLeft = 2f; // остановился поговорить if (CastleView != null) BuildingWindow.Open(CastleView); } }