Project_WL/Assets/WL/Combat/WLAttackFacing.cs

90 lines
5.0 KiB
C#

// ─────────────────────────────────────────────────────────────────────────────
// WLAttackFacing.cs — 공격할 때는 대상 쪽으로 돌아선 뒤 쏜다/벤다 (#923 · 2026-10-18)
//
// PD 원문: 「구르기 직 후 캐릭터가 바라보는 방향과 반대 방향으로 활을 쏘는 경우가 있는데 활을 쏘거나 무기로 공격할 때는
// 반드시 바라보는 방향으로 전환한 후 공격하도록 해줘.」
// ■ 원본 Actor.Play_Attack 은 공격 시작 때 한 번 대상을 본다(LookAt) — 그 뒤 구르기 끝 회전 등이 몸을 돌려 놓으면
// 화살은 대상(뒤)으로 날아가는데 몸은 반대를 본 채 쏘았다.
// ■ ① 기본 공격(CurAnim Attack ~ Attack4) 동안 매 프레임 대상 쪽으로 빠르게 돈다(turnDegPerSec · 수평)
// ② 판정 · 투사체가 나가는 순간(CombatEvents.HitboxSpawned = 원본 Shoot_Projectile 첫 줄 · 투사체 생성 전) 대상 쪽으로 바로 맞춘다
// → 투사체 시작 위치(몸 앞) · 근접 판정 상자도 대상 쪽에서 나온다.
// ■ 값 = Resources/WL/WLAttackFacing.json (C45) · 끄기 = enabled 0 · 내 PC 만(몹 · 펫 무관)
// 🔴 원본 수정 0줄 · Debug.LogError 금지.
// ─────────────────────────────────────────────────────────────────────────────
using UnityEngine;
using WL.Combat.Core;
namespace WL.Combat
{
[System.Serializable]
public class WLAttackFacingJson
{
public int enabled = 1;
public float turnDegPerSec = 1440f; // 공격 동작 중 대상 쪽으로 도는 속도(도/초)
public int snapAtHitbox = 1; // 1 = 판정 · 투사체가 나가는 순간 바로 대상 쪽으로
public float minDistance = 0.05f; // 대상이 이보다 가까우면(겹침) 돌지 않는다(m)
public string note = "";
}
public sealed class WLAttackFacing : MonoBehaviour
{
public const string ResourcesPath = "WL/WLAttackFacing";
public static int Snaps, Turns; public static float LastSnapDeg;
static WLAttackFacingJson s_cfg; static WLAttackFacing s_inst;
static WLAttackFacingJson Cfg
{
get
{
if (s_cfg != null) return s_cfg;
s_cfg = new WLAttackFacingJson();
var ta = Resources.Load<TextAsset>(ResourcesPath);
if (ta != null) { try { JsonUtility.FromJsonOverwrite(ta.text, s_cfg); } catch (System.Exception) { s_cfg.enabled = 0; } }
return s_cfg;
}
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
static void Boot()
{
s_cfg = null; Snaps = Turns = 0;
if (Cfg.enabled == 0 || s_inst != null) return;
var go = new GameObject("WL_AttackFacing"); DontDestroyOnLoad(go); s_inst = go.AddComponent<WLAttackFacing>();
CombatEvents.HitboxSpawned.Remove(OnHitbox); CombatEvents.HitboxSpawned.Add(OnHitbox);
}
void OnDestroy() { if (s_inst == this) { CombatEvents.HitboxSpawned.Remove(OnHitbox); s_inst = null; } }
static bool Dir(PCActor pc, out Vector3 dir)
{
dir = Vector3.zero;
if (pc == null || pc.IsDead() || !pc.isTarget) return false;
var t = pc.Get_Target(); if (DSUtil.CheckNull(t) || t.IsDead()) return false;
dir = t.Get_position() - pc.Get_position(); dir.y = 0f;
return dir.magnitude > Cfg.minDistance;
}
static void OnHitbox(in HitboxSpawnedEvent e)
{
var c = Cfg; if (c.enabled == 0 || c.snapAtHitbox == 0) return;
var pc = e.shooter as PCActor; if (pc == null || !ReferenceEquals(pc, MyValue.MyPC)) return;
var cur = pc.Get_CurAnim(); if (cur < eAnim.Attack || cur >= eAnim.Skill) return; // 기본 공격만(스킬은 각자 방향을 정한다)
Vector3 dir; if (!Dir(pc, out dir)) return;
var want = Quaternion.LookRotation(dir.normalized);
LastSnapDeg = Quaternion.Angle(pc.transform.rotation, want);
pc.transform.rotation = want; Snaps++;
}
void LateUpdate()
{
var c = Cfg; if (c.enabled == 0) return;
var pc = MyValue.MyPC as PCActor; if (pc == null) return;
var cur = pc.Get_CurAnim(); if (cur < eAnim.Attack || cur >= eAnim.Skill) return;
Vector3 dir; if (!Dir(pc, out dir)) return;
var want = Quaternion.LookRotation(dir.normalized);
if (Quaternion.Angle(pc.transform.rotation, want) < 0.5f) return;
pc.transform.rotation = Quaternion.RotateTowards(pc.transform.rotation, want, c.turnDegPerSec * Time.deltaTime); Turns++;
}
}
}