ThreeKingdomsSlingshot/Assets/Scripts/Runtime/Skills/HitFeel.cs

114 lines
4.5 KiB
C#
Raw Permalink Normal View History

스킬 이펙트 1차 + IB(쿠로네코) 연출 시험 — 돌진·조준 UI·타격감 (PD 2026-11-10) - 기본 공격(돌진) = IB 이펙트 그대로 시험: 앞 빛 물결 · 속도 꼬리(진영색 4색 · 멈춘 자리에서 0.12초 흐려짐) · WL 흙먼지 · 기준점 dash(아래로 갈수록 발 쪽) · 히트 이펙트 제거(PD 지정 대기) - 슬링샷 방향 결정 UI = IB GameTouchArrow 이식(IbAimArrow · IB 유닛 반지름 0.496 기준 비율) + 진행 중 캐릭터 연출(IbUnitMarks: 발밑 원 · 몸 빛 · 조준 중 속성 방울) · 값 표 Resources/Skills/ib_ui.csv - 기병: 복귀 영역(채운 원 · PreviewCavalryReturn) · 복귀 때 돌아서기(UnitView.TurnAround) - 타격감: HitFeel + CameraController.Shake · 표 hit_feel.csv(IB 흔들림 값은 디컴파일에 없어 추정값) - 액티브 스킬 1차 연출(Fable 제안 56 스킬 · 사본 Resources/Fx/TKS_Fx_Sk_*) · SkillVfx 확장(s_camp · f_fwd · f_drift · f_fade · s_over · b_flat · 트레일 페이드) · 셀화 셰이더 포트 - 2D 돌진 시험본(Dash2D · Aura2D test_aura) · 이전 시험 행은 skill_vfx.csv 에 # 주석 - 여포 크기: dz_boss_lvbu 몸 높이 819 → 718(깃털 제외 · 말 탄 유닛 머리 끝 상한 때문에 작아졌음) - 원본 = Assets/VFX/IB(쿠로네코 · GUID 유지 · Tools/wl_vfx_import.py --skip-scripts) · 사본 도구 Tools/make_ib_fx_copies.cs · make_ib_camp_textures.py · build_dash2d.cs - 테스트 PlayMode 118/118 통과 · 자동 재직렬화(png meta · UITheme · ProjectSettings) 제외 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 04:19:44 +00:00
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;
namespace TKS
{
/// <summary>
/// 타격감 화면 효과(PD 2026-11-10 「타격감을 위한 화면 효과(화면 흔들림 등)도 IB 를 참고해서 적용」) — 값 = 표 Resources/Skills/hit_feel.csv(코드 상수 없음 · C45).
/// 키(s_event): hit = 행동 중 유닛이 상대에게 처음 피해를 준 순간(대상마다 1번 · SkillSystem.Hit) · 병종별 = hit_&lt;병종&gt;(있으면 hit 대신).
/// 칸: f_shake_time 초 · f_shake_power 흔들림 크기(유닛 키 배수) · f_stop 멈춤(히트 스톱) 초 · f_stop_scale 멈춤 동안 시간 배속(0 = 완전 정지).
/// 가상 실행(Sim.Active · 적 AI 예측) 중에는 아무것도 하지 않는다.
/// </summary>
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<string, Row> rows;
static Coroutine stopRoutine;
static float stopRestoreScale = 1f;
public static void Reload()
{
rows = null;
}
static void Ensure()
{
if (rows != null) return;
rows = new Dictionary<string, Row>();
var ta = Resources.Load<TextAsset>(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;
}
/// <summary>행동 중 유닛 actor 가 target 에 처음 피해를 준 순간.</summary>
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<CameraController>() : 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));
}
}
/// <summary>히트 스톱: 잠깐 시간 배속을 낮췄다가 원래대로(실시간 기준 · 자동 플레이 배속 4 등도 복원).</summary>
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;
}
}
}