using CodeStage.AntiCheat.ObscuredTypes; using UnityEngine; public class Projectile : MonoBehaviour { public float m_Speed = 12f; public float radius = 0.1f; Vector2 dir; Vector2 prevPos; ObscuredInt m_bounceCount; 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; transform.SetPositionAndRotation(m_ProjectileData.tf_Start.position, m_ProjectileData.tf_Start.rotation); if (!m_ProjectileData.IsPC) transform.eulerAngles = new Vector3(0f, 0f, 180f); dir = transform.up.normalized; m_bounceCount = m_ProjectileData.m_Data.n_AttackBounceLimit; 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_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); break; case "MobShield": collision.GetComponent().Get_Dmg(); break; } } else { if (collision.tag == "Wall_HP") { IngameMgr.Ins.Get_Dmg(m_ProjectileData.Dmg); } } } void Projectile_Split(bool hitEnemy) { if (hitEnemy) { for (int i = 0; i < 2; i++) { //ProjectileMgr.Ins.Get(transform.position, Quaternion.Euler(0, 0, Random.Range(-20, 20)) * dir); } } } } 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(); } } }