using UnityEngine; // ГЕРОЙ В ГОРОДЕ -- живёт у Терема богатыря и гуляет по свободным клеткам рядом с ним. // - Пока без арта: фигурка из простых форм (тело, голова, щит) цвета героя. Арт подставим в BuildFigure. // - Идёт к случайной свободной клетке в радиусе WalkRadius от Терема, стоит 1-3 с, идёт дальше. // - Тап по герою -- он отвечает фразой и открывается окно героя. // - В режиме стройки герой прячется, чтобы не мешать ставить здания. // Это только оживление города: на экономику, бой и сохранение не влияет. public class HeroWalker : MonoBehaviour { public static HeroWalker Instance { get; private set; } const float Speed = 1.3f; // клеток в секунду const int WalkRadius = 6; // насколько далеко уходит от Терема const int SortingOrder = 40; // поверх зданий static readonly string[] Phrases = { "Город под защитой!", "Веди, воевода, — не подведу.", "Силушка богатырская при мне.", "Дозором обхожу владения.", "Есть ли где ворог? Только скажи.", }; HeroCastle castle; Vector3 target; float waitLeft; Transform figure; Vector3 downPos; BoxCollider2D tapArea; public static void Spawn(HeroCastle home) { if (Instance != null) { Instance.castle = home; return; } var go = new GameObject("Hero"); var w = go.AddComponent(); w.castle = home; w.BuildFigure(); go.transform.position = w.Gate(); w.target = go.transform.position; w.waitLeft = 1f; Instance = w; } public static void OnCastleRemoved(HeroCastle removed) { if (Instance == null || Instance.castle != removed) return; var next = HeroCastle.Main; if (next != null) Instance.castle = next; else Destroy(Instance.gameObject); } void OnDestroy() { if (Instance == this) Instance = null; } // ---------------- Внешний вид (заглушка) ---------------- void BuildFigure() { figure = new GameObject("Figure").transform; figure.SetParent(transform, false); Part("Body", Square(), new Color(0.55f, 0.4f, 0.2f), new Vector2(0f, 0.28f), new Vector2(0.38f, 0.48f), 0); Part("Head", Circle(), new Color(1f, 0.85f, 0.7f), new Vector2(0f, 0.66f), new Vector2(0.3f, 0.3f), 1); Part("Helmet", Circle(), new Color(0.75f, 0.75f, 0.8f), new Vector2(0f, 0.76f), new Vector2(0.32f, 0.16f), 2); Part("Shield", Circle(), new Color(0.8f, 0.15f, 0.15f), new Vector2(0.2f, 0.28f), new Vector2(0.22f, 0.3f), 3); Part("Shadow", Circle(), new Color(0f, 0f, 0f, 0.3f), new Vector2(0f, 0.02f), new Vector2(0.45f, 0.14f), -1); // Коллайдер для тапа -- чуть больше фигурки, ближе к камере, чем здания tapArea = gameObject.AddComponent(); tapArea.size = new Vector2(0.7f, 1f); tapArea.offset = new Vector2(0f, 0.45f); var p = transform.position; p.z = -0.5f; transform.position = p; } SpriteRenderer Part(string name, Sprite sprite, Color color, Vector2 pos, Vector2 size, int order) { var t = new GameObject(name).transform; t.SetParent(figure, false); t.localPosition = pos; t.localScale = new Vector3(size.x, size.y, 1f); var sr = t.gameObject.AddComponent(); sr.sprite = sprite; sr.color = color; sr.sortingOrder = SortingOrder + order; return sr; } static Sprite square, circle; static Sprite Square() { if (square != null) return square; 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(); square = Sprite.Create(tex, new Rect(0, 0, 4, 4), new Vector2(0.5f, 0.5f), 4); return square; } static Sprite Circle() { if (circle != null) return circle; const int n = 64; var tex = new Texture2D(n, n, TextureFormat.RGBA32, false); var px = new Color32[n * n]; float r = n * 0.5f; for (int y = 0; y < n; y++) for (int x = 0; x < n; x++) { float d = Mathf.Sqrt((x + 0.5f - r) * (x + 0.5f - r) + (y + 0.5f - r) * (y + 0.5f - r)); byte a = (byte)(Mathf.Clamp01(r - d) * 255); px[y * n + x] = new Color32(255, 255, 255, a); } tex.SetPixels32(px); tex.Apply(); circle = Sprite.Create(tex, new Rect(0, 0, n, n), new Vector2(0.5f, 0.5f), n); return circle; } // ---------------- Прогулка ---------------- Vector3 Gate() { var c = castle != null ? castle.transform.position : Vector3.zero; var grid = BuildGrid.Instance; if (grid != null && grid.FindFreeNear(c, Vector2Int.one, out Vector2Int cell)) { Vector2 p = grid.RectCenter(cell, Vector2Int.one); return new Vector3(p.x, p.y - 0.4f, -0.5f); } return new Vector3(c.x, c.y - 1f, -0.5f); } Vector3 PickTarget() { var grid = BuildGrid.Instance; if (grid == null || castle == null) return transform.position; grid.Refresh(); Vector2Int home = grid.WorldToCell(castle.transform.position); for (int attempt = 0; attempt < 20; attempt++) { int x = home.x + Random.Range(-WalkRadius, WalkRadius + 1); int y = home.y + Random.Range(-WalkRadius, WalkRadius + 1); if (grid.IsBusy(x, y)) continue; Vector2 p = grid.RectCenter(new Vector2Int(x, y), Vector2Int.one); return new Vector3(p.x, p.y - 0.4f, -0.5f); } return Gate(); } void Update() { bool building = BuildModeController.Instance != null && BuildModeController.Instance.IsActive; if (figure.gameObject.activeSelf == building) { figure.gameObject.SetActive(!building); if (tapArea != null) tapArea.enabled = !building; } if (building) return; if (waitLeft > 0f) { waitLeft -= Time.deltaTime; return; } Vector3 pos = transform.position; Vector3 delta = target - pos; delta.z = 0f; if (delta.magnitude < 0.05f) { target = PickTarget(); waitLeft = Random.Range(1f, 3f); return; } Vector3 step = delta.normalized * Speed * Time.deltaTime; if (step.magnitude > delta.magnitude) step = delta; transform.position = pos + step; if (Mathf.Abs(delta.x) > 0.01f) { var s = figure.localScale; s.x = delta.x < 0 ? -1f : 1f; // смотрит туда, куда идёт figure.localScale = s; } // лёгкое покачивание при ходьбе figure.localPosition = new Vector3(0f, Mathf.Abs(Mathf.Sin(Time.time * 10f)) * 0.05f, 0f); } // ---------------- Тап ---------------- void OnMouseDown() { downPos = Input.mousePosition; } void OnMouseUpAsButton() { if (UIBlocker.IsPointerOverUI()) return; if ((Input.mousePosition - downPos).magnitude > 25f) return; UIKit.Toast("Богатырь: " + Phrases[Random.Range(0, Phrases.Length)]); waitLeft = 2f; // остановился поговорить if (castle != null) BuildingWindow.Open(castle.gameObject); } }