254 lines
16 KiB
C#
254 lines
16 KiB
C#
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
// WLTelegraphSettings.cs — 적 공격 예고(텔레그래프) 값의 단일 출처(SOT · C45)
|
|||
|
|
//
|
|||
|
|
// PD 지시 #815-5 「적의 공격을 확실히 유저가 인지하고 피할 수 있는 매커니즘」
|
|||
|
|
// 발주서 WL-815f · 기준서 v1 §G-0~§G-4 · §G-7 1차(= ① 데칼 + ⓐ 속도 곡선 + ⓓ 펄스 + ③ 색 규약 + ④ SFX)
|
|||
|
|
//
|
|||
|
|
// 에셋 경로(고정): Assets/WL/Combat/Settings/Resources/WL/WLTelegraphSettings.asset
|
|||
|
|
// → Resources.Load<WLTelegraphSettings>("WL/WLTelegraphSettings") 로 1회 로드 후 캐시.
|
|||
|
|
// 에셋이 없거나 enabled = false 또는 RuntimeDisabled 면 Telegraph 가 아무 것도 하지 않는다
|
|||
|
|
// (C8 · 애니메이터 속도 원본 · 데칼 0 · MPB 0 · 스케일 원본 = 원본 동작 100 %).
|
|||
|
|
//
|
|||
|
|
// 【기준서 §G-0 실측 요약 — 이 값들의 근거】
|
|||
|
|
// · 원본에는 선딜 개념이 0 이다. 데미지는 공격 클립 안의 애니메이션 이벤트 `Projectile` 이 낸다.
|
|||
|
|
// 즉 「예고 시간 = 클립 시작 ~ 이벤트 시각」이고 실측 0.081~0.228 s(잡몹·엘리트 전원).
|
|||
|
|
// · 그래서 ⓐ 속도 곡선이 성립한다 — 애니메이터를 늦추면 **히트(=데미지)도 같이 늦어진다**(별도 동기화 0).
|
|||
|
|
// · 공격 판정 범위의 출처 = 투사체 프리팹의 BoxCollider 다(근접도 투사체를 쏜다). 실측:
|
|||
|
|
// Mob/P_*_Meele 3×3×3 center 0 · Elite/P_Golem_A 3×3×3 center z 1.5 · FieldBoss/Anubis_Attack 2×2×4 center z 2
|
|||
|
|
// → 데칼 크기를 코드 상수로 넣지 않고 **그 프리팹의 콜라이더를 읽어** 그린다 = 판정과 100 % 일치.
|
|||
|
|
// · URP Decal Renderer Feature 가 없다(URP-Balanced-Renderer 의 피처 = SSAO 1개뿐) → DecalProjector 는 화면에 안 나온다.
|
|||
|
|
// 기존 인디케이터 20종이 쓰는 **파티클/쿼드 방식**을 따른다(여기서는 런타임 쿼드 2장).
|
|||
|
|
// · 보스(Anubis)는 코드가 3 s 차징을 이미 건다(Boss_Anubis.cs) → **보스는 건드리지 않는다**(bossUntouched).
|
|||
|
|
//
|
|||
|
|
// 🔴 어셈블리 주의: Assets/WL/Combat/ 에 .asmdef 를 만들지 말 것.
|
|||
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
namespace WL.Combat.Telegraph
|
|||
|
|
{
|
|||
|
|
/// <summary>기준서 §G-3 위험 색 규약 「제안」. 빨강 = 못 피한다(막기) · 노랑 = 피할 수 있다(회피).</summary>
|
|||
|
|
public enum TelegraphDanger { Avoidable = 0, Unavoidable = 1 }
|
|||
|
|
|
|||
|
|
/// <summary>데칼이 「채워지는」 방향.</summary>
|
|||
|
|
public enum TelegraphFill { ForwardSweep = 0, CenterExpand = 1 }
|
|||
|
|
|
|||
|
|
[CreateAssetMenu(fileName = "WLTelegraphSettings", menuName = "WL/Telegraph Settings", order = 326)]
|
|||
|
|
public sealed class WLTelegraphSettings : ScriptableObject
|
|||
|
|
{
|
|||
|
|
public const string ResourcesPath = "WL/WLTelegraphSettings";
|
|||
|
|
|
|||
|
|
static WLTelegraphSettings s_cached;
|
|||
|
|
static bool s_lookupDone;
|
|||
|
|
|
|||
|
|
public static WLTelegraphSettings Instance
|
|||
|
|
{
|
|||
|
|
get
|
|||
|
|
{
|
|||
|
|
if (!s_lookupDone)
|
|||
|
|
{
|
|||
|
|
s_cached = Resources.Load<WLTelegraphSettings>(ResourcesPath);
|
|||
|
|
s_lookupDone = true;
|
|||
|
|
}
|
|||
|
|
return s_cached;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>진단·A/B 전용 런타임 스위치. 에셋을 건드리지 않고 예고만 끈다.</summary>
|
|||
|
|
public static bool RuntimeDisabled;
|
|||
|
|
|
|||
|
|
public static bool Enabled { get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled; } }
|
|||
|
|
|
|||
|
|
/// <summary>811gh ImpactTier 가 읽는다 — 예고 중인 몹에게 히트스톱을 걸지 않는다(기준서 §G-4).</summary>
|
|||
|
|
public static bool SkipHitStop { get { var i = Instance; return i != null && i.enabled && !RuntimeDisabled && i.skipHitStopDuringTelegraph; } }
|
|||
|
|
|
|||
|
|
public static void ClearCache() { s_cached = null; s_lookupDone = false; }
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── 전체 스위치
|
|||
|
|
[Header("전체 스위치")]
|
|||
|
|
[Tooltip("끄면 속도 곡선·데칼·펄스·색·SFX 가 전부 비활성 = 원본 동작 100 %(C8 롤백 1순위).")]
|
|||
|
|
public bool enabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("예고 시작/종료를 Console 에 남긴다(검증용 · GC 발생).")]
|
|||
|
|
public bool verboseLog = false;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ⓐ 예고 시간 · 속도 곡선
|
|||
|
|
[Header("ⓐ 예고 시간 (기준서 §G-6 표 · AttackStarted → HitboxSpawned 사이 목표 초)")]
|
|||
|
|
[Tooltip("잡몹 근접 목표 예고 초. 기준서 §G-6 = 0.5 s(원본 실측 0.081~0.167).")]
|
|||
|
|
public float telegraphSecondsNormal = 0.5f;
|
|||
|
|
|
|||
|
|
[Tooltip("잡몹 원거리(Batty_B/D 102·104) 목표 예고 초. 기준서 §G-6 = 0.6 s.")]
|
|||
|
|
public float telegraphSecondsRanged = 0.6f;
|
|||
|
|
|
|||
|
|
[Tooltip("엘리트 목표 예고 초. 기준서 §G-6 = 0.8 s(첫 타만).")]
|
|||
|
|
public float telegraphSecondsElite = 0.8f;
|
|||
|
|
|
|||
|
|
[Tooltip("🔴 보스는 건드리지 않는다 — 원본 코드가 이미 3 s 차징을 건다(Boss_Anubis.cs Skill1/2ChargingTime = 3).\n" +
|
|||
|
|
"끄면 보스 평타에도 예고가 걸린다(발주 범위 밖 · 기본 켬 유지 권장).")]
|
|||
|
|
public bool bossUntouched = true;
|
|||
|
|
|
|||
|
|
[Header("ⓐ 속도 곡선 (animator.speed = 히트시각 ÷ 목표 · 하한 있음)")]
|
|||
|
|
[Tooltip("끄면 애니메이터 속도를 건드리지 않는다(데칼·펄스·색만 남는다).")]
|
|||
|
|
public bool speedCurveEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("🔴 한계값. 원본 속도 대비 이 배수보다 더 느리게는 만들지 않는다.\n" +
|
|||
|
|
"기준서 §G-2 ⓐ: k = 0.135 는 사실상 정지 화면이라 자연스러운 하한은 0.35 (Batty 예고 0.23 s).\n" +
|
|||
|
|
"→ 속도 곡선 단독으로 0.5 s 는 안 된다. 데칼·펄스와 반드시 함께 쓴다.")]
|
|||
|
|
public float minSpeedMultiplier = 0.35f;
|
|||
|
|
|
|||
|
|
[Tooltip("공격 클립의 히트 애니메이션 이벤트 함수명(이 이름의 이벤트 시각 = 원본 예고 시간).")]
|
|||
|
|
public string[] hitEventNames = new[] { "Projectile", "BossProjectile2", "BossProjectile3" };
|
|||
|
|
|
|||
|
|
[Tooltip("클립에서 히트 이벤트를 못 찾았을 때 쓰는 기본 히트 시각(초). 기준서 실측 중앙값 ≈ 0.16.")]
|
|||
|
|
public float defaultHitSeconds = 0.16f;
|
|||
|
|
|
|||
|
|
[Tooltip("첫 틱에서 실제 재생 중인 클립을 다시 읽어 히트 시각을 보정한다(이름 추정이 틀렸을 때 자가 치유 · 두 번째 공격부터 정확).")]
|
|||
|
|
public bool correctFromLiveClip = true;
|
|||
|
|
|
|||
|
|
[Tooltip("HitboxSpawned 가 이 시간 안에 안 오면 강제로 원복한다(피격·사망으로 공격이 끊긴 경우 · 초).")]
|
|||
|
|
public float maxTelegraphSeconds = 2.5f;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ① 지면 데칼
|
|||
|
|
[Header("① 지면 데칼 (투사체 BoxCollider 를 읽어 같은 크기 · 런타임 쿼드 2장 · 풀링)")]
|
|||
|
|
[Tooltip("끄면 판을 그리지 않는다.")]
|
|||
|
|
public bool decalEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("동시에 켤 수 있는 판의 최대 수. 초과하면 그 예고는 판을 생략한다(811s 예산기 방식 · 군중에서 화면이 뭉개지는 것 방지).")]
|
|||
|
|
public int maxConcurrent = 6;
|
|||
|
|
|
|||
|
|
[Tooltip("풀 예열 개수(첫 전투에서의 순간 할당을 없앤다). maxConcurrent 이상 권장.")]
|
|||
|
|
public int poolWarmCount = 8;
|
|||
|
|
|
|||
|
|
[Tooltip("몹 발밑 기준 판의 높이(지면 Z-파이팅 방지 · m).")]
|
|||
|
|
public float groundYOffset = 0.06f;
|
|||
|
|
|
|||
|
|
[Tooltip("발사 위치 규약(Actor.Get_CenterPositionFoward 의 기본값)과 같은 전방 거리(m). 원본 = 0.3.")]
|
|||
|
|
public float forwardOffset = 0.3f;
|
|||
|
|
|
|||
|
|
[Tooltip("판 크기 = 콜라이더 크기 × 이 배수. 1 = 판정과 100 % 일치(기본값 · 바꾸지 말 것 권장).")]
|
|||
|
|
public float sizeScale = 1f;
|
|||
|
|
|
|||
|
|
[Tooltip("투사체 프리팹을 못 읽었을 때의 기본 판 크기(기준서 실측 잡몹 근접 = 3×3×3).")]
|
|||
|
|
public Vector3 defaultBoxSize = new Vector3(3f, 3f, 3f);
|
|||
|
|
|
|||
|
|
[Tooltip("투사체 수명(f_ProjectileLifeTime1)이 이 값 이상이면 원거리로 보고 직선 판을 쓴다.\n" +
|
|||
|
|
"실측: 근접 0.1 s · 원거리 1.5 s(Batty_B/D 102·104 · Mummy_King 1024).")]
|
|||
|
|
public float rangedLifetimeThreshold = 0.5f;
|
|||
|
|
|
|||
|
|
[Tooltip("원거리 직선 판의 최대 길이(m). 길이 = 투사체 속도 × 수명 이지만 화면을 넘지 않게 자른다.")]
|
|||
|
|
public float rangedMaxLength = 12f;
|
|||
|
|
|
|||
|
|
[Tooltip("🔴 투사체 프리팹에는 속도 필드가 없다(실측) — 실제 속도는 런타임 ProjectileData.Speed 기본값이다.\n" +
|
|||
|
|
"원본 ProjectileBase.cs:19 실측 = 5. Projectile_Strait.m_Speed 가 0 보다 크면 프리팹 값이 이긴다.")]
|
|||
|
|
public float defaultProjectileSpeed = 5f;
|
|||
|
|
|
|||
|
|
[Tooltip("몹이 회전하면 판도 따라 돈다(원본 Play_Attack 이 transform.LookAt 으로 계속 돌린다).")]
|
|||
|
|
public bool followRotation = true;
|
|||
|
|
|
|||
|
|
[Tooltip("채워지는 방향. ForwardSweep = 몹 쪽에서 앞으로 채워진다 · CenterExpand = 가운데서 퍼진다.")]
|
|||
|
|
public TelegraphFill fillMode = TelegraphFill.ForwardSweep;
|
|||
|
|
|
|||
|
|
[Tooltip("바깥 판(범위)의 알파.")]
|
|||
|
|
[Range(0f, 1f)] public float plateAlpha = 0.28f;
|
|||
|
|
|
|||
|
|
[Tooltip("채워지는 안쪽 판의 알파.")]
|
|||
|
|
[Range(0f, 1f)] public float fillAlpha = 0.62f;
|
|||
|
|
|
|||
|
|
[Tooltip("판 셰이더 후보(앞에서부터 Shader.Find · 새 셰이더/머티리얼 에셋 0 · 811gh DashAfterImage 와 같은 방식).")]
|
|||
|
|
public string[] decalShaderNames = new[] { "Universal Render Pipeline/Unlit", "Unlit/Transparent", "Sprites/Default" };
|
|||
|
|
|
|||
|
|
[Tooltip("판 색 프로퍼티 후보(앞에서부터 FindPropertyIndex).")]
|
|||
|
|
public string[] decalColorProperties = new[] { "_BaseColor", "_Color", "_TintColor" };
|
|||
|
|
|
|||
|
|
[Tooltip("판의 렌더 큐(반투명 · 3000 이상). 지면 위·캐릭터 아래로 보이게 한다.")]
|
|||
|
|
public int decalRenderQueue = 3050;
|
|||
|
|
|
|||
|
|
[Header("① IndicatorInfo.Show_Indicator 재사용 (원본 공개 API · 로드/풀링/자동 끄기 완비)")]
|
|||
|
|
[Tooltip("비어 있지 않으면 Res_Addr/Obj/<이름>.prefab 인디케이터를 같이 띄운다(IndicatorInfo.Make_Indicator 로 풀에서 꺼내 WL 이 위치·회전·수명을 직접 준다).\n" +
|
|||
|
|
"🔴 기본값이 빈 문자열인 이유 = 보유 인디케이터 20종은 전부 **원형 파티클**이라 사각 콜라이더와 크기를 100 % 맞출 수 없다.\n" +
|
|||
|
|
"판정 일치는 위의 쿼드가 담당하고, 이 항목은 「연출 덧칠」이다. 예: Golem_Child_Skill1_Indicator.")]
|
|||
|
|
public string indicatorPrefabName = "";
|
|||
|
|
|
|||
|
|
[Tooltip("인디케이터의 파티클 시작 크기를 실측해 콜라이더 최대 변에 맞춰 스케일을 잡는다(끄면 스케일 1).")]
|
|||
|
|
public bool indicatorAutoScale = true;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ③ 색 규약
|
|||
|
|
[Header("③ 위험 색 규약 (기준서 §G-3 「제안」 · 빨강 = 못 피함/막기 · 노랑 = 회피 가능)")]
|
|||
|
|
[Tooltip("회피 가능(노랑) 판 색.")]
|
|||
|
|
[ColorUsage(true, true)] public Color colorAvoidable = new Color(1f, 0.82f, 0.15f, 1f);
|
|||
|
|
|
|||
|
|
[Tooltip("회피 불가(빨강) 판 색.")]
|
|||
|
|
[ColorUsage(true, true)] public Color colorUnavoidable = new Color(1f, 0.16f, 0.12f, 1f);
|
|||
|
|
|
|||
|
|
[Tooltip("잡몹 근접의 위험 등급. 기준서 §G-6 = 노랑(회피 가능).")]
|
|||
|
|
public TelegraphDanger dangerNormalMelee = TelegraphDanger.Avoidable;
|
|||
|
|
|
|||
|
|
[Tooltip("잡몹 원거리의 위험 등급. 기준서 §G-6 = 노랑(회피 가능).")]
|
|||
|
|
public TelegraphDanger dangerNormalRanged = TelegraphDanger.Avoidable;
|
|||
|
|
|
|||
|
|
[Tooltip("엘리트의 위험 등급. 기준서 §G-6 엘리트 연속기 = 「데칼 + 색(노랑)」.\n" +
|
|||
|
|
"🔴 「막아야 하는 공격」을 도입할 때 Unavoidable(빨강)로 올리면 된다 = 값 한 줄.")]
|
|||
|
|
public TelegraphDanger dangerElite = TelegraphDanger.Avoidable;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ⓓ 몹 색/펄스
|
|||
|
|
[Header("ⓓ 몹 틴트 (MobHitFlash MPB 경로 재사용 · 813s 와 같은 소유자 = 머티리얼 인스턴스 0)")]
|
|||
|
|
[Tooltip("끄면 몹 색을 건드리지 않는다.")]
|
|||
|
|
public bool tintEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("회피 가능(노랑) 예고 중 알베도 색.")]
|
|||
|
|
[ColorUsage(true, true)] public Color tintAlbedoAvoidable = new Color(1.20f, 1.05f, 0.60f, 1f);
|
|||
|
|
[Tooltip("회피 가능(노랑) 예고 중 에미션 색.")]
|
|||
|
|
[ColorUsage(true, true)] public Color tintEmissionAvoidable = new Color(0.55f, 0.34f, 0.02f, 1f);
|
|||
|
|
|
|||
|
|
[Tooltip("회피 불가(빨강) 예고 중 알베도 색.")]
|
|||
|
|
[ColorUsage(true, true)] public Color tintAlbedoUnavoidable = new Color(1.30f, 0.60f, 0.55f, 1f);
|
|||
|
|
[Tooltip("회피 불가(빨강) 예고 중 에미션 색.")]
|
|||
|
|
[ColorUsage(true, true)] public Color tintEmissionUnavoidable = new Color(0.75f, 0.08f, 0.04f, 1f);
|
|||
|
|
|
|||
|
|
[Tooltip("히트 직전 흰 섬광을 넣는다.")]
|
|||
|
|
public bool flashEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("히트까지 남은 시간이 이 값 이하가 되면 흰 섬광으로 바꾼다(초 · 「직전」).")]
|
|||
|
|
public float flashLeadSeconds = 0.06f;
|
|||
|
|
|
|||
|
|
[Tooltip("흰 섬광이 유지되는 시간(초 · 60 fps 기준 0.05 ≈ 3프레임).")]
|
|||
|
|
public float flashSeconds = 0.05f;
|
|||
|
|
|
|||
|
|
[ColorUsage(true, true)] public Color flashAlbedo = new Color(2.2f, 2.2f, 2.2f, 1f);
|
|||
|
|
[ColorUsage(true, true)] public Color flashEmission = new Color(1.6f, 1.6f, 1.6f, 1f);
|
|||
|
|
|
|||
|
|
[Header("ⓓ 스케일 펄스 (813s EliteMarker 와 같은 localScale 경로 · 원 스케일 복원)")]
|
|||
|
|
[Tooltip("끄면 크기를 건드리지 않는다.")]
|
|||
|
|
public bool pulseEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("예고 끝(히트)에서의 최대 배수. 기준서 §G-2 ⓓ = 1.12.")]
|
|||
|
|
public float pulseScale = 1.12f;
|
|||
|
|
|
|||
|
|
[Tooltip("원복 뒤 이 시간 동안 원 스케일을 다시 확인한다(811b WLHitFeel 스케일 펀치와 겹칠 때 크기가 남는 것 방지 · 초).")]
|
|||
|
|
public float pulseRestoreGuardSeconds = 0.2f;
|
|||
|
|
|
|||
|
|
// ─────────────────────────────────────────── ④ SFX · 히트스톱
|
|||
|
|
[Header("④ 예고 SFX (보유 사운드 재사용 · 동시 상한)")]
|
|||
|
|
[Tooltip("끄면 소리를 내지 않는다.")]
|
|||
|
|
public bool sfxEnabled = true;
|
|||
|
|
|
|||
|
|
[Tooltip("eSound 인덱스. 9 = s009_CastSpell(짧은 캐스팅 음). 프로브가 SoundInfo.arr_clip 실존을 확인한다.")]
|
|||
|
|
public int sfxIndex = 9;
|
|||
|
|
|
|||
|
|
[Tooltip("최대 볼륨(거리 감쇠는 원본 Play_OneShot_byDistance 가 한다).")]
|
|||
|
|
[Range(0f, 1f)] public float sfxVolume = 0.35f;
|
|||
|
|
|
|||
|
|
[Tooltip("기준서 §G-4 「동시 3발 상한」 — 이 창 안에서 이 개수까지만 낸다(군중에서 소리가 뭉개지는 것 방지).")]
|
|||
|
|
public int sfxMaxConcurrent = 3;
|
|||
|
|
|
|||
|
|
[Tooltip("동시 상한을 세는 창(초).")]
|
|||
|
|
public float sfxWindowSeconds = 0.35f;
|
|||
|
|
|
|||
|
|
[Header("④ 811gh 히트스톱과의 관계 (기준서 §G-4)")]
|
|||
|
|
[Tooltip("🔴 예고 중인 몹을 때렸을 때 히트스톱을 걸지 않는다.\n" +
|
|||
|
|
"이유: hitStopMs 가 예고 구간에 겹치면 timeScale 이 바뀌어 **예고 길이가 흔들린다**.\n" +
|
|||
|
|
"끄면 811gh 기존 동작 그대로(히트스톱 유지).")]
|
|||
|
|
public bool skipHitStopDuringTelegraph = true;
|
|||
|
|
}
|
|||
|
|
}
|