using CodeStage.AntiCheat.ObscuredTypes; using System.Collections.Generic; using UnityEngine; public class Projectile : MonoBehaviour { public float m_Speed = 12f; public float radius = 0.1f; Vector2 dir; Vector2 prevPos; ObscuredInt m_bounceCount, m_piereceCount; ProjectileData m_ProjectileData; #region 투사체 매니저 ProjectileMgr owner; string prefabKey; public void SetOwner(ProjectileMgr mgr, string key) { owner = mgr; prefabKey = key; } public string GetPoolKey() { return prefabKey; } void Kill() { owner.Return(this); // 이펙트 표시 필요 } public ProjectileTableData Get_ProjectileTData() { return m_ProjectileData.m_Data; } #endregion public void Set(ProjectileData pd) { m_ProjectileData = pd; Vector3 pos = pd.tf_Start.position; Quaternion rot = pd.tf_Start.rotation; /* ============================= * 직선 화살 위치 분산 * ============================= */ if (pd.PreviousArrow > 0) { int count = pd.PreviousArrow + 1; float spacing = 0.5f; float offset = (pd.LineIndex - (count - 1) * 0.5f) * spacing; pos += pd.tf_Start.right * offset; } transform.SetPositionAndRotation(pos, rot); /* ============================= * 사선 화살 각도 분산 * ============================= */ float angle = 0f; if (pd.DiagonalArrow > 0 && pd.DiagIndex > 0) { int n = pd.DiagonalArrow; float step = 90f / (n + 1); int order = pd.DiagIndex; float offset = step * order; bool right = (order % 2) == 1; angle = right ? offset : -offset; } transform.rotation = Quaternion.AngleAxis(angle, transform.forward) * transform.rotation; if (!pd.IsPC) transform.eulerAngles += new Vector3(0f, 0f, 180f); dir = transform.up.normalized; m_bounceCount = m_ProjectileData.m_Data.n_AttackBounceLimit; if (pd.IsPC) m_bounceCount += (int)IngameMgr.Ins.Get_SkillValue(eSkillType.Reflect); m_bounceCount.RandomizeCryptoKey(); } void Update() { prevPos = transform.position; float moveDist = m_Speed * Time.deltaTime; transform.position += (Vector3)(dir * moveDist); } private void OnTriggerEnter2D(Collider2D collision) { if (!m_ProjectileData.IsPC && collision.tag == "Mob") return; Check_Hit(collision); if (m_piereceCount > 0) { --m_piereceCount; m_piereceCount.RandomizeCryptoKey(); return; } if (m_bounceCount <= 0) { Kill(); return; } Vector2 currentPos = transform.position; Vector2 moveVec = currentPos - prevPos; float distance = moveVec.magnitude; Vector2 moveDir = distance > 0 ? moveVec / distance : dir; // CircleCast로 정확한 충돌 지점(centroid)과 법선 탐색 RaycastHit2D[] hits = Physics2D.CircleCastAll(prevPos, radius, moveDir, distance); RaycastHit2D hit = new RaycastHit2D(); bool found = false; foreach (var h in hits) { if (h.collider == collision) { // 자기 자신(시작 위치에서 걸리는 경우) 제외 로직이 필요할 수 있으나, // distance > 0 체크 등으로 보완. 여기서는 가장 먼저 닿는 지점을 찾음. hit = h; found = true; break; } } Vector2 normal; Vector2 targetPos; if (found) { normal = hit.normal; // CircleCast의 centroid는 충돌 시의 원의 중심 위치입니다. targetPos = hit.centroid; } else { // fallback: ClosestPoint Vector2 closestRef = collision.ClosestPoint(prevPos); normal = (prevPos - closestRef).normalized; if (normal == Vector2.zero) { normal = (prevPos - (Vector2)collision.bounds.center).normalized; } targetPos = closestRef + normal * radius; } // 반사 dir = Vector2.Reflect(dir, normal).normalized; m_bounceCount--; m_bounceCount.RandomizeCryptoKey(); // 회전 float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; transform.rotation = Quaternion.Euler(0, 0, angle); // 위치 보정 transform.position = targetPos; } void Check_Hit(Collider2D collision) { if (m_ProjectileData.IsPC) { switch(collision.tag) { case "Mob": collision.GetComponent().Get_Dmg(m_ProjectileData); Spawn_LeftRight(collision.transform); Spawn_UpDown(collision.transform); Spawn_Bounce(collision.GetComponent()); break; case "MobShield": collision.GetComponent().Get_Dmg(); break; } } else { if (collision.tag == "Wall_HP") { IngameMgr.Ins.Get_Dmg(m_ProjectileData.Dmg); } } } public void SetDirection(Vector2 direction) { dir = direction.normalized; // 방향에 맞게 회전도 맞춰줌 float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f; transform.rotation = Quaternion.Euler(0, 0, angle); } List GetOffsets(int count, float spacing) { List offsets = new List(); float start = -(count - 1) * 0.5f; for (int i = 0; i < count; i++) offsets.Add((start + i) * spacing); return offsets; } void SpawnExtra(Vector3 pos, Vector3 dir) { var p = ProjectileMgr.Ins.Get(m_ProjectileData.m_Data.s_ProjectilePrefabs); p.Set(new ProjectileData { IsPC = m_ProjectileData.IsPC, m_Data = m_ProjectileData.m_Data, tf_Start = transform, Dmg = m_ProjectileData.Dmg, ArrowLeftRight = 0, ArrowUpDown = 0, Bounce = m_ProjectileData.Bounce }); p.transform.position = pos; p.SetDirection(dir); } void Spawn_LeftRight(Transform tf) { if (m_ProjectileData.ArrowLeftRight <= 0) return; Vector3 hitPos = tf.position; float spacing = 0.5f; int count = m_ProjectileData.ArrowLeftRight; var offsets = GetOffsets(count, spacing); foreach (float offset in offsets) { // ▶ 오른쪽 SpawnExtra(hitPos + Vector3.up * (offset + 0.5f), Vector2.right); // ◀ 왼쪽 SpawnExtra(hitPos + Vector3.up * (offset + 0.5f), Vector2.left); } } void Spawn_UpDown(Transform tf) { if (m_ProjectileData.ArrowUpDown <= 0) return; Vector3 hitPos = tf.position; float spacing = 1.5f; int count = m_ProjectileData.ArrowLeftRight; var offsets = GetOffsets(count, spacing); foreach (float offset in offsets) { // ▲ 위 SpawnExtra(hitPos + Vector3.right * offset, Vector2.up); // ▼ 아래 SpawnExtra(hitPos + Vector3.right * offset, Vector2.down); } } void Spawn_Bounce(MobActor mob) { if (m_ProjectileData.Bounce <= 0 || mob == null) return; MobActor target = IngameMgr.Ins.FindNearestMob(mob.transform.position, mob); if (target == null) { Kill(); return; } Vector3 dir = (target.transform.position - mob.transform.position).normalized; SetDirection(dir); --m_ProjectileData.Bounce; } } public class ProjectileData { public ProjectileTableData m_Data; public Transform tf_Start; ObscuredBool _IsPC; public bool IsPC { get { return _IsPC; } set { _IsPC = value; _IsPC.RandomizeCryptoKey(); } } ObscuredInt _Lv; public int Lv { get { return _Lv; } set { _Lv = value; _Lv.RandomizeCryptoKey(); } } ObscuredInt _Dmg; public int Dmg { get { return _Dmg; } set { _Dmg = value; _Dmg.RandomizeCryptoKey(); } } // 발사 계열 public int PreviousArrow; public int DiagonalArrow; public int LineIndex; public int DiagIndex; // 피격 계열 public int ArrowLeftRight; public int ArrowUpDown; public int Bounce; }