187 lines
9.1 KiB
C#
187 lines
9.1 KiB
C#
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<int> 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<int> townHallLevel)
|
|
{
|
|
if (Instance != null) Destroy(Instance.gameObject);
|
|
var go = new GameObject("Hero3D");
|
|
go.transform.SetParent(parent, false);
|
|
var h = go.AddComponent<City3DHero>();
|
|
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<BoxCollider>();
|
|
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<Collider>());
|
|
p.transform.SetParent(figure, false);
|
|
p.transform.localPosition = pos;
|
|
p.transform.localScale = scale;
|
|
p.GetComponent<Renderer>().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<Vector2Int>();
|
|
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);
|
|
}
|
|
}
|