using System.Collections; using System.Collections.Generic; using System.Globalization; using UnityEngine; namespace TKS { /// /// 타격감 화면 효과(PD 2026-11-10 「타격감을 위한 화면 효과(화면 흔들림 등)도 IB 를 참고해서 적용」) — 값 = 표 Resources/Skills/hit_feel.csv(코드 상수 없음 · C45). /// 키(s_event): hit = 행동 중 유닛이 상대에게 처음 피해를 준 순간(대상마다 1번 · SkillSystem.Hit) · 병종별 = hit_<병종>(있으면 hit 대신). /// 칸: f_shake_time 초 · f_shake_power 흔들림 크기(유닛 키 배수) · f_stop 멈춤(히트 스톱) 초 · f_stop_scale 멈춤 동안 시간 배속(0 = 완전 정지). /// 가상 실행(Sim.Active · 적 AI 예측) 중에는 아무것도 하지 않는다. /// public static class HitFeel { public const string TablePath = "Skills/hit_feel"; public class Row { public string evt = ""; public float shakeTime; public float shakePower; public float stop; public float stopScale; } static Dictionary rows; static Coroutine stopRoutine; static float stopRestoreScale = 1f; public static void Reload() { rows = null; } static void Ensure() { if (rows != null) return; rows = new Dictionary(); var ta = Resources.Load(TablePath); if (ta == null) return; string[] head = null; foreach (var raw in ta.text.Replace("\r", "").Split('\n')) { var line = raw.TrimStart(''); if (line.Trim().Length == 0 || line.TrimStart().StartsWith("#")) continue; var cells = SkillVfx.SplitCsv(line); if (head == null) { head = cells; continue; } var r = new Row(); for (int i = 0; i < head.Length && i < cells.Length; i++) { string k = head[i].Trim(), v = cells[i].Trim(); if (v.Length == 0) continue; switch (k) { case "s_event": r.evt = v.ToLowerInvariant(); break; case "f_shake_time": r.shakeTime = F(v); break; case "f_shake_power": r.shakePower = F(v); break; case "f_stop": r.stop = F(v); break; case "f_stop_scale": r.stopScale = F(v); break; } } if (r.evt.Length > 0) rows[r.evt] = r; } } static float F(string v) { float f; return float.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out f) ? f : 0f; } /// 행동 중 유닛 actor 가 target 에 처음 피해를 준 순간. public static void OnHit(Unit actor, Unit target) { if (Sim.Active || actor == null) return; Ensure(); Row r; if (!rows.TryGetValue("hit_" + actor.Type.ToString().ToLowerInvariant(), out r) && !rows.TryGetValue("hit", out r)) return; Play(r, actor); } static void Play(Row r, Unit actor) { float h = actor.ArtHeight > 0f ? actor.ArtHeight : actor.Radius * 2f; var cam = Camera.main != null ? Camera.main.GetComponent() : null; if (cam != null && r.shakeTime > 0f && r.shakePower > 0f) cam.Shake(r.shakeTime, r.shakePower * h); if (r.stop > 0f && Fx.Runner != null) { if (stopRoutine != null) { Fx.Runner.StopCoroutine(stopRoutine); Time.timeScale = stopRestoreScale; } stopRoutine = Fx.Runner.StartCoroutine(HitStop(r.stop, r.stopScale)); } } /// 히트 스톱: 잠깐 시간 배속을 낮췄다가 원래대로(실시간 기준 · 자동 플레이 배속 4 등도 복원). static IEnumerator HitStop(float seconds, float scale) { stopRestoreScale = Time.timeScale; Time.timeScale = Mathf.Max(0f, scale) * stopRestoreScale; yield return new WaitForSecondsRealtime(seconds); Time.timeScale = stopRestoreScale; stopRoutine = null; } } }