90 lines
5.0 KiB
C#
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++;
|
|
}
|
|
}
|
|
}
|