ThreeKingdomsSlingshot/Assets/Scripts/Runtime/Gimmicks/GimmickView.cs

352 lines
15 KiB
C#
Raw Permalink Normal View History

지형 기믹 레이어(PD #1097 세션 D) + 병종 규칙 개정 2건 · 예측 시뮬레이션 적 AI(PD #1098) - 데이터: Assets/Data/Gimmicks/{gimmicks,stage_gimmicks,tutorial_tips}.csv + Assets/Data/AI/ai_weights.csv(기획 tables/ 사본) → 메뉴 TKS/Import Gimmicks(검증 · 오류 시 자산 불변) → Resources/Gimmicks/GimmickDb(16)·StageGimmickTable(16 + 팁 3) · Resources/AI/AiWeights(32) - 레이어: GimmickLayer(전투 시작 = 스테이지 배치 · 배경 위 유닛 아래 · 원/사각/다각 · 색 도형 + 글자 · 내구 핍 · 강 콜라이더 = 다리 구간 제외 · 겹친 유닛 보정) · 장애물 = 병종별 내구 피해·튕김·창 목책 밀기·바위 굴림·기병 정지 · 투사체 pass/block/destroy · 폭약 광역(아군 포함)+밀기+불 · 책사 광역 덤불 제거 · 강 낙하 - 지대: 경로 스윕(독 1→2→3 % · 늪 ×2 · 불 10 % · 빙판 미끄러짐 · 고지 · 안개 · 군기 · 함정) · 양 진영 턴 시작 훅 - 규칙 개정: 보병 튕김 중 다른 적과 연쇄 1회 · 창병 반작용(spearRecoilRatio · 밀며 전진 폐기) · 기존 테스트 기대값 갱신 - 적 AI: Runtime/AI(Sim 가상 실행 · TacticalAI 후보·점수·난이도·턴 운영 · 1부 보스 액티브) · 조준선 = 가상 실행 예상 궤적·기믹 반응·지대 띠 · TKS/Gimmick Painter · 캡처 GimmickImporter.Capture - 검증: 컴파일 0 · 플레이모드 113/113(신규 26) · console error 0 · 적 10기 턴 결정 62 ms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:20:07 +00:00
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace TKS
{
/// <summary>
/// 기믹 표시(PD #1097 §6): 스프라이트(Resources/Gimmicks/Art/&lt;art&gt;)가 있으면 모양에 맞춰 늘리고, 없으면 색 도형 + 이름 글자.
/// 장애물 = 내구 핍 · 피격 흔들림 · 파괴 파편 → 잔해(art_broken) · 함정 = 발동 전 숨김 · 불 = 꺼지면 회색 · 군기 = 점령 진영 색.
/// 정렬 = 배경(−20~−16) 위 · 유닛(−6~) 아래.
/// </summary>
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<SpriteRenderer> pips = new List<SpriteRenderer>();
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<Sprite>(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)는 안 그림
지형 기믹 레이어(PD #1097 세션 D) + 병종 규칙 개정 2건 · 예측 시뮬레이션 적 AI(PD #1098) - 데이터: Assets/Data/Gimmicks/{gimmicks,stage_gimmicks,tutorial_tips}.csv + Assets/Data/AI/ai_weights.csv(기획 tables/ 사본) → 메뉴 TKS/Import Gimmicks(검증 · 오류 시 자산 불변) → Resources/Gimmicks/GimmickDb(16)·StageGimmickTable(16 + 팁 3) · Resources/AI/AiWeights(32) - 레이어: GimmickLayer(전투 시작 = 스테이지 배치 · 배경 위 유닛 아래 · 원/사각/다각 · 색 도형 + 글자 · 내구 핍 · 강 콜라이더 = 다리 구간 제외 · 겹친 유닛 보정) · 장애물 = 병종별 내구 피해·튕김·창 목책 밀기·바위 굴림·기병 정지 · 투사체 pass/block/destroy · 폭약 광역(아군 포함)+밀기+불 · 책사 광역 덤불 제거 · 강 낙하 - 지대: 경로 스윕(독 1→2→3 % · 늪 ×2 · 불 10 % · 빙판 미끄러짐 · 고지 · 안개 · 군기 · 함정) · 양 진영 턴 시작 훅 - 규칙 개정: 보병 튕김 중 다른 적과 연쇄 1회 · 창병 반작용(spearRecoilRatio · 밀며 전진 폐기) · 기존 테스트 기대값 갱신 - 적 AI: Runtime/AI(Sim 가상 실행 · TacticalAI 후보·점수·난이도·턴 운영 · 1부 보스 액티브) · 조준선 = 가상 실행 예상 궤적·기믹 반응·지대 띠 · TKS/Gimmick Painter · 캡처 GimmickImporter.Capture - 검증: 컴파일 0 · 플레이모드 113/113(신규 26) · console error 0 · 적 10기 턴 결정 62 ms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:20:07 +00:00
}
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);
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
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);
}
지형 기믹 레이어(PD #1097 세션 D) + 병종 규칙 개정 2건 · 예측 시뮬레이션 적 AI(PD #1098) - 데이터: Assets/Data/Gimmicks/{gimmicks,stage_gimmicks,tutorial_tips}.csv + Assets/Data/AI/ai_weights.csv(기획 tables/ 사본) → 메뉴 TKS/Import Gimmicks(검증 · 오류 시 자산 불변) → Resources/Gimmicks/GimmickDb(16)·StageGimmickTable(16 + 팁 3) · Resources/AI/AiWeights(32) - 레이어: GimmickLayer(전투 시작 = 스테이지 배치 · 배경 위 유닛 아래 · 원/사각/다각 · 색 도형 + 글자 · 내구 핍 · 강 콜라이더 = 다리 구간 제외 · 겹친 유닛 보정) · 장애물 = 병종별 내구 피해·튕김·창 목책 밀기·바위 굴림·기병 정지 · 투사체 pass/block/destroy · 폭약 광역(아군 포함)+밀기+불 · 책사 광역 덤불 제거 · 강 낙하 - 지대: 경로 스윕(독 1→2→3 % · 늪 ×2 · 불 10 % · 빙판 미끄러짐 · 고지 · 안개 · 군기 · 함정) · 양 진영 턴 시작 훅 - 규칙 개정: 보병 튕김 중 다른 적과 연쇄 1회 · 창병 반작용(spearRecoilRatio · 밀며 전진 폐기) · 기존 테스트 기대값 갱신 - 적 AI: Runtime/AI(Sim 가상 실행 · TacticalAI 후보·점수·난이도·턴 운영 · 1부 보스 액티브) · 조준선 = 가상 실행 예상 궤적·기믹 반응·지대 띠 · TKS/Gimmick Painter · 캡처 GimmickImporter.Capture - 검증: 컴파일 0 · 플레이모드 113/113(신규 26) · console error 0 · 적 10기 턴 결정 62 ms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:20:07 +00:00
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<MeshFilter>();
var mr = go.AddComponent<MeshRenderer>();
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;
}
/// <summary>귀 자르기 삼각 분할(단순 다각형).</summary>
public static int[] Triangulate(Vector2[] pts)
{
var tris = new List<int>();
var idx = new List<int>();
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);
}
/// <summary>상태(내구 · 잔해 · 군기 · 불 꺼짐 · 함정 노출)를 그림에 반영.</summary>
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<Sprite>(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++)
{
UI 리뉴얼 A-2~A-4 화면 적용(PD #1100) — 팩 부품 + UI 테마(id 치환) · 화면 10종 · 인박스 ui kind - 테마: SOT Assets/Data/UI/ui_theme.csv(색 57 · 스킨 32 · 아이콘 63 · 제목 폰트) → 메뉴 TKS/Import UI Theme → Resources/UI/UITheme.asset (Runtime/UI/UITheme · UISkin · Editor/UIThemeImporter · ProjectSetup 에서도 임포트) · 화면 코드는 id 로만 묻는다(스프라이트 경로 0) - 신규 제작(B) 치환: Assets/UI/Samguk/**/<id>.png → 임포트 시 같은 id 스킨·아이콘 자동 치환(팩 보존 · 새 id = 아이콘 추가) · Tools/apply_delivery.py ui kind(md5 → Samguk/<group> · gim_* → Resources/Gimmicks/Art · ui_delivered.csv) - 공통: UiKit(카드 = BorderFrame_Round20 틴트 · 버튼 = Button01_s + 광택 · 원 · 진행 바 = Slider_Basic01 · 초상 틀 · 등급 칸 ItemFrame02 · 별 줄 · 병종 배지) · 재화 바(ResourceBar + cur_* 아이콘) · 하단 탭(선택 판 + PictoIcon) · 제목 띠 · 팝업(Popup Topber + 닫기 X) · 토스트 - 화면: 로비(키아트 04 배경 위쪽 ×1.22 · 출정·편성·연무장 아이콘) · 영웅(등급 칸 + 장비 아이콘 · 체크/알림 점 · 별 아이콘) · 편성 · 거래(배너 · 상자 아이콘) · 보물(상자 아이콘 · 포인트 배지) · 내정(보상 줄 · 도면·골드 아이콘) · 연무장 · 결과(리본 · 별) · 월드맵 스테이지 팝업만 팩(거점 kit 유지) - 전투: HUD 칩 · 턴 띠 · 원형 스킬 버튼 + 쿨 라디얼 · 유닛 체력 바 · 기믹 내구 핍 = 팩 슬라이더(월드 9-slice 사본 · 가운데 2 px) · 조준선 색 유지 - 3:4(1536×2048): 본문 비율 축소(UiFitHeight · 1080×1920 = 그대로) - 검증: 플레이모드 116/116(기존 115 + UIThemeTests 1) · 컴파일 0 · error 0 · 캡처 = DataImporter.CaptureUI(UICaptureDriver · 15장) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:20:57 +00:00
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));
지형 기믹 레이어(PD #1097 세션 D) + 병종 규칙 개정 2건 · 예측 시뮬레이션 적 AI(PD #1098) - 데이터: Assets/Data/Gimmicks/{gimmicks,stage_gimmicks,tutorial_tips}.csv + Assets/Data/AI/ai_weights.csv(기획 tables/ 사본) → 메뉴 TKS/Import Gimmicks(검증 · 오류 시 자산 불변) → Resources/Gimmicks/GimmickDb(16)·StageGimmickTable(16 + 팁 3) · Resources/AI/AiWeights(32) - 레이어: GimmickLayer(전투 시작 = 스테이지 배치 · 배경 위 유닛 아래 · 원/사각/다각 · 색 도형 + 글자 · 내구 핍 · 강 콜라이더 = 다리 구간 제외 · 겹친 유닛 보정) · 장애물 = 병종별 내구 피해·튕김·창 목책 밀기·바위 굴림·기병 정지 · 투사체 pass/block/destroy · 폭약 광역(아군 포함)+밀기+불 · 책사 광역 덤불 제거 · 강 낙하 - 지대: 경로 스윕(독 1→2→3 % · 늪 ×2 · 불 10 % · 빙판 미끄러짐 · 고지 · 안개 · 군기 · 함정) · 양 진영 턴 시작 훅 - 규칙 개정: 보병 튕김 중 다른 적과 연쇄 1회 · 창병 반작용(spearRecoilRatio · 밀며 전진 폐기) · 기존 테스트 기대값 갱신 - 적 AI: Runtime/AI(Sim 가상 실행 · TacticalAI 후보·점수·난이도·턴 운영 · 1부 보스 액티브) · 조준선 = 가상 실행 예상 궤적·기믹 반응·지대 띠 · TKS/Gimmick Painter · 캡처 GimmickImporter.Capture - 검증: 컴파일 0 · 플레이모드 113/113(신규 26) · console error 0 · 적 10기 턴 결정 62 ms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:20:07 +00:00
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<MeshFilter>();
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;
}
}
}
/// <summary>피격 흔들림.</summary>
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;
}
/// <summary>파괴 파편(작은 조각이 흩어지며 사라짐) → 잔해 표시.</summary>
public void Shatter()
{
Fx.Run(ShatterRoutine(g.Shape.Center, baseColor));
Refresh();
}
static IEnumerator ShatterRoutine(Vector2 c, Color color)
{
var bits = new List<SpriteRenderer>();
var vel = new List<Vector2>();
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);
}
/// <summary>함정 발동(드러났다 사라짐) · 덤불 제거(흐려지며 사라짐).</summary>
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);
}
}
}