374 lines
23 KiB
C#
374 lines
23 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLRapidShot.cs — 활 주력 「속사」(Skill_RapidShot) WL 방식 (#916 · 2026-10-17)
|
||
//
|
||
// PD 원문: 「활을 쏠 때 플레이어가 뒤로 밀리지 않게」 · 「활의 주력 스킬시전할 때 플레이어의 활이 빛나며 기가 모이는 연출을 추가
|
||
// (해머 각성기 참고하되, 해머보다 임펙트가 너무 강하지는 않도록)」 · 「과녁의 크기가 몬스터의 크기를 고려해 유동적이 되도록 ·
|
||
// (이펙트가 몬스터에게 가려지지 않도록)」 · 「매우 빠르게 활쏘는 모션을 반복해 3발의 투사체를 연속으로 발사」
|
||
// ■ 원본(Skill_RapidShot.Co_Update): 과녁(Effect_BulletTarget · 대상 몸 가운데 · 크기 고정) → 1 초 → 0.25 초 간격 7 발 · 발마다 시전자 넉백
|
||
// (원본 넉백 최소 0.1 초 × 속도 4 = 한 발 약 0.4 m 뒤로 · Play 실측).
|
||
// ■ WL — 원본 Set 의 훅 1줄(Take)이 넘겨받는다:
|
||
// ① 과녁 = 원본 이펙트 그대로 · 대상 중심에서 카메라 쪽으로 몸 반지름 + markFront 앞에(몸에 가려지지 않게) · 배율 = 몹 키 × markPerHeight(markMin~markMax)
|
||
// ② 모으기 chargeSeconds — 활 당긴 자세(shotState 의 drawNormalized)로 멈추고, 활에 흰 빛(WLWeaponGlowFx · glowMax) + 활 가운데 모으기 이펙트(chargeFx)
|
||
// ③ shots 발 — 발마다 shotState 를 (첫 발 drawNormalized · 다음 fromNormalized) 부터 shotSpeed 배로 재생 → 놓는 순간(클립 Projectile 이벤트)에
|
||
// 원본 투사체 1 발(관통 · 원본과 같은 한 발 피해) · 대상을 본다 · 시전자 넉백 없음
|
||
// ④ 끝 — endNormalized 까지 재생 → 애니메이터 되돌림 · 대기 · 빛 glowFadeSeconds 동안 꺼짐 · 투사체 Off. 이동 · 구르기로 스킬 상태가 끊기면 즉시 끝(스킵).
|
||
// ■ noRecoil = 쏠 때 시전자를 뒤로 미는 다른 활 스킬(원본 Set 훅 1줄 · 속박의 화살 Skill_BindingArrow 0.05 초 × 속도 10)
|
||
// ■ 값 = Resources/WL/WLRapidShot.json (C45) · 끄기 = enabled 0 → 원본 동작
|
||
// 🔴 Debug.LogError 금지(원본 팝업).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace WL.Combat.Weapon
|
||
{
|
||
[System.Serializable]
|
||
public class WLRapidShotJson
|
||
{
|
||
public int enabled = 0;
|
||
public int[] classes = new int[0]; // 비우면 모든 직업
|
||
public int shots = 3;
|
||
public string shotState = "attack";
|
||
public string shotClip = "FR_Bow1_10402"; // 놓는 순간 = 이 클립의 Projectile 이벤트
|
||
public float drawNormalized = 0.8f; // 모으기 동안 멈출 자세(활 당김) · 첫 발 시작
|
||
public float fromNormalized = 0.55f; // 둘째 발부터 여기서 재생
|
||
public float shotSpeed = 2.5f; // 발 재생 배속
|
||
public float endNormalized = 0.97f; // 마지막 발 뒤 여기까지 재생하고 대기
|
||
public float chargeSeconds = 0.6f;
|
||
public string socket = "w_archer_l"; // 활 소켓
|
||
public string chargeFx = "";
|
||
public float chargeFxScale = 0.06f;
|
||
public string glowMaterial = "WL/Fx/WL_Mat_WeaponGlow";
|
||
public float[] glowColor = { 1.2f, 1.3f, 1.5f };
|
||
public float glowMax = 0.6f, glowFill = 0.3f, glowRimPower = 2.5f, glowFadeSeconds = 0.2f;
|
||
public float markFront = 0.15f; // 과녁을 몸 반지름 + 이 거리(m)만큼 카메라 쪽 앞에
|
||
public float markPerHeight = 0.35f; // 과녁 배율 = 몹 키(m) × 이 값
|
||
public float markMin = 0.3f, markMax = 2f;
|
||
public float markExtraLife = 0.3f; // 마지막 발 뒤 과녁 유지(초)
|
||
public string[] noRecoil = new string[0]; // 쏠 때 시전자 넉백을 하지 않을 다른 스킬(eEffect 이름)
|
||
// #921 PD 「이펙트 궤적이 타겟 방향으로만 집중해서 날아가도록 · 그 방향으로 화살 기본 공격의 사정거리만큼 직선상 경로상 모든 적에게 피해(레이저 판정)」
|
||
public int laser = 0; // 1 = 발마다 PC → 대상 방향 직선(길이 = 기본 공격 사정거리 · 흰 원) 위 모든 적에게 그 화살의 한 발 피해를 즉시 · 화살은 판정 없이 사정거리까지만 곧게 날아간다
|
||
public float laserWidth = 0.6f; // 직선 판정 폭(m) — 적 몸 반지름은 따로 더한다
|
||
public int aimBody = 1; // 1 = 화살이 대상 몸 중심(공중 몹은 그림 가운데) 쪽으로 곧게
|
||
public int chargeBlocksMove = 0; // #922 PD 「주력 스킬 모으기 이펙트 재생중에는 이동하지 못하게(구르기로 취소는 가능)」 — 1 = 모으기 동안 이동 입력 무시(WLSkillCancel.HoldMove · 구르기 = 스킵)
|
||
public int holdThroughShots = 0; // #922 1 = 막기를 마지막 발까지(모으기만 막으면 이동 키를 누른 채일 때 모으기 직후 이동이 들어와 한 발도 안 쏘고 끊겼다 · Play 실측)
|
||
public float holdMaxSeconds = 3f; // 막기 상한(초) — 끝나면 어떻게든 푼다
|
||
public string note = "";
|
||
}
|
||
|
||
public static class WLRapidShot
|
||
{
|
||
public const string ResourcesPath = "WL/WLRapidShot";
|
||
public static int Taken, Shots, Aborted, RecoilSkipped, Errors, Lasers, LaserHits; public static string LastLog = "";
|
||
static WLRapidShotJson s_cfg;
|
||
|
||
internal static WLRapidShotJson Cfg
|
||
{
|
||
get
|
||
{
|
||
if (s_cfg != null) return s_cfg;
|
||
s_cfg = new WLRapidShotJson();
|
||
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.SubsystemRegistration)] static void Reset() { s_cfg = null; }
|
||
|
||
static int ClassOf(PCActor pc) { return pc != null ? pc.Get_ID() : 0; }
|
||
|
||
/// <summary>원본 Skill_RapidShot.Set 훅 — true 면 WL 이 이 투사체를 맡는다(원본 Co_Update 건너뜀).</summary>
|
||
public static bool Take(ProjectileBase pb, List<ProjectileData> shots, string projectile)
|
||
{
|
||
try
|
||
{
|
||
var c = Cfg;
|
||
if (c.enabled == 0 || pb == null || shots == null || shots.Count == 0) return false;
|
||
var data = pb.Get_ProjectTileData(); var pc = data != null ? data.shooter as PCActor : null;
|
||
if (pc == null || pc.m_animation == null) return false;
|
||
if (c.classes != null && c.classes.Length > 0 && System.Array.IndexOf(c.classes, ClassOf(pc)) < 0) return false;
|
||
var run = pb.GetComponent<WLRapidShotRunner>();
|
||
if (run == null) run = pb.gameObject.AddComponent<WLRapidShotRunner>();
|
||
run.Begin(pb, pc, data.target, shots, projectile, c);
|
||
Taken++;
|
||
return true;
|
||
}
|
||
catch (System.Exception ex) { Errors++; LastLog = "넘겨받기 실패 — " + ex.Message; return false; }
|
||
}
|
||
|
||
/// <summary>원본 활 스킬 Set 훅 — true 면 시전자 넉백(쏠 때 뒤로 밀림)을 하지 않는다.</summary>
|
||
public static bool NoRecoil(Actor shooter, string skill)
|
||
{
|
||
var c = Cfg;
|
||
if (c.enabled == 0 || c.noRecoil == null || System.Array.IndexOf(c.noRecoil, skill) < 0 || !(shooter is PCActor)) return false;
|
||
RecoilSkipped++;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
/// <summary>넘겨받은 속사 투사체 1개의 진행 — 과녁 · 모으기 · 빠른 연사 · 끝.</summary>
|
||
internal sealed class WLRapidShotRunner : MonoBehaviour
|
||
{
|
||
ProjectileBase _pb; PCActor _pc; Actor _target; List<ProjectileData> _shots; string _proj; WLRapidShotJson _c;
|
||
Animator _an; bool _animOwned; Coroutine _co;
|
||
readonly WLWeaponGlowFx _glow = new WLWeaponGlowFx();
|
||
Transform _socket; Vector3 _centerLocal; GameObject _charge;
|
||
static readonly Dictionary<string, GameObject> s_fx = new Dictionary<string, GameObject>();
|
||
|
||
public void Begin(ProjectileBase pb, PCActor pc, Actor target, List<ProjectileData> shots, string proj, WLRapidShotJson c)
|
||
{
|
||
_pb = pb; _pc = pc; _target = target; _shots = shots; _proj = proj; _c = c; _an = pc.m_animation;
|
||
if (_co != null) StopCoroutine(_co);
|
||
_co = StartCoroutine(Run());
|
||
}
|
||
|
||
IEnumerator Run()
|
||
{
|
||
var c = _c; var an = _an;
|
||
float releaseN = ReleaseNormalized(an, c.shotClip);
|
||
if (releaseN <= 0f) { WLRapidShot.LastLog = "놓는 이벤트 없음 " + c.shotClip; releaseN = 0.92f; }
|
||
int n = Mathf.Clamp(c.shots, 1, _shots.Count);
|
||
|
||
// ① 과녁 — 몹 크기에 맞춘 배율 · 카메라 쪽 앞(몸에 가려지지 않게)
|
||
ShowMark(c.chargeSeconds + n * 0.2f + c.markExtraLife);
|
||
|
||
// ② 모으기 — 활 당긴 자세 · 활 흰 빛 · 모으기 이펙트
|
||
an.fireEvents = false; _animOwned = true;
|
||
an.Play(c.shotState, 0, c.drawNormalized); an.speed = 0f; an.Update(0f);
|
||
Face();
|
||
_socket = FindSocket(_pc, c.socket);
|
||
if (_socket != null) { _glow.Begin(_socket, c.glowMaterial); _centerLocal = WLWeaponGlowFx.CenterLocal(_socket); }
|
||
_charge = Spawn(c.chargeFx, BowCenter(), c.chargeFxScale, c.chargeSeconds + 0.5f);
|
||
if (c.chargeBlocksMove != 0) { WLSkillCancel.HoldMove(_pc, c.holdThroughShots != 0 ? Mathf.Max(c.chargeSeconds, c.holdMaxSeconds) : c.chargeSeconds); _holding = true; } // #922 모으기(· 마지막 발까지) 이동 무시(구르기 = 스킵)
|
||
var col = Rgb(c.glowColor, 1f);
|
||
float t = 0f;
|
||
while (t < c.chargeSeconds)
|
||
{
|
||
if (Broken()) { End(false); yield break; }
|
||
if (an.speed != 0f) an.speed = 0f;
|
||
if (!an.GetCurrentAnimatorStateInfo(0).IsName(c.shotState)) an.Play(c.shotState, 0, c.drawNormalized); // 다른 연출이 상태를 바꾸면 되돌림
|
||
float k = Mathf.Clamp01(t / Mathf.Max(0.01f, c.chargeSeconds));
|
||
_glow.Set(c.glowMax * k, col, c.glowFill, c.glowRimPower);
|
||
if (_charge != null) _charge.transform.position = BowCenter();
|
||
t += Time.deltaTime; yield return null;
|
||
}
|
||
_glow.Set(c.glowMax, col, c.glowFill, c.glowRimPower);
|
||
if (_charge != null) { Destroy(_charge); _charge = null; }
|
||
if (_holding && c.holdThroughShots == 0) { _holding = false; WLSkillCancel.EndHoldMove(_pc, true); } // #922 모으기 끝 — 그동안 미룬 이동 입력 적용(마지막 발까지 막을 때는 연사 뒤에)
|
||
|
||
// ③ 빠른 연사 n 발
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
Face();
|
||
float startN = i == 0 ? c.drawNormalized : c.fromNormalized, s0 = Time.time;
|
||
an.Play(c.shotState, 0, startN); an.speed = Mathf.Max(0.1f, c.shotSpeed);
|
||
yield return null;
|
||
while (an.GetCurrentAnimatorStateInfo(0).normalizedTime < releaseN && Time.time - s0 < 1f)
|
||
{
|
||
if (Broken()) { End(false); yield break; }
|
||
if (an.speed != c.shotSpeed) an.speed = c.shotSpeed;
|
||
if (!an.GetCurrentAnimatorStateInfo(0).IsName(c.shotState)) an.Play(c.shotState, 0, startN);
|
||
yield return null;
|
||
}
|
||
var data = _shots[i];
|
||
ProjectileInfo.Ins.Shoot_Projectile(_proj, data, p => { p.m_Pierce = true; WLProjectileVisual.ApplyTo(p, _proj); if (_c.laser != 0) Laser(p); }); // 원본과 같은 한 발 · 시전자 넉백 없음 · #917 겉모습 · 명중 이펙트(WLProjectileVisual 표) · #921 레이저 판정
|
||
WLRapidShot.Shots++;
|
||
}
|
||
|
||
if (_holding) { _holding = false; WLSkillCancel.EndHoldMove(_pc, true); } // #922 마지막 발 — 미룬 이동 입력 적용
|
||
|
||
// ④ 마무리 — 빛 꺼짐 · 끝 자세
|
||
float f0 = Time.time;
|
||
while (an.GetCurrentAnimatorStateInfo(0).normalizedTime < c.endNormalized && Time.time - f0 < 0.5f)
|
||
{
|
||
if (Broken()) { End(false); yield break; }
|
||
float k = 1f - Mathf.Clamp01((Time.time - f0) / Mathf.Max(0.01f, c.glowFadeSeconds));
|
||
_glow.Set(c.glowMax * k, col, c.glowFill, c.glowRimPower);
|
||
yield return null;
|
||
}
|
||
End(true);
|
||
}
|
||
|
||
/// <summary>#921 레이저 판정 — PC 발에서 대상 쪽 수평 방향으로 기본 공격 사정거리(흰 원 WLRangeRing.AttackReach)만큼의 직선 위(폭 laserWidth + 몸 반지름) 모든 적에게
|
||
/// 이 화살의 원본 피해 경로(ProcessCollider · 끄지 않음)를 즉시 · 화살은 대상 몸 중심 쪽으로 곧게 사정거리까지만 날고 자기 판정은 끈다(이중 피해 방지).</summary>
|
||
void Laser(ProjectileBase p)
|
||
{
|
||
var c = _c; if (p == null || _pc == null) return;
|
||
float range = WL.Combat.RangeRing.WLRangeRing.AttackReach(_pc);
|
||
if (range <= 0f) return;
|
||
Vector3 origin = _pc.Get_position();
|
||
Vector3 dir = _pc.transform.forward; dir.y = 0f;
|
||
bool hasTarget = !DSUtil.CheckNull(_target) && !_target.IsDead();
|
||
if (hasTarget) { var to = _target.Get_position() - origin; to.y = 0f; if (to.sqrMagnitude > 1e-4f) dir = to; }
|
||
if (dir.sqrMagnitude < 1e-6f) return;
|
||
dir.Normalize();
|
||
// ① 겉모습 — 대상 몸 중심 쪽으로 곧게 · 사정거리 끝에서 사라짐
|
||
Vector3 look = dir;
|
||
if (c.aimBody != 0 && hasTarget)
|
||
{
|
||
var v = WL.Combat.Impact.WLImpactFx.BodyCenter(_target) - p.transform.position; var vf = v; vf.y = 0f;
|
||
if (vf.sqrMagnitude > 0.01f) look = v;
|
||
}
|
||
p.transform.rotation = Quaternion.LookRotation(look);
|
||
var pd = p.Get_ProjectTileData();
|
||
var start = p.transform.position - origin; start.y = 0f;
|
||
if (pd != null && pd.Speed > 0f) pd.LifeTime = Mathf.Max(0.05f, (range - Vector3.Dot(start, dir)) / pd.Speed);
|
||
// ② 판정 — 직선 위 모든 적
|
||
int hits = 0; var info = ActorInfo.Ins;
|
||
var list = info != null ? info.Get_EnemyActors(_pc.IsEnemy(), origin, range + 2f) : null;
|
||
if (list != null)
|
||
{
|
||
for (int k = 0; k < list.Count; k++)
|
||
{
|
||
var a = list[k];
|
||
if (a == null || a.IsDead()) continue;
|
||
var v = a.Get_position() - origin; v.y = 0f;
|
||
float body = WL.Combat.WLCombatMotionSettings.BodyRadius(a), t = Vector3.Dot(v, dir);
|
||
if (t < -body || t > range + body) continue;
|
||
if ((v - dir * t).magnitude > c.laserWidth * 0.5f + body) continue;
|
||
var col = a.GetComponent<Collider>();
|
||
if (col == null || !col.enabled) continue;
|
||
if (p.ProcessCollider(col, false)) hits++;
|
||
}
|
||
info.Release_Actors(list);
|
||
}
|
||
WLLaserMute.Mute(p);
|
||
WLRapidShot.LaserHits += hits; WLRapidShot.Lasers++;
|
||
WLRapidShot.LastLog = "레이저 " + range.ToString("F2") + " m · 맞음 " + hits;
|
||
}
|
||
|
||
bool Broken() { return _pc == null || _pc.IsDead() || _pc.Get_CurAnim() != eAnim.Skill; }
|
||
|
||
bool _holding;
|
||
|
||
void End(bool toIdle)
|
||
{
|
||
if (_holding) { _holding = false; WLSkillCancel.EndHoldMove(_pc, toIdle); }
|
||
if (!toIdle) { WLRapidShot.Aborted++; WLRapidShot.LastLog = "끊김(이동 · 구르기)"; }
|
||
if (_animOwned && _an != null) { _an.speed = 1f; _an.fireEvents = true; }
|
||
_animOwned = false; _co = null;
|
||
if (toIdle && _pc != null && !_pc.IsDead() && _pc.Get_CurAnim() == eAnim.Skill) _pc.Play_Idle();
|
||
_glow.End();
|
||
if (_charge != null) { Destroy(_charge); _charge = null; }
|
||
WLSlashFlurry.Off(_pb);
|
||
}
|
||
|
||
void OnDisable()
|
||
{
|
||
if (_holding) { _holding = false; WLSkillCancel.EndHoldMove(_pc, false); }
|
||
if (_co != null) { StopCoroutine(_co); _co = null; }
|
||
if (_animOwned && _an != null) { _an.speed = 1f; _an.fireEvents = true; _animOwned = false; }
|
||
_glow.End();
|
||
if (_charge != null) { Destroy(_charge); _charge = null; }
|
||
}
|
||
|
||
void Face()
|
||
{
|
||
if (_pc == null || DSUtil.CheckNull(_target) || _target.IsDead()) return;
|
||
var to = _target.Get_position() - _pc.Get_position(); to.y = 0f;
|
||
if (to.sqrMagnitude > 1e-4f) _pc.transform.rotation = Quaternion.LookRotation(to);
|
||
}
|
||
|
||
Vector3 BowCenter()
|
||
{
|
||
if (_socket != null) return _socket.TransformPoint(_centerLocal);
|
||
return _pc != null ? _pc.Get_Center_position() : transform.position;
|
||
}
|
||
|
||
void ShowMark(float life)
|
||
{
|
||
if (DSUtil.CheckNull(_target) || InGameInfo.Ins == null) return;
|
||
var c = _c; var target = _target;
|
||
float h = Mathf.Max(0.1f, target.Get_Top_postion().y - target.Get_position().y);
|
||
float scale = Mathf.Clamp(h * c.markPerHeight, c.markMin, c.markMax);
|
||
InGameInfo.Ins.Show_Effect("Effect_BulletTarget", target, eEffectLocation.Mid, life, off =>
|
||
{
|
||
if (off == null) return;
|
||
var fit = off.GetComponent<WLMarkFit>(); if (fit == null) fit = off.gameObject.AddComponent<WLMarkFit>();
|
||
fit.Begin(target, c.markFront, scale, life);
|
||
});
|
||
}
|
||
|
||
static Transform FindSocket(PCActor pc, string name)
|
||
{
|
||
if (pc == null || pc.tfs_weapon == null) return null;
|
||
foreach (var s in pc.tfs_weapon) if (s != null && s.name == name && s.gameObject.activeInHierarchy) return s;
|
||
return null;
|
||
}
|
||
|
||
static float ReleaseNormalized(Animator an, string clipName)
|
||
{
|
||
if (an == null || an.runtimeAnimatorController == null) return -1f;
|
||
foreach (var clip in an.runtimeAnimatorController.animationClips)
|
||
{
|
||
if (clip == null || clip.name != clipName || clip.length <= 0f) continue;
|
||
foreach (var ev in clip.events) if (ev.functionName == "Projectile") return ev.time / clip.length;
|
||
}
|
||
return -1f;
|
||
}
|
||
|
||
static GameObject Spawn(string path, Vector3 pos, float scale, float life)
|
||
{
|
||
if (string.IsNullOrEmpty(path)) return null;
|
||
GameObject prefab;
|
||
if (!s_fx.TryGetValue(path, out prefab)) { prefab = Resources.Load<GameObject>(path); s_fx[path] = prefab; }
|
||
if (prefab == null) { WLRapidShot.LastLog = "이펙트 없음 " + path; return null; }
|
||
var go = Instantiate(prefab, pos, prefab.transform.rotation);
|
||
go.transform.localScale = prefab.transform.localScale * Mathf.Max(0.01f, scale);
|
||
Destroy(go, Mathf.Max(0.1f, life));
|
||
return go;
|
||
}
|
||
|
||
static Color Rgb(float[] a, float d) { return new Color(a != null && a.Length > 0 ? a[0] : d, a != null && a.Length > 1 ? a[1] : d, a != null && a.Length > 2 ? a[2] : d, 1f); }
|
||
}
|
||
|
||
/// <summary>#916 과녁 맞춤 — 원본 TurnOff_GO 가 Update 에서 대상 가운데로 옮긴 뒤(LateUpdate) 카메라 쪽 앞으로 당기고 몹 크기 배율을 준다. 기간이 지나면 원래 배율.</summary>
|
||
public sealed class WLMarkFit : MonoBehaviour
|
||
{
|
||
Actor _target; float _front, _scale = 1f, _until; bool _on; Vector3 _scale0 = Vector3.one; bool _hasScale0;
|
||
|
||
public void Begin(Actor target, float front, float scale, float life)
|
||
{
|
||
if (!_hasScale0) { _scale0 = transform.localScale; _hasScale0 = true; }
|
||
_target = target; _front = front; _scale = scale; _until = Time.time + life; _on = true;
|
||
foreach (var ps in GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; }
|
||
Apply();
|
||
}
|
||
|
||
void LateUpdate() { if (_on) Apply(); }
|
||
|
||
void Apply()
|
||
{
|
||
if (Time.time > _until || DSUtil.CheckNull(_target) || _target.IsDead()) { Stop(); return; }
|
||
var mid = WL.Combat.Impact.WLImpactFx.BodyCenter(_target); // #921 PD 「몸 정중앙 · 공중 몬스터라도」 — 원본 Center_position(등장 때 잰 고정 높이) 대신 지금 몸 중심(떠 있는 몹 = 지금 그림 가운데)
|
||
var cam = Camera.main;
|
||
var toCam = cam != null ? (cam.transform.position - mid).normalized : Vector3.up;
|
||
transform.position = mid + toCam * (WL.Combat.WLCombatMotionSettings.BodyRadius(_target) + _front);
|
||
transform.localScale = _scale0 * _scale;
|
||
}
|
||
|
||
void Stop() { _on = false; if (_hasScale0) transform.localScale = _scale0; }
|
||
void OnDisable() { Stop(); }
|
||
}
|
||
|
||
/// <summary>#921 레이저로 피해를 준 속사 화살 — 날아가는 동안 자기 판정을 끈다(이중 피해 방지) · 풀로 돌아갈 때(꺼질 때) 되살린다.</summary>
|
||
public sealed class WLLaserMute : MonoBehaviour
|
||
{
|
||
Collider[] _cols;
|
||
public static void Mute(ProjectileBase p)
|
||
{
|
||
if (p == null) return;
|
||
var m = p.GetComponent<WLLaserMute>(); if (m == null) m = p.gameObject.AddComponent<WLLaserMute>();
|
||
if (m._cols == null) m._cols = p.GetComponentsInChildren<Collider>(true);
|
||
for (int i = 0; i < m._cols.Length; i++) if (m._cols[i] != null) m._cols[i].enabled = false;
|
||
m.enabled = true;
|
||
}
|
||
void OnDisable()
|
||
{
|
||
if (_cols != null) for (int i = 0; i < _cols.Length; i++) if (_cols[i] != null) _cols[i].enabled = true;
|
||
enabled = false;
|
||
}
|
||
}
|
||
}
|