127 lines
6.3 KiB
C#
127 lines
6.3 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
|
// WLJumpSlam.cs — 점프 내려찍기 타이밍: 점프 중(최고점 무렵)에 내려찍기 동작으로 넘어가 착지 순간에 지면을 찍게 (#909 · 2026-10-17)
|
|
//
|
|
// PD 원문: 「망치 주력 스킬 사용시 점프 최고점에서 내려찍기 모션 상태로 지면을 찍도록 해줘. (지금은 점프가 끝난 후 지면에서 내려치는 모션이 나오고 있어서 어색해)」
|
|
// ■ 원본 = 점프 이동은 Actor.Co_JumpAttack(높이 1 · landAt 초) · 동작은 컨트롤러 jump(Armed-Jump-Flip) → 끝 무렵 jumpattack(Mace-Attack-R2 · SkillEvent = 타격)
|
|
// 으로 넘어가 내려치기 타격이 착지 뒤에 나왔다.
|
|
// ■ 처리 — 스킬 시작(CombatEvents.SkillCast)부터 slamAt 초에 slamState 로 바로 넘기고, 그 클립의 SkillEvent(타격) 시각이 정확히 landAt 초(착지)에 오도록
|
|
// 애니메이터 속도를 맞춘다(타격 이벤트 시각은 클립에서 읽음). 착지 뒤 속도 1 로 되돌림.
|
|
// ■ 값 = Resources/WL/WLJumpSlam.json (C45) · 끄기 = enabled 0 → 원본 동작 · 원본 수정 0줄
|
|
// 🔴 Debug.LogError 금지(원본 팝업).
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
using UnityEngine;
|
|
using WL.Combat.Core;
|
|
|
|
namespace WL.Combat.Weapon
|
|
{
|
|
[System.Serializable]
|
|
public class WLJumpSlamRule
|
|
{
|
|
public string skill = ""; // eEffect 이름(원본 스킬 클래스)
|
|
public string slamState = ""; // 내려찍기 상태 이름
|
|
public string slamClip = ""; // 그 상태의 클립 이름(타격 SkillEvent 시각을 읽는다)
|
|
public float slamAt = 0.3f; // 스킬 시작 뒤 내려찍기로 넘어가는 시각(초)
|
|
public float landAt = 0.8f; // 착지 시각(초) = 원본 Co_JumpAttack 지속 시간
|
|
public float crossFade = 0.08f; // 넘어가는 시간(초)
|
|
}
|
|
|
|
[System.Serializable] public class WLJumpSlamJson { public int enabled; public WLJumpSlamRule[] rules = new WLJumpSlamRule[0]; public string note = ""; }
|
|
|
|
public static class WLJumpSlam
|
|
{
|
|
public const string ResourcesPath = "WL/WLJumpSlam";
|
|
public static int Slams, Missing, Errors; public static string LastLog = "";
|
|
static WLJumpSlamJson s_cfg;
|
|
|
|
static WLJumpSlamJson Cfg
|
|
{
|
|
get
|
|
{
|
|
if (s_cfg != null) return s_cfg;
|
|
s_cfg = new WLJumpSlamJson();
|
|
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;
|
|
CombatEvents.SkillCast.Remove(OnCast);
|
|
if (Cfg.enabled != 0) CombatEvents.SkillCast.Add(OnCast);
|
|
}
|
|
|
|
static void OnCast(in SkillCastEvent e)
|
|
{
|
|
try
|
|
{
|
|
var pc = e.actor as PCActor;
|
|
if (pc == null || !pc.IsMainPC() || e.skill == null || Cfg.rules == null) return;
|
|
string ef = e.skill.e_Skill.ToString();
|
|
foreach (var r in Cfg.rules)
|
|
{
|
|
if (r == null || r.skill != ef) continue;
|
|
var run = pc.GetComponent<WLJumpSlamRunner>(); if (run == null) run = pc.gameObject.AddComponent<WLJumpSlamRunner>();
|
|
run.Begin(pc, r);
|
|
return;
|
|
}
|
|
}
|
|
catch (System.Exception ex) { Errors++; LastLog = "시작 실패 — " + ex.Message; }
|
|
}
|
|
|
|
/// <summary>클립의 첫 SkillEvent 시각(초) · 없으면 -1.</summary>
|
|
internal static float ImpactTime(Animator an, string clipName)
|
|
{
|
|
if (an == null || an.runtimeAnimatorController == null || string.IsNullOrEmpty(clipName)) return -1f;
|
|
foreach (var c in an.runtimeAnimatorController.animationClips)
|
|
{
|
|
if (c == null || c.name != clipName) continue;
|
|
foreach (var ev in c.events) if (ev.functionName == "SkillEvent") return ev.time;
|
|
}
|
|
return -1f;
|
|
}
|
|
}
|
|
|
|
public sealed class WLJumpSlamRunner : MonoBehaviour
|
|
{
|
|
PCActor _pc; WLJumpSlamRule _r; float _t0, _impact; bool _on, _slammed;
|
|
|
|
public void Begin(PCActor pc, WLJumpSlamRule r)
|
|
{
|
|
_pc = pc; _r = r; _t0 = Time.time; _slammed = false;
|
|
_impact = WLJumpSlam.ImpactTime(pc.m_animation, r.slamClip);
|
|
_on = _impact > 0f;
|
|
if (!_on) { WLJumpSlam.Missing++; WLJumpSlam.LastLog = "타격 이벤트 없음 " + r.slamClip; }
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (!_on) return;
|
|
var an = _pc != null ? _pc.m_animation : null;
|
|
if (an == null || _pc.IsDead() || _pc.Get_CurAnim() != eAnim.Skill) { End(an); return; }
|
|
float t = Time.time - _t0;
|
|
if (!_slammed && t >= _r.slamAt)
|
|
{
|
|
_slammed = true;
|
|
float remain = Mathf.Max(0.05f, _r.landAt - t);
|
|
an.CrossFadeInFixedTime(_r.slamState, Mathf.Max(0f, _r.crossFade), 0, 0f);
|
|
an.speed = Mathf.Clamp(_impact / remain, 0.3f, 3f);
|
|
WLJumpSlam.Slams++;
|
|
WLJumpSlam.LastLog = "내려찍기 " + _r.slamState + " · " + t.ToString("0.00") + " s · 속도 " + an.speed.ToString("0.00") + " · 타격 예정 " + _r.landAt.ToString("0.00") + " s";
|
|
}
|
|
if (_slammed && t >= _r.landAt + 0.05f) End(an);
|
|
}
|
|
|
|
void End(Animator an)
|
|
{
|
|
if (_on && _slammed && an != null) an.speed = 1f;
|
|
_on = false;
|
|
}
|
|
|
|
void OnDisable() { _on = false; }
|
|
}
|
|
}
|