204 lines
8.0 KiB
C#
204 lines
8.0 KiB
C#
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<HeroWalker>();
|
|
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<BoxCollider2D>();
|
|
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<SpriteRenderer>();
|
|
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);
|
|
}
|
|
}
|