149 lines
10 KiB
C#
149 lines
10 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLShadowSlash.cs — 단검(비수 1043 · Dagger 10202) 주력 「그림자 베기」(Skill_ShadowSlash) — 시전 중 캐릭터 보이기 · 이펙트 크기 배율
|
||
//
|
||
// PD 원문(#943-⑧ · 2026-10-19): 「단검 주력 스킬 시전 시 캐릭터의 모습이 보이도록 수정하고, 이펙트의 크기를 절반으로 줄여줘.」
|
||
//
|
||
// ■ 안 보이던 원인(코드 실측) — 원본 Skill_ShadowSlash.Set 이 shooter.Set_Invisible(2.1 초)를 부른다 → Actor.Co_Invisible 이
|
||
// 애니메이터 배속 0 · 무적 · Model_OnOff(false)(파티클 외 렌더러 전부 끔) · Weapon_OnOff(false) · 이동 금지 → 2.1 초 뒤 되돌린 다음 대상 뒤 1.5 m 로 순간이동.
|
||
// 이펙트가 덮거나 카메라 문제가 아니라 원본 스킬이 모델을 끈다.
|
||
// ■ 처리 — 원본 Set 의 Set_Invisible 뒤 훅 1줄(OnSet): Co_Invisible 이 끈 렌더러 · 무기를 바로 다시 켠다(원본이 2.1 초 뒤 부르는 같은 함수).
|
||
// freezeAnim 0 이면 애니메이터 배속도 1 로 되돌려 원본 상태 사슬 DualBlades_Jump_Start → JumpAttack1·2·3 → Jump_End → idle 이 보인다
|
||
// (JumpAttack1~3 클립 이벤트 0 개 · Jump_Start 의 SkillEvent 는 시각 0 하나 · 종료 전이 0.7 이라 다시 안 돈다 — 2026-10-19 컨트롤러 · FBX 메타 실측).
|
||
// 무적 · 이동 금지 · 2.1 초 뒤 대상 뒤 순간이동 · 판정은 원본 그대로.
|
||
// ■ 이펙트 — 베기(go_effect = Slash · 파티클 6 층 · 크기 2.6 ~ 7.7)와 연기(go_invisibleEffect = FX_DarkSkill_SmokeExplosion)의 Transform 을
|
||
// 「처음 크기 × 배율」로(풀 재사용 때 누적 없음 · 처음 크기는 WLShadowSlashBase 에 기억) · 파티클 크기 모드 Hierarchy
|
||
// (베기 팩 원본이 Local 이라 Transform 배율이 안 먹는다). 베기 판정 상자(BoxCollider 5.25 × 9 × 5.9)는 keepHitBox 1 이면 배율의 역수로 키워 월드 크기 그대로.
|
||
// ■ 값 = Resources/WL/WLShadowSlash.json (C45) · 끄기 = enabled 0(원본 그대로)
|
||
// 🔴 Debug.LogError 금지(원본 ErrorLogHook 팝업) — 실패는 LastLog
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
using System.Reflection;
|
||
using UnityEngine;
|
||
|
||
namespace WL.Combat.Weapon
|
||
{
|
||
[System.Serializable]
|
||
public class WLShadowSlashJson
|
||
{
|
||
public int enabled = 0;
|
||
public int keepVisible = 1; // 1 = 시전 중 PC 렌더러 · 무기를 켠다(원본 = 2.1 초 숨김)
|
||
public int freezeAnim = 0; // 1 = 원본처럼 애니메이터 배속 0 유지(보이면 2.1 초 정지 자세) · 0 = 원본 상태 사슬 재생
|
||
public float slashScale = 1f; // 베기(go_effect) 보이는 크기 배율
|
||
public float smokeScale = 1f; // 연기(go_invisibleEffect) 크기 배율
|
||
public int keepHitBox = 1; // 1 = 베기 판정 상자 월드 크기 유지(보이는 크기만 바꿈)
|
||
// #954 PD 「비수 주력 = Slash Effects FREE 의 녹색 난사(Multiple Slash 2)로」 — 원본 베기 렌더러를 끄고(판정 상자 그대로) WL 사본을 같은 자리·시각에 띄운다
|
||
public string fxPrefab = ""; // Resources 경로(비우면 원본 베기 그대로)
|
||
public float fxScale = 1f, fxDelay = 0.5f, fxForward = 1.5f, fxYOffset = 0f, fxLife = 2.5f; // 배율 · 시전 뒤 시각(원본 go_effect 0.5 s) · PC 정면 거리(원본 1.5) · 높이 · 수명
|
||
public int hideOriginalFx = 0; // 1 = 원본 베기(go_effect) 렌더러 끔
|
||
}
|
||
|
||
public static class WLShadowSlash
|
||
{
|
||
public const string ResourcesPath = "WL/WLShadowSlash";
|
||
public static int Casts, Shown, Scaled, Errors;
|
||
public static string LastLog = "";
|
||
|
||
static WLShadowSlashJson s_cfg;
|
||
static MethodInfo s_model, s_weapon;
|
||
|
||
static WLShadowSlashJson Cfg
|
||
{
|
||
get
|
||
{
|
||
if (s_cfg != null) return s_cfg;
|
||
s_cfg = new WLShadowSlashJson();
|
||
var ta = Resources.Load<TextAsset>(ResourcesPath);
|
||
if (ta == null) { LastLog = "설정 없음 " + ResourcesPath; return s_cfg; }
|
||
try { JsonUtility.FromJsonOverwrite(ta.text, s_cfg); } catch (System.Exception ex) { LastLog = "설정 파싱 실패 — " + ex.Message; }
|
||
return s_cfg;
|
||
}
|
||
}
|
||
|
||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||
static void Boot() { s_cfg = null; Casts = Shown = Scaled = Errors = 0; LastLog = ""; }
|
||
|
||
public static string Describe() { return "casts " + Casts + " · shown " + Shown + " · scaled " + Scaled + " · err " + Errors + " · " + LastLog; }
|
||
|
||
/// <summary>원본 Skill_ShadowSlash.Set 훅(Set_Invisible 바로 뒤 1줄) — 캐릭터 보이기 · 베기 · 연기 크기 배율.</summary>
|
||
public static void OnSet(Skill_ShadowSlash skill)
|
||
{
|
||
try
|
||
{
|
||
var c = Cfg;
|
||
if (skill == null || c.enabled == 0) return;
|
||
Casts++;
|
||
var pd = skill.Get_ProjectTileData();
|
||
var shooter = pd != null ? pd.shooter : null;
|
||
if (c.keepVisible != 0 && !DSUtil.CheckNull(shooter))
|
||
{
|
||
bool model = Call(ref s_model, "Model_OnOff", shooter);
|
||
bool weapon = Call(ref s_weapon, "Weapon_OnOff", shooter);
|
||
if (model && weapon) Shown++;
|
||
if (c.freezeAnim == 0 && shooter.m_animation != null) shooter.m_animation.speed = 1f;
|
||
}
|
||
if (skill.go_effect != null) Scale(skill.go_effect.transform, c.slashScale, c.keepHitBox != 0);
|
||
if (skill.go_invisibleEffect != null) Scale(skill.go_invisibleEffect.transform, c.smokeScale, false);
|
||
if (!string.IsNullOrEmpty(c.fxPrefab) && !DSUtil.CheckNull(shooter)) // #954
|
||
{
|
||
if (c.hideOriginalFx != 0 && skill.go_effect != null) foreach (var rd in skill.go_effect.GetComponentsInChildren<Renderer>(true)) rd.enabled = false; // 풀 인스턴스라 한 번 끄면 유지
|
||
Runner.StartCoroutine(CoFx(shooter, c));
|
||
}
|
||
LastLog = "cast " + Casts + " · shown " + Shown + " · scaled " + Scaled;
|
||
}
|
||
catch (System.Exception ex) { Errors++; LastLog = "실패 — " + ex.Message; }
|
||
}
|
||
|
||
public static int FxShown;
|
||
static WLShadowSlashRunner s_runner;
|
||
static WLShadowSlashRunner Runner { get { if (s_runner == null) { var go = new GameObject("[WL954] ShadowSlashFx"); go.hideFlags = HideFlags.HideAndDontSave; Object.DontDestroyOnLoad(go); s_runner = go.AddComponent<WLShadowSlashRunner>(); } return s_runner; } }
|
||
/// <summary>#954 원본 베기가 켜지는 시각(fxDelay)에 WL 사본(녹색 난사)을 PC 정면 fxForward 에 띄운다(PC 방향 · fxLife 뒤 파괴).</summary>
|
||
static System.Collections.IEnumerator CoFx(Actor shooter, WLShadowSlashJson c)
|
||
{
|
||
if (c.fxDelay > 0f) yield return new WaitForSeconds(c.fxDelay);
|
||
if (DSUtil.CheckNull(shooter) || shooter.IsDead()) yield break;
|
||
var p = Resources.Load<GameObject>(c.fxPrefab); if (p == null) { LastLog = "fx 프리팹 없음 " + c.fxPrefab; yield break; }
|
||
var fwd = shooter.transform.forward; fwd.y = 0f; if (fwd.sqrMagnitude < 1e-4f) fwd = Vector3.forward; fwd.Normalize();
|
||
var pos = shooter.Get_CenterPositionFoward(c.fxForward) + Vector3.up * c.fxYOffset;
|
||
var go = Object.Instantiate(p, pos, Quaternion.LookRotation(fwd, Vector3.up)); go.transform.localScale = p.transform.localScale * c.fxScale;
|
||
foreach (var ps in go.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; ps.Play(true); }
|
||
Object.Destroy(go, Mathf.Max(0.5f, c.fxLife)); FxShown++; LastLog = "fx " + c.fxPrefab + " @" + pos.ToString("F2") + " ×" + c.fxScale;
|
||
}
|
||
|
||
/// <summary>Actor 의 protected 켜기 함수(Model_OnOff · Weapon_OnOff(bool))를 true 로 부른다 — 원본 Co_Invisible 끝과 같은 호출(가상 함수는 PCActor 쪽이 불린다).</summary>
|
||
static bool Call(ref MethodInfo m, string name, Actor a)
|
||
{
|
||
if (m == null) m = typeof(Actor).GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, new[] { typeof(bool) }, null);
|
||
if (m == null) { LastLog = "함수 없음 " + name; return false; }
|
||
m.Invoke(a, new object[] { true });
|
||
return true;
|
||
}
|
||
|
||
/// <summary>Transform 을 처음 크기 × k 로 · 파티클 크기 모드 Hierarchy · 판정 상자는 keepBox 면 1/k 로(월드 크기 유지).</summary>
|
||
static void Scale(Transform t, float k, bool keepBox)
|
||
{
|
||
if (t == null || k <= 0f) return;
|
||
var b = t.GetComponent<WLShadowSlashBase>();
|
||
if (b == null)
|
||
{
|
||
if (Mathf.Approximately(k, 1f)) return; // 손댄 적 없고 배율 1 = 원본 그대로
|
||
b = t.gameObject.AddComponent<WLShadowSlashBase>();
|
||
b.scale = t.localScale;
|
||
var bc0 = t.GetComponent<BoxCollider>();
|
||
if (bc0 != null) { b.hasBox = true; b.size = bc0.size; b.center = bc0.center; }
|
||
foreach (var ps in t.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; }
|
||
}
|
||
t.localScale = b.scale * k;
|
||
if (b.hasBox)
|
||
{
|
||
var bc = t.GetComponent<BoxCollider>();
|
||
if (bc != null) { float inv = keepBox ? 1f / k : 1f; bc.size = b.size * inv; bc.center = b.center * inv; }
|
||
}
|
||
Scaled++;
|
||
}
|
||
}
|
||
|
||
/// <summary>#943-⑧ 이 Transform(원본 스킬 풀 인스턴스)의 처음 크기 · 판정 상자 — 배율 누적 방지.</summary>
|
||
public sealed class WLShadowSlashBase : MonoBehaviour { public Vector3 scale, size, center; public bool hasBox; }
|
||
/// <summary>#954 코루틴 숙주(씬 밖 · 숨김).</summary>
|
||
public sealed class WLShadowSlashRunner : MonoBehaviour { }
|
||
}
|