using UnityEngine; /// /// PD #797 — 타격 시점 임팩트 연출 수치의 단일 출처(SOT · 조직 C45). /// /// 에셋 경로(고정): Assets/WL/Feel/Settings/Resources/WL/WLHitFeelSettings.asset /// → WLHitFeel 이 Resources.Load<WLHitFeelSettings>("WL/WLHitFeelSettings") 로 1회 로드한다. /// 에셋이 없거나 enabled = false 면 Actor.Get_Damage 의 훅이 false 를 반환하고 /// 기존 RealCamera.ShakeCamera(1) 경로가 그대로 살아난다 = 100% 기존 동작. 롤백 1순위 스위치. /// /// 【기본값의 근거 — Feel v5.9.1 공식 데모 실측】 /// Assets/Feel/FeelDemos/Barbarians/Prefabs/FeelBarbarianEnemy.prefab /// = PD 참고 이미지(탑다운 캐릭터가 무리를 베는 순간)와 같은 구성의 More Mountains 공식 프리셋. /// FreezeFrameDuration 0.02 · MinimumTimescaleThreshold 0.1 /// CinemachineImpulse m_ImpulseDuration 0.2 · Position(넉백) 0.3 · Blink On/Off 각 0.03 /// → 전체 화면 플래시(MMF_Flash) 는 이 프리셋에 **없다**. 참고 이미지의 '번쩍임' 은 /// 몹 Blink + 히트 파티클이며 풀스크린 플래시가 아니다. 따라서 flashEnabled 기본 false. /// MMF_FreezeFrame.cs 의 툴팁 원문도 "I usually go with 0.01s or 0.02s" 로 0.02 대를 권장한다. /// /// 【힘민지 실측 제약 — 이 수치를 함부로 올리면 안 되는 이유】 /// · 근접 스윙 1회가 군집에서 몹 5~7 마리를 같은 프레임에 때린다 → throttleSeconds 로 스윙당 1발. /// · 콤보 Knight@Attack1_S/2_S/3_S 간격 0.42~0.47 s → 히트스톱이 0.08 s 를 넘으면 리듬이 깨진다. /// · ProjectileBase.cs:305/308 이 transform.localScale.x > 2f 로 **보스를 판정**한다 /// → 스케일 펀치가 그 경계를 넘나들면 보스 판정이 흔들린다(WLHitFeel 이 자동으로 건너뛴다). /// · RealCamera 는 자기 출력을 다시 입력으로 읽는 저역통과 필터(smooth 4 · τ≈0.25 s)다 /// → 셰이크는 WLHitFeelCameraShake 가 필터 밖에서 가감산한다(해당 파일 주석 참조). /// [CreateAssetMenu(fileName = "WLHitFeelSettings", menuName = "WL/Hit Feel Settings")] public class WLHitFeelSettings : ScriptableObject { // ─────────────────────────────────────────── 전체 스위치 [Header("전체 스위치")] [Tooltip("끄면 Actor 의 훅이 false 를 반환해 기존 RealCamera.ShakeCamera(1) 경로가 그대로 살아난다. 롤백 1순위.")] public bool enabled = true; [Tooltip("스윙 1회당 1발로 묶는 간격(초 · unscaled).\n" + "근접 스윙 1회가 군집에서 5~7 마리를 같은 프레임에 때리므로 반드시 필요하다.\n" + "콤보 간격 0.42~0.47 s 보다 충분히 작아야 3 히트가 각각 발동한다. 0.08 = 발주 규정값.")] public float throttleSeconds = 0.08f; // ─────────────────────────────────────────── ① 히트스톱 public enum eHitStopMode { WLSafe = 0, // WL 자체 구현 — 직전 timeScale 을 저장했다가 '아무도 안 건드렸을 때만' 복원 FeelFreezeFrame = 1, // Feel 의 MMFreezeFrameEvent + MMTimeManager 경로 } [Header("① 히트스톱 (타격 순간의 정지)")] public bool hitStopEnabled = true; [Tooltip("WLSafe(기본) = 직전 timeScale 을 기억했다가 '그 사이 아무도 timeScale 을 안 바꿨을 때만' 복원한다.\n" + " → 일시정지(0)·서버 팝업(0)·보스 연출과 절대 싸우지 않는다.\n" + "FeelFreezeFrame = Feel 의 MMFreezeFrameEvent 를 쏘고 MMTimeManager 가 처리한다.\n" + " → MMTimeManager 가 timeScale 을 전역 소유하며 해제 시 항상 NormalTimeScale(1) 로 되돌린다.")] public eHitStopMode hitStopMode = eHitStopMode.WLSafe; [Tooltip("히트스톱 길이(초 · unscaled). Feel 공식 데모 = 0.02.\n" + "0.08 을 넘으면 콤보(0.42~0.47 s) 리듬이 깨진다 — hitStopMax 로 강제 제한된다.")] public float hitStopDuration = 0.03f; [Tooltip("킬(이 타격으로 몹이 죽음) 시의 히트스톱 길이(초). 킬 강조를 끄려면 hitStopDuration 과 같게.")] public float hitStopKillDuration = 0.05f; [Tooltip("히트스톱 동안의 timeScale. 0 = 완전 정지(Feel 과 동일). 0.05 = 힘민지 기존 TimeMgr 와 동일한 느낌.")] [Range(0f, 0.5f)] public float hitStopTimeScale = 0f; [Tooltip("안전 상한(초). 이 값을 넘는 히트스톱은 강제로 잘린다. 콤보 보호용 — 함부로 올리지 말 것.")] public float hitStopMax = 0.08f; [Tooltip("현재 timeScale 이 이 값보다 낮으면 히트스톱을 발동하지 않는다.\n" + "이미 멈춰 있거나(일시정지 0) 느려진 상태를 덮어쓰지 않기 위한 것. Feel 기본값과 동일한 0.1.")] public float hitStopMinTimescale = 0.1f; // ─────────────────────────────────────────── ② 카메라 셰이크 [Header("② 카메라 셰이크")] public bool cameraShakeEnabled = true; [Tooltip("셰이크 진폭(m). 카메라-주시점 거리 5.677 m · FOV 60 실측 기준\n" + " 화면 세로 비율 = 진폭 / (2 × 5.677 × tan30°) = 진폭 × 15.3 %/m\n" + " 0.10 m ≒ 화면 세로의 1.5 % (세로 화면에서는 가로 폭 대비 더 크게 보인다)\n" + "발주 규정 범위 0.08~0.12.")] public float shakeAmplitude = 0.10f; [Tooltip("셰이크 길이(초 · unscaled). 발주 규정 범위 0.10~0.12.\n" + "기존 RealCamera.ShakeCamera(1) 은 0.25 s 로 콤보 간격의 절반을 차지해 늘어져 보였다.")] public float shakeDuration = 0.12f; [Tooltip("셰이크 주파수(Hz 상당). 높을수록 잘게 떤다. 32 = 짧고 날카로운 타격감.")] public float shakeFrequency = 32f; [Tooltip("카메라 **로컬** 축별 가중치 (x = 화면 좌우, y = 화면 상하, z = 시선 앞뒤).\n" + "세로 서비스라 상하 흔들림은 화면 세로가 길어 더 크게 체감된다 → y 를 낮춘다.\n" + "기본 (1, 0.35, 0.6) = 좌우 위주 + 상하 소량 + 앞뒤(줌) 소량.")] public Vector3 shakeAxisWeight = new Vector3(1f, 0.35f, 0.6f); [Tooltip("킬 시 진폭 배수. 1 이면 킬 강조 없음.")] public float shakeKillMultiplier = 1.35f; [Tooltip("기존 Actor.cs 의 RealCamera.ShakeCamera(1)(진폭 0.1 · 0.25 s) 를 대체한다.\n" + "끄면 둘이 **동시에** 걸려 과하게 흔들린다 — 켜 두는 것이 '살짝' 요구에 맞다.")] public bool suppressLegacyShake = true; // ─────────────────────────────────────────── ③ 피격 몹 스케일 펀치 [Header("③ 피격 몹 스케일 펀치")] public bool scalePunchEnabled = true; [Tooltip("최대 배수. 1.12 = 발주 규정값. 몹의 테이블 기본 스케일(f_DefaultScale)에 곱한다.")] public float scalePunch = 1.12f; [Tooltip("펀치 길이(초 · unscaled). 0 → 최대 → 0 으로 한 번 튀고 돌아온다.")] public float scalePunchDuration = 0.10f; [Tooltip("보스에게는 펀치를 걸지 않는다. 보스는 덩치가 커서 같은 배수도 훨씬 과하게 보이고,\n" + "ProjectileBase 의 localScale.x > 2f 보스 판정과 경계가 겹친다.")] public bool skipPunchOnBoss = true; [Tooltip("이 타격으로 몹이 죽으면 펀치를 걸지 않는다.\n" + "죽은 몹은 곧 풀로 반환되므로 펀치 복원이 재사용된 객체에 쓰일 위험이 있다.")] public bool skipPunchOnKill = true; // ─────────────────────────────────────────── ④ 화면 플래시 (기본 OFF) [Header("④ 화면 플래시 — 기본 OFF")] [Tooltip("Feel 공식 Barbarians 프리셋에는 풀스크린 플래시가 없다. 기본 false 를 권장한다.\n" + "켜면 런타임에 오버레이 Canvas + Image 1 장(드로우 +1)을 만들고, 꺼져 있는 동안은 아무것도 만들지 않는다.")] public bool flashEnabled = false; [Tooltip("플래시 색. 알파 ≤ 0.12 (발주 규정 상한).")] public Color flashColor = new Color(1f, 1f, 1f, 0.10f); [Tooltip("플래시 길이(초 · unscaled). 발주 규정 0.06.")] public float flashDuration = 0.06f; // ─────────────────────────────────────────── ⑤ 진동 (기본 OFF) [Header("⑤ 진동 — 기본 OFF (모바일 배터리)")] [Tooltip("NiceVibrations 햅틱. 모바일 배터리 때문에 기본 false.")] public bool hapticEnabled = false; // ─────────────────────────────────────────── 진단 [Header("진단 (검증용 · 배포 시 false)")] [Tooltip("타격/발동 시각과 스로틀 결과를 Debug.Log 로 남긴다.")] public bool logHits = false; }