298 lines
21 KiB
C#
298 lines
21 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줄
|
||
// ■ #998 (2026-10-30) PD 「창 기본 스킬은 회전하며 점프하는게 아니라 제자리에서 점프했다가 내려치기만」 · 「창 기본 스킬 점프하며 내려치는 동안 짧게 기가 모이는 연출을 발생한 후 내리쳐」
|
||
// · 「대검 기본 스킬 = 점프하는 동안 몸을 회전하며 검기로 몸을 세로 방향으로 최대 3회전 하며 공격하고 내려치는 공격(회전하는 검기는 기본 궤적 검기)」
|
||
// — 규칙 classId(직업 행 먼저) · ownJump 1 = 원본 점프 이동(Co_JumpAttack · 대상 쪽 · 앞구르기 클립 jump)을 Actor 훅 1줄(OwnJump)로 끄고 제자리 점프:
|
||
// 오름 rise(내려찍기 클립을 처음부터 타격 시각 × holdAt 까지 = 무기를 든 자세) → 멈춤 hang(자세 고정 · hangFx) → 내림 fall(타격 시각이 착지에 오게 속도) · 높이 height
|
||
// spins = 오름 + 멈춤 동안 엉덩이 뼈를 몸 중심 · 오른쪽 축으로 세로 회전하는 바퀴 수 · spinFx = 바퀴마다 몸 중심에 띄우는 검기(PC 를 따라감)
|
||
// 🔴 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; // 넘어가는 시간(초)
|
||
public int classId; // #998 0 = 모든 직업 · > 0 = 이 직업만(직업이 맞는 행 먼저)
|
||
public int ownJump; // #998 1 = 제자리 점프(아래 값) · 0 = 원본 점프 이동 + slamAt · landAt
|
||
public float height = 1f, rise = 0.3f, hang = 0.25f, fall = 0.2f; // #998 높이(m) · 오름 · 멈춤 · 내림(초)
|
||
public float holdAt = 0.5f; // #998 멈춤 자세 = 내려찍기 클립 타격 시각의 이 비율 자리
|
||
public WLStaffEarthFx hangFx = new WLStaffEarthFx(); // #998 멈춤 동안 모으기 이펙트(시전자 기준 · follow 1 = 따라감)
|
||
public int spins; // #998 세로 회전 바퀴 수(0 = 안 돎)
|
||
public WLStaffEarthFx spinFx = new WLStaffEarthFx(); // #998 바퀴마다 몸 중심에 띄우는 검기(PC 를 따라감 · euler = 정면 기준)
|
||
// #998 PD 「대검 기본 스킬은 회전하는 동안 제자리에 있는게 아니라 1.5m 가량 전진하며 낙하까지 · 회전하는 동안 상승(↗)과 낙하(↘)하며 회전이 멈출 타이밍에 맞춰 내려치는 동작이 이어지도록」
|
||
// arc 1 = 높이가 착지까지 한 포물선(오름 · 멈춤 · 내림 구분 없이 ↗↘ · 회전은 rise + hang 동안 = 내려가는 중에 멈추고 바로 내림 fall 동안 내려치기) · forward = 착지까지 정면으로 가는 거리(m)
|
||
public int arc;
|
||
public float forward;
|
||
// #1000 PD 「대검 기본 스킬 … 마지막 내려칠 때는 기본 공격 마지막타와 동일하게 내려치기동작과 이펙트로」 — landFx = 착지(타격) 순간 PC 정면 기준 offset 자리에 띄우는 지면 이펙트(기본 4타 지면 충격과 같은 프리팹)
|
||
public WLStaffEarthFx landFx = new WLStaffEarthFx();
|
||
public WLStaffEarthFx landTrace = new WLStaffEarthFx(); // #1006 PD 「대검 기본 스킬 사용 후 지면에 충격 흔적이 남도록 (해머 공격에 사용 된 지면 충격 효과를 참고)」 — landFx 와 같은 순간 · 같은 기준(PC 정면 offset)에 남기는 흔적
|
||
public float impactTime; // #1001 0 보다 크면 내려찍기 타격 시각(클립 초)을 클립 SkillEvent 대신 이 값으로(대검 = SkillEvent 를 점프 시작으로 옮겨 회전 연타 판정을 WLStaffEarth 가 맡음 · 내려치기 동작 시각은 4타 그대로 0.80)
|
||
// #1008 PD 「해머 주력 스킬 시전 시 해머를 내려치기 전 딜레이를 추가하고, 딜레이 시간 동안 해머가 빛나게 해줘. (해머 각성 스킬의 50% 정도의 딜레이 및 빛나는 연출)」 —
|
||
// hangGlow > 0 = 멈춤(hang) 동안 무기 흰 빛 0 → hangGlow(해머 각성과 같은 덧그림 WLWeaponGlowFx) · 내림 동안 유지 · 착지(타격)부터 glowFade 초에 꺼짐
|
||
public float hangGlow;
|
||
public float[] glowColor = { 1.6f, 1.6f, 1.6f };
|
||
public float glowFill = 0.35f, glowRim = 2.5f, glowFade = 0.25f;
|
||
public string glowMaterial = "WL/Fx/WL_Mat_WeaponGlow";
|
||
}
|
||
|
||
[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;
|
||
var r = Find(pc, e.skill.e_Skill.ToString()); if (r == null) return;
|
||
var run = pc.GetComponent<WLJumpSlamRunner>(); if (run == null) run = pc.gameObject.AddComponent<WLJumpSlamRunner>();
|
||
run.Begin(pc, r);
|
||
}
|
||
catch (System.Exception ex) { Errors++; LastLog = "시작 실패 — " + ex.Message; }
|
||
}
|
||
|
||
/// <summary>#998 직업이 맞는 행 먼저 · 없으면 classId 0 행.</summary>
|
||
static WLJumpSlamRule Find(PCActor pc, string skill)
|
||
{
|
||
WLJumpSlamRule any = null; int cls = pc.Get_ID();
|
||
foreach (var r in Cfg.rules)
|
||
{
|
||
if (r == null || r.skill != skill) continue;
|
||
if (r.classId == cls) return r;
|
||
if (r.classId == 0 && any == null) any = r;
|
||
}
|
||
return any;
|
||
}
|
||
|
||
/// <summary>#998 Actor.Play_Skill 훅(1줄) — 이번 시전을 제자리 점프 러너가 맡았으면 true(원본 점프 이동 Co_JumpAttack 을 건너뜀). SkillCast 이벤트(Begin)가 먼저 불린다.</summary>
|
||
public static bool OwnJump(Actor a)
|
||
{
|
||
try { var pc = a as PCActor; if (pc == null || Cfg.enabled == 0) return false; var run = pc.GetComponent<WLJumpSlamRunner>(); return run != null && run.OwnActive; }
|
||
catch (System.Exception ex) { Errors++; LastLog = "OwnJump 실패 — " + ex.Message; return false; }
|
||
}
|
||
public static int OwnJumps, SpinFxShown, HangFxShown, LandFxShown, LandTraces, Glows; public static float LastSpinDeg, LastPeak, LastGlow;
|
||
|
||
/// <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;
|
||
// #998 제자리 점프
|
||
bool _own, _started, _hung, _set, _landFx; float _hold, _y0, _peak, _lastAng; int _spun; Transform _bone; Vector3 _pivotLocal, _setPos, _start, _fwd; Quaternion _setRot;
|
||
// #1008 멈춤 동안 무기 흰 빛
|
||
readonly WLWeaponGlowFx _glow = new WLWeaponGlowFx(); WLJumpSlamRule _glowRule; float _glowFadeAt = -1f; Color _glowCol = Color.white;
|
||
public bool OwnActive { get { return _on && _own; } }
|
||
|
||
void GlowSet(float g) { if (_glowRule == null) return; _glow.Set(g, _glowCol, _glowRule.glowFill, _glowRule.glowRim); WLJumpSlam.LastGlow = g; }
|
||
void GlowFade() { if (_glow.Active && _glowFadeAt < 0f) _glowFadeAt = Time.time; }
|
||
/// <summary>#1008 착지 뒤 흰 빛 끄기 — 시전이 끝난 뒤(_on 꺼짐)에도 돈다.</summary>
|
||
void GlowTick()
|
||
{
|
||
if (_glowFadeAt < 0f || _glowRule == null) return;
|
||
float k = 1f - Mathf.Clamp01((Time.time - _glowFadeAt) / Mathf.Max(0.01f, _glowRule.glowFade));
|
||
if (k <= 0f) { _glow.End(); _glowFadeAt = -1f; return; }
|
||
GlowSet(_glowRule.hangGlow * k);
|
||
}
|
||
|
||
public void Begin(PCActor pc, WLJumpSlamRule r)
|
||
{
|
||
if (_on && _own) Restore();
|
||
_glow.End(); _glowFadeAt = -1f; _glowRule = null; // #1008
|
||
_pc = pc; _r = r; _t0 = Time.time; _slammed = false;
|
||
_impact = r.impactTime > 0f ? r.impactTime : WLJumpSlam.ImpactTime(pc.m_animation, r.slamClip);
|
||
_on = _impact > 0f;
|
||
_own = _on && r.ownJump != 0; _started = _hung = _set = _landFx = false; _spun = 0; _lastAng = 0f; _bone = null;
|
||
if (!_on) { WLJumpSlam.Missing++; WLJumpSlam.LastLog = "타격 이벤트 없음 " + r.slamClip; return; }
|
||
if (!_own) return;
|
||
_hold = _impact * Mathf.Clamp01(r.holdAt); _y0 = pc.transform.position.y; _peak = 0f; _start = pc.transform.position; _fwd = pc.transform.forward;
|
||
if (OptionInfo.Ins != null) OptionInfo.Ins.Set_PCMove(pc, false); // 원본 Co_JumpAttack 과 같게(조작 끔 · 착지에 되돌림)
|
||
if (r.spins > 0)
|
||
{
|
||
_bone = SpinBone(pc);
|
||
_pivotLocal = pc.transform.InverseTransformPoint(WL.Combat.Impact.WLImpactFx.BodyCenter(pc));
|
||
}
|
||
WLJumpSlam.OwnJumps++;
|
||
}
|
||
|
||
/// <summary>#956 과 같은 뼈 — Humanoid Hips · 없으면 루트 직계 중 이름이 Root 로 시작하는 것(이 PC = Root_M).</summary>
|
||
static Transform SpinBone(PCActor pc)
|
||
{
|
||
var an = pc.m_animation; Transform b = an != null && an.isHuman ? an.GetBoneTransform(HumanBodyBones.Hips) : null;
|
||
if (b == null) foreach (Transform c in pc.transform) if (c.name.StartsWith("Root")) { b = c; break; }
|
||
return b;
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
GlowTick(); // #1008
|
||
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 (_own) { OwnTick(an, t); return; }
|
||
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);
|
||
}
|
||
|
||
/// <summary>#998 제자리 점프 — 오름(무기 드는 자세까지 감기) → 멈춤(자세 고정 · 모으기) → 내림(타격 = 착지) · 높이는 매 프레임 직접.</summary>
|
||
void OwnTick(Animator an, float t)
|
||
{
|
||
var r = _r; float rise = Mathf.Max(0.05f, r.rise), hang = Mathf.Max(0f, r.hang), fall = Mathf.Max(0.05f, r.fall), land = rise + hang + fall;
|
||
if (!_started) // 첫 프레임 — Play_Skill 이 시전 이벤트 뒤에 스킬 동작(jump)을 틀므로 여기서 내려찍기로 넘긴다
|
||
{
|
||
_started = true;
|
||
_start = _pc.transform.position; _fwd = _pc.transform.forward; _fwd.y = 0f; if (_fwd.sqrMagnitude < 1e-4f) _fwd = Vector3.forward; _fwd.Normalize(); // Play_Skill 이 대상 쪽으로 돌린 뒤
|
||
an.CrossFadeInFixedTime(r.slamState, Mathf.Max(0f, r.crossFade), 0, 0f);
|
||
an.speed = Mathf.Clamp(_hold / rise, 0.05f, 3f);
|
||
WLJumpSlam.Slams++;
|
||
WLJumpSlam.LastLog = "제자리 점프 " + r.slamState + " · 높이 " + r.height.ToString("0.00") + " · 오름 " + rise.ToString("0.00") + " · 멈춤 " + hang.ToString("0.00") + " · 내림 " + fall.ToString("0.00") + " · 회전 " + r.spins + " · 타격 예정 " + land.ToString("0.00") + " s";
|
||
}
|
||
if (!_hung && t >= rise)
|
||
{
|
||
_hung = true; an.speed = 0f;
|
||
if (hang > 0f && r.hangFx != null && !string.IsNullOrEmpty(r.hangFx.prefab))
|
||
{
|
||
var go = WLStaffEarth.SpawnAt(r.hangFx, _pc, _pc.Get_position(), _pc.transform.forward, hang);
|
||
if (go != null) { if (r.hangFx.follow != 0) go.transform.SetParent(_pc.transform, true); WLJumpSlam.HangFxShown++; }
|
||
}
|
||
if (r.hangGlow > 0f) // #1008 무기 흰 빛 덧그림(무기 소켓 = 끝이 가장 먼 소켓)
|
||
{
|
||
Transform sk; Vector3 tl;
|
||
if (WL.Combat.Impact.WLImpactRunner.SocketTip(_pc, out sk, out tl) && _glow.Begin(sk, r.glowMaterial) > 0)
|
||
{
|
||
_glowRule = r; WLJumpSlam.Glows++;
|
||
var gc = r.glowColor; _glowCol = gc != null && gc.Length >= 3 ? new Color(gc[0], gc[1], gc[2], 1f) : Color.white;
|
||
}
|
||
}
|
||
}
|
||
if (_glowRule != null && _glowFadeAt < 0f && t >= rise) GlowSet(r.hangGlow * Mathf.Clamp01((t - rise) / Mathf.Max(0.01f, hang))); // #1008 멈춤 동안 0 → hangGlow · 내림 동안 유지
|
||
if (!_slammed && t >= rise + hang)
|
||
{
|
||
_slammed = true; an.speed = Mathf.Clamp((_impact - _hold) / fall, 0.3f, 4f);
|
||
WLJumpSlam.LastLog += " · 내림 속도 " + an.speed.ToString("0.00");
|
||
}
|
||
float y;
|
||
if (r.arc != 0) y = r.height * Mathf.Sin(Mathf.Clamp01(t / land) * Mathf.PI);
|
||
else if (t < rise) y = r.height * Mathf.Sin(t / rise * Mathf.PI * 0.5f);
|
||
else if (t < rise + hang) y = r.height;
|
||
else { float k = Mathf.Clamp01((t - rise - hang) / fall); y = r.height * (1f - k * k); }
|
||
var p = _pc.transform.position; p.y = _y0 + y;
|
||
if (r.forward != 0f) { var h = _start + _fwd * (r.forward * Mathf.Clamp01(t / land)); p.x = h.x; p.z = h.z; }
|
||
_pc.transform.position = p; if (y > _peak) { _peak = y; WLJumpSlam.LastPeak = y; }
|
||
if (!_landFx && t >= land)
|
||
{
|
||
_landFx = true; var g = new Vector3(p.x, _y0, p.z);
|
||
if (r.landFx != null && !string.IsNullOrEmpty(r.landFx.prefab) && WLStaffEarth.SpawnAt(r.landFx, _pc, g, _pc.transform.forward, 0f) != null) WLJumpSlam.LandFxShown++;
|
||
if (r.landTrace != null && WLStaffEarth.SpawnAt(r.landTrace, _pc, g, _pc.transform.forward, 0f) != null) WLJumpSlam.LandTraces++; // #1006
|
||
GlowFade(); // #1008 타격부터 흰 빛 꺼짐
|
||
}
|
||
if (t >= land + 0.05f) End(an);
|
||
}
|
||
|
||
/// <summary>#998 세로 회전 — 애니메이터가 이번 프레임 자세를 쓴 뒤 엉덩이 뼈(아래 무기 포함)를 몸 중심 · 오른쪽 축으로 돌린다(+ = 머리가 앞으로) · 바퀴마다 검기 1개.</summary>
|
||
void LateUpdate()
|
||
{
|
||
if (!_on || !_own || _r.spins <= 0 || _pc == null) return;
|
||
float dur = Mathf.Max(0.05f, _r.rise + _r.hang), t = Time.time - _t0; if (t > dur + 0.1f) return;
|
||
float ang = 360f * _r.spins * Mathf.SmoothStep(0f, 1f, Mathf.Clamp01(t / dur));
|
||
var root = _pc.transform; Vector3 pivot = root.TransformPoint(_pivotLocal);
|
||
if (_bone != null)
|
||
{
|
||
bool rewritten = !_set || Quaternion.Angle(_bone.rotation, _setRot) > 0.01f || (_bone.position - _setPos).sqrMagnitude > 1e-8f; // 애니메이터가 안 쓴 프레임이면 차이만
|
||
float d = rewritten ? ang : ang - _lastAng;
|
||
if (Mathf.Abs(d) > 0.001f) _bone.RotateAround(pivot, root.right, d);
|
||
_setRot = _bone.rotation; _setPos = _bone.position; _set = true; WLJumpSlam.LastSpinDeg = ang;
|
||
}
|
||
_lastAng = ang;
|
||
if (_spun < _r.spins && ang >= 360f * _spun && _r.spinFx != null && !string.IsNullOrEmpty(_r.spinFx.prefab))
|
||
{
|
||
_spun++;
|
||
var go = WLStaffEarth.SpawnAt(_r.spinFx, _pc, pivot, root.forward, 0f);
|
||
if (go != null) { go.transform.SetParent(root, true); WLJumpSlam.SpinFxShown++; }
|
||
}
|
||
}
|
||
|
||
/// <summary>#998 끊김(구르기 · 사망 · 상태 바뀜) 때 발 높이 · 조작을 되돌린다.</summary>
|
||
void Restore()
|
||
{
|
||
if (_pc == null) return;
|
||
var p = _pc.transform.position; p.y = _y0;
|
||
var agent = _pc.GetComponent<UnityEngine.AI.NavMeshAgent>(); UnityEngine.AI.NavMeshHit hit; // 앞으로 간 경우 에이전트 자리도 맞춘다(원본 Co_JumpAttack 끝의 Snap_toNavMesh 와 같게)
|
||
if (agent != null && agent.enabled && UnityEngine.AI.NavMesh.SamplePosition(p, out hit, 1f, UnityEngine.AI.NavMesh.AllAreas)) agent.Warp(hit.position); else _pc.transform.position = p;
|
||
WLJumpSlam.LastLog += " · 착지 이동 " + Vector3.Distance(new Vector3(_start.x, 0f, _start.z), new Vector3(_pc.transform.position.x, 0f, _pc.transform.position.z)).ToString("0.00") + " m";
|
||
if (OptionInfo.Ins != null) OptionInfo.Ins.Set_PCMove(_pc, true);
|
||
}
|
||
|
||
void End(Animator an)
|
||
{
|
||
if (_on && (_slammed || _own) && an != null) an.speed = 1f;
|
||
if (_on && _own) Restore();
|
||
GlowFade(); // #1008 끊겨도 흰 빛은 서서히
|
||
_on = false;
|
||
}
|
||
|
||
void OnDisable() { if (_on && _own) Restore(); _on = false; _glow.End(); _glowFadeAt = -1f; }
|
||
}
|
||
}
|