200 lines
14 KiB
C#
200 lines
14 KiB
C#
using UnityEngine;
|
||
|
||
/// <summary>
|
||
/// 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 가 필터 밖에서 가감산한다(해당 파일 주석 참조).
|
||
/// </summary>
|
||
[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("⑥ 피격 몹 경직 — 맞은 몹이 아주 짧게 멈춘다")]
|
||
[Tooltip("PD #816zb — 맞은 몹이 넉백으로 밀린 직후 바로 걸어 나와 타격감이 죽는 문제.\n" +
|
||
"직접 타격(HitConfirmed) 마다 NavMeshAgent 속도를 0 으로 잠그고 애니메이터를 멈춘다(WL.Combat.Reaction.MobStagger).\n" +
|
||
"끄면 그 구독자가 아무 것도 하지 않는다 = 기존 동작 100 %. 롤백 스위치.")]
|
||
public bool mobStaggerEnabled = true;
|
||
|
||
[Tooltip("일반 몹 경직 길이(초 · unscaled). 콤보 간격 0.42~0.47 s 의 절반 이하로 둔다 — 0.18 = 발주 규정값.\n" +
|
||
"넉백 중에 맞으면 넉백이 끝난 시점부터 이 길이를 1회 다시 준다(밀리다 말고 곧장 걷지 않게).")]
|
||
public float mobStaggerSeconds = 0.25f;
|
||
|
||
[Tooltip("엘리트 경직 길이(초). 일반보다 짧게 — 압박감을 유지한다.")]
|
||
public float mobStaggerSecondsElite = 0.18f;
|
||
|
||
[Tooltip("보스 경직 길이(초). 가장 짧게 — 보스 패턴이 끊기면 안 된다.")]
|
||
public float mobStaggerSecondsBoss = 0.08f;
|
||
|
||
// ── 2026-09-17 PD 「몬스터가 피격 후 경직되지 않아 타격감이 부족 → 밀린 상태에서 경직 시간을 주어 히트모션 상태로 0.2~0.3초 멈추었다가 공격을 시도」
|
||
// 종전 = 이동 속도 0 + 애니메이터 정지(0.18 s)뿐이라 ① 공격 중인 몹은 공격 모션 그대로 얼었다 풀려 그대로 때렸고 ② 새 공격도 경직 중에 시작됐다.
|
||
[Tooltip("true = 공격 중인 **일반** 몹도 피격 모션으로 끊는다(공격 취소 → 경직 뒤 다시 시도). 원본 Play_Hit 는 공격 중이면 피격 모션을 안 튼다.")]
|
||
public bool mobStaggerInterruptAttack = true;
|
||
[Tooltip("true = 엘리트도 공격을 끊는다. 기본 false(압박감 유지 · 엘리트는 모션만 잠깐 멈춘다). 보스는 항상 끊지 않는다.")]
|
||
public bool mobStaggerInterruptElite = false;
|
||
[Tooltip("true = 경직 중에는 새 공격을 시작하지 못하게 공격 쿨타임을 경직이 끝날 때까지 붙잡아 둔다(원본 필드 읽기/쓰기만 · 원본 코드 0줄).")]
|
||
public bool mobStaggerBlockAttack = true;
|
||
[Tooltip("피격 모션을 이만큼(초) 재생한 뒤 그 자세로 멈춘다(움찔한 자세로 굳는다). 음수 = 멈추지 않고 모션을 끝까지 재생.")]
|
||
public float mobStaggerFreezeDelay = 0.1f;
|
||
[Tooltip("연타 중 피격 모션을 다시 트는 최소 간격(초). 너무 짧으면 첫 프레임만 반복돼 떨려 보인다.")]
|
||
public float mobStaggerReplayInterval = 0.2f;
|
||
|
||
// ── #903 (2026-10-17) PD 「피격으로 경직 된 몬스터는 피격 모션 발생 후 바로 움직이지 않고 최소 0.5초 정도 딜레이 후 이동」 ·
|
||
// 「히트 모션 이후 원래 모션으로 돌아올 때는 밀린 위치를 반영해 대기 모션으로 돌아오도록(전진하며 원래 모습으로 돌아와 바로 플레이어와 붙는 현상 제거)」
|
||
[Tooltip("#903 피격 뒤 이 시간(초 · 피격 시각부터) 동안 이동 · 새 공격을 막는다(경직 길이보다 짧으면 경직 길이). 0 = 경직 길이만.")]
|
||
public float mobMoveLockSeconds = 0.5f;
|
||
[Tooltip("#903 true = 경직(자세 굳힘)이 풀릴 때 피격 클립의 되돌아오는 구간(몸이 앞으로 돌아옴)을 재생하지 않고 바로 대기 모션으로 바꾼다.")]
|
||
public bool mobRecoverToIdle = true;
|
||
[Tooltip("#903 피격 때 몹 위치(루트)를 때린 쪽 반대로 이만큼(m) 실제로 민다 — 대기 모션이 밀린 자리에서 이어진다. 0 = 끔. 보스는 밀지 않는다.")]
|
||
public float mobHitPushDistance = 0.3f;
|
||
[Tooltip("#903 위 밀기에 걸리는 시간(초).")]
|
||
public float mobHitPushSeconds = 0.12f;
|
||
|
||
// ── 2026-09-18 PD 「몬스터 넉백 거리가 너무 멀어. 우선은 절반 정도로 줄이고, 추후 캐릭터 테이블에서 공격 타수별 넉백 거리를 조절」
|
||
// 원본 넉백 = 남은 시간(CCTime) × 속도로 민다(뒤로 밀기 초속 4 m · 때린 쪽 반대로 밀기 초속 10 m). 새 넉백이 시작된 프레임에
|
||
// **남은 시간을 줄여** 총 이동 거리를 배율만큼으로 만든다(MobKnockbackScale · 원본 코드 0줄). 타수별 테이블 값은 설계서(인계 델타 추가분 35) 참조.
|
||
[Tooltip("몹 넉백 이동 거리 배율. 1 = 원본 · 0.5 = 절반. 0 이하 = 기능 끔(원본 그대로).")]
|
||
[Range(0f, 1f)] public float mobKnockbackDistanceScale = 0.5f;
|
||
[Tooltip("때린 쪽 반대로 미는 넉백(스킬 추가효과 KnockBack_byHitter · 초속 10 m)에도 같은 배율을 쓴다.")]
|
||
public bool mobKnockbackScaleByHitter = true;
|
||
|
||
// ─────────────────────────────────────────── 진단
|
||
|
||
[Header("진단 (검증용 · 배포 시 false)")]
|
||
[Tooltip("타격/발동 시각과 스로틀 결과를 Debug.Log 로 남긴다.")]
|
||
public bool logHits = false;
|
||
}
|