using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TKS
{
///
/// 기믹 표시(PD #1097 §6): 스프라이트(Resources/Gimmicks/Art/<art>)가 있으면 모양에 맞춰 늘리고, 없으면 색 도형 + 이름 글자.
/// 장애물 = 내구 핍 · 피격 흔들림 · 파괴 파편 → 잔해(art_broken) · 함정 = 발동 전 숨김 · 불 = 꺼지면 회색 · 군기 = 점령 진영 색.
/// 정렬 = 배경(−20~−16) 위 · 유닛(−6~) 아래.
///
public class GimmickView
{
public const int ZoneOrder = -15;
public const int RiverOrder = -14;
public const int BridgeOrder = -13;
public const int BlockOrder = -12;
public const int LabelOrder = -11;
public const string ArtFolder = "Gimmicks/Art/";
static Material polyMaterial;
readonly GimmickInstance g;
readonly Transform root;
Transform body;
Renderer fill;
TextMesh label;
readonly List pips = new List();
Color baseColor;
Coroutine shake;
public GimmickView(GimmickInstance inst)
{
g = inst;
root = inst.Go.transform;
Build();
}
public Transform Body
{
get { return body; }
}
public bool Visible
{
get { return body != null && body.gameObject.activeSelf; }
}
public string LabelText
{
get { return label != null ? label.text : ""; }
}
public int PipCount
{
get { return pips.Count; }
}
public static Color ColorOf(GimmickDef d)
{
switch (d.zone)
{
case GimmickZone.Poison: return new Color(0.55f, 0.25f, 0.75f, 0.45f);
case GimmickZone.Slow: return new Color(0.42f, 0.32f, 0.16f, 0.5f);
case GimmickZone.Burn: return new Color(1f, 0.45f, 0.1f, 0.5f);
case GimmickZone.Slide: return new Color(0.6f, 0.88f, 1f, 0.5f);
case GimmickZone.High: return new Color(0.78f, 0.66f, 0.4f, 0.38f);
case GimmickZone.Fog: return new Color(0.92f, 0.92f, 0.96f, 0.42f);
case GimmickZone.Capture: return new Color(0.75f, 0.75f, 0.75f, 0.3f);
case GimmickZone.Trap: return new Color(0.3f, 0.18f, 0.08f, 0.85f);
case GimmickZone.River: return new Color(0.2f, 0.45f, 0.85f, 0.9f);
}
if (d.id == GimmickIds.Bridge) return new Color(0.62f, 0.46f, 0.26f, 1f);
if (d.id == GimmickIds.Boulder) return new Color(0.52f, 0.52f, 0.55f, 1f);
if (d.projectile == GimmickProjectile.Destroy) return new Color(0.78f, 0.22f, 0.15f, 1f);
if (d.strategistClear) return new Color(0.2f, 0.42f, 0.18f, 1f); // 가시덤불
if (d.projectile == GimmickProjectile.Pass) return new Color(0.58f, 0.4f, 0.2f, 1f); // 목책
if (d.durable) return new Color(0.42f, 0.27f, 0.14f, 1f); // 성문
return new Color(0.46f, 0.46f, 0.43f, 1f); // 성벽
}
static int OrderOf(GimmickInstance i)
{
if (i.Zone == GimmickZone.River) return RiverOrder;
if (i.IsBridge) return BridgeOrder;
return i.IsBlock ? BlockOrder : ZoneOrder;
}
void Build()
{
var bgo = new GameObject("Body");
bgo.transform.SetParent(root, false);
body = bgo.transform;
baseColor = ColorOf(g.Def);
int order = OrderOf(g);
var s = g.Shape;
var art = string.IsNullOrEmpty(g.Def.art) ? null : Resources.Load(ArtFolder + g.Def.art);
if (s.Type == GimmickShapeType.Poly)
{
fill = MakePoly(body, s, baseColor, order);
}
else
{
Vector2 size = s.Type == GimmickShapeType.Circle ? new Vector2(s.R * 2f, s.R * 2f) : s.Size;
var sr = ProceduralSprites.NewSprite(body, "Fill", art != null ? art : (s.Type == GimmickShapeType.Circle ? ProceduralSprites.Circle : ProceduralSprites.Square), art != null ? Color.white : baseColor, 1f, order);
Vector2 native = sr.sprite.bounds.size;
sr.transform.localScale = new Vector3(size.x / Mathf.Max(0.001f, native.x), size.y / Mathf.Max(0.001f, native.y), 1f);
sr.transform.localRotation = Quaternion.Euler(0f, 0f, s.Type == GimmickShapeType.Rect ? s.Rot : 0f);
fill = sr;
if (art != null) baseColor = Color.white;
if (g.Zone == GimmickZone.Fog) sr.maskInteraction = SpriteMaskInteraction.VisibleOutsideMask; // 책사 광역이 걷은 자리(SpriteMask)는 안 그림
}
float h = Mathf.Clamp(Mathf.Min(s.Type == GimmickShapeType.Rect ? Mathf.Min(s.Size.x, s.Size.y) : s.BoundingRadius) * 0.55f, 0.22f, 0.5f);
label = TextUtil.CreateWorldText(body, g.Def.nameKo, h, new Color(1f, 1f, 1f, g.IsBlock ? 0.95f : 0.75f), LabelOrder);
label.gameObject.name = "Label";
if (g.Zone == GimmickZone.River && s.Type == GimmickShapeType.Rect) label.transform.localPosition = (Vector3)(s.LocalAxisX * s.Size.x * 0.3f); // 다리 글자와 겹치지 않게
if (g.Def.durable && g.MaxHp > 0)
{
float top = s.Type == GimmickShapeType.Rect ? Mathf.Abs(s.Size.y) * 0.5f + 0.18f : s.BoundingRadius + 0.18f;
const float w = 0.16f, gap = 0.05f;
float total = g.MaxHp * w + (g.MaxHp - 1) * gap;
for (int i = 0; i < g.MaxHp; i++)
{
var p = ProceduralSprites.NewSprite(body, "Pip", ProceduralSprites.Square, Color.white, 1f, LabelOrder);
p.transform.localPosition = new Vector3(-total * 0.5f + w * 0.5f + i * (w + gap), top, 0f);
p.transform.localScale = new Vector3(w, 0.1f, 1f);
var ps = UISkin.WorldSprite("pip", 460f); // 팩 둥근 칸 핍(PD #1100 · 외곽선)
if (ps != null)
{
p.sprite = ps;
p.drawMode = SpriteDrawMode.Sliced;
p.transform.localScale = Vector3.one;
p.size = new Vector2(w, 0.12f);
}
pips.Add(p);
}
}
if (g.Zone == GimmickZone.Trap) body.gameObject.SetActive(false); // 함정 = 숨김(아군 조준선 미표시 · 적 AI 는 인지)
Refresh();
}
static Renderer MakePoly(Transform parent, GimmickShape s, Color color, int order)
{
var go = new GameObject("Fill");
go.transform.SetParent(parent, false);
var mf = go.AddComponent();
var mr = go.AddComponent();
if (polyMaterial == null) polyMaterial = new Material(Shader.Find("Sprites/Default"));
mr.sharedMaterial = polyMaterial;
mr.sortingOrder = order;
var mesh = new Mesh();
var verts = new Vector3[s.Points.Length];
var cols = new Color[s.Points.Length];
for (int i = 0; i < verts.Length; i++)
{
verts[i] = s.Points[i] - s.Center;
cols[i] = color;
}
mesh.vertices = verts;
mesh.colors = cols;
mesh.triangles = Triangulate(s.Points);
mf.sharedMesh = mesh;
return mr;
}
/// 귀 자르기 삼각 분할(단순 다각형).
public static int[] Triangulate(Vector2[] pts)
{
var tris = new List();
var idx = new List();
float area = 0f;
for (int i = 0, j = pts.Length - 1; i < pts.Length; j = i++) area += pts[j].x * pts[i].y - pts[i].x * pts[j].y;
for (int i = 0; i < pts.Length; i++) idx.Add(area > 0f ? i : pts.Length - 1 - i);
int guard = 0;
while (idx.Count > 3 && guard++ < 1000)
{
bool cut = false;
for (int i = 0; i < idx.Count; i++)
{
int a = idx[(i + idx.Count - 1) % idx.Count], b = idx[i], c = idx[(i + 1) % idx.Count];
Vector2 pa = pts[a], pb = pts[b], pc = pts[c];
if ((pb.x - pa.x) * (pc.y - pa.y) - (pb.y - pa.y) * (pc.x - pa.x) <= 0f) continue;
bool inside = false;
foreach (int k in idx)
{
if (k == a || k == b || k == c) continue;
if (InTri(pts[k], pa, pb, pc)) { inside = true; break; }
}
if (inside) continue;
tris.Add(a); tris.Add(b); tris.Add(c);
idx.RemoveAt(i);
cut = true;
break;
}
if (!cut) break;
}
if (idx.Count == 3) { tris.Add(idx[0]); tris.Add(idx[1]); tris.Add(idx[2]); }
return tris.ToArray();
}
static bool InTri(Vector2 p, Vector2 a, Vector2 b, Vector2 c)
{
float d1 = (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y);
float d2 = (p.x - c.x) * (b.y - c.y) - (b.x - c.x) * (p.y - c.y);
float d3 = (p.x - a.x) * (c.y - a.y) - (c.x - a.x) * (p.y - a.y);
bool neg = d1 < 0 || d2 < 0 || d3 < 0, pos = d1 > 0 || d2 > 0 || d3 > 0;
return !(neg && pos);
}
/// 상태(내구 · 잔해 · 군기 · 불 꺼짐 · 함정 노출)를 그림에 반영.
public void Refresh()
{
if (body == null) return;
Color c = baseColor;
string text = g.Def.nameKo;
if (g.Broken)
{
c = Color.Lerp(baseColor, new Color(0.2f, 0.18f, 0.15f, 0.55f), 0.6f);
text = g.Def.nameKo + " 잔해";
var broken = string.IsNullOrEmpty(g.Def.artBroken) ? null : Resources.Load(ArtFolder + g.Def.artBroken);
var sr = fill as SpriteRenderer;
if (broken != null && sr != null)
{
sr.sprite = broken;
c = Color.white;
}
}
if (g.Out)
{
c = new Color(0.35f, 0.33f, 0.3f, 0.35f);
text = "꺼진 잔해";
}
if (g.Zone == GimmickZone.Capture)
{
c = g.Owner == (int)Faction.Player ? new Color(0.25f, 0.55f, 0.95f, 0.35f) : g.Owner == (int)Faction.Enemy ? new Color(0.9f, 0.3f, 0.25f, 0.35f) : baseColor;
text = g.Owner < 0 ? g.Def.nameKo : g.Def.nameKo + (g.Owner == (int)Faction.Player ? "(아군)" : "(적군)");
}
if (g.Zone == GimmickZone.Burn && g.BurnLeft > 0) text = g.Def.nameKo + " " + g.BurnLeft;
SetColor(c);
if (label != null) label.text = text;
for (int i = 0; i < pips.Count; i++)
{
pips[i].color = i < g.Hp ? UISkin.Col("pip_on", new Color(1f, 0.85f, 0.3f)) : UISkin.Col("pip_off", new Color(0.15f, 0.15f, 0.15f, 0.8f));
pips[i].gameObject.SetActive(!g.Broken);
}
}
void SetColor(Color c)
{
var sr = fill as SpriteRenderer;
if (sr != null) sr.color = c;
var mr = fill as MeshRenderer;
if (mr != null)
{
var mf = mr.GetComponent();
if (mf != null && mf.sharedMesh != null)
{
var cols = new Color[mf.sharedMesh.vertexCount];
for (int i = 0; i < cols.Length; i++) cols[i] = c;
mf.sharedMesh.colors = cols;
}
}
}
/// 피격 흔들림.
public void Shake()
{
if (body == null || Fx.Runner == null) return;
if (shake != null) Fx.Runner.StopCoroutine(shake);
shake = Fx.Run(ShakeRoutine());
}
IEnumerator ShakeRoutine()
{
float t = 0f;
const float dur = 0.25f;
while (t < dur && body != null)
{
t += Time.deltaTime;
float k = 1f - t / dur;
body.localPosition = new Vector3(Mathf.Sin(t * 70f) * 0.09f * k, Mathf.Cos(t * 55f) * 0.05f * k, 0f);
yield return null;
}
if (body != null) body.localPosition = Vector3.zero;
shake = null;
}
/// 파괴 파편(작은 조각이 흩어지며 사라짐) → 잔해 표시.
public void Shatter()
{
Fx.Run(ShatterRoutine(g.Shape.Center, baseColor));
Refresh();
}
static IEnumerator ShatterRoutine(Vector2 c, Color color)
{
var bits = new List();
var vel = new List();
for (int i = 0; i < 10; i++)
{
var sr = ProceduralSprites.NewSprite(null, "Debris", ProceduralSprites.Square, new Color(color.r, color.g, color.b, 1f), 0.16f, 7);
sr.transform.position = new Vector3(c.x, c.y, 0f);
float a = i * Mathf.PI * 2f / 10f + Random.value * 0.4f;
vel.Add(new Vector2(Mathf.Cos(a), Mathf.Sin(a)) * (1.5f + Random.value * 1.5f));
bits.Add(sr);
}
float t = 0f;
const float dur = 0.5f;
while (t < dur)
{
t += Time.deltaTime;
for (int i = 0; i < bits.Count; i++)
{
bits[i].transform.position += (Vector3)(vel[i] * Time.deltaTime);
var col = bits[i].color;
col.a = 1f - t / dur;
bits[i].color = col;
}
yield return null;
}
foreach (var b in bits) Object.Destroy(b.gameObject);
}
/// 함정 발동(드러났다 사라짐) · 덤불 제거(흐려지며 사라짐).
public void Vanish(bool reveal)
{
if (body == null) return;
if (reveal) body.gameObject.SetActive(true);
Refresh();
Fx.Run(VanishRoutine(reveal ? 0.7f : 0.35f));
}
IEnumerator VanishRoutine(float dur)
{
float t = 0f;
while (t < dur && body != null)
{
t += Time.deltaTime;
var c = baseColor;
c.a = baseColor.a * (1f - t / dur);
SetColor(c);
yield return null;
}
if (body != null) body.gameObject.SetActive(false);
}
}
}