2026-01-13 00:46:52 +00:00
|
|
|
using CodeStage.AntiCheat.ObscuredTypes;
|
2026-01-14 22:48:21 +00:00
|
|
|
using System.Collections.Generic;
|
2026-01-11 23:53:38 +00:00
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
public class Projectile : MonoBehaviour
|
|
|
|
|
{
|
2026-01-12 03:05:09 +00:00
|
|
|
public float m_Speed = 12f;
|
|
|
|
|
public float radius = 0.1f;
|
2026-01-11 23:53:38 +00:00
|
|
|
|
|
|
|
|
Vector2 dir;
|
2026-01-12 03:05:09 +00:00
|
|
|
Vector2 prevPos;
|
2026-01-14 22:48:21 +00:00
|
|
|
ObscuredInt m_bounceCount, m_piereceCount;
|
2026-01-13 00:46:52 +00:00
|
|
|
|
2026-01-13 06:20:15 +00:00
|
|
|
ProjectileData m_ProjectileData;
|
2026-01-11 23:53:38 +00:00
|
|
|
|
2026-01-12 03:23:48 +00:00
|
|
|
#region 투사체 매니저
|
|
|
|
|
ProjectileMgr owner;
|
2026-01-13 00:46:52 +00:00
|
|
|
string prefabKey;
|
|
|
|
|
|
|
|
|
|
public void SetOwner(ProjectileMgr mgr, string key)
|
|
|
|
|
{
|
|
|
|
|
owner = mgr;
|
|
|
|
|
prefabKey = key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetPoolKey() { return prefabKey; }
|
|
|
|
|
void Kill()
|
|
|
|
|
{
|
|
|
|
|
owner.Return(this);
|
|
|
|
|
// 이펙트 표시 필요
|
|
|
|
|
}
|
2026-01-13 06:20:15 +00:00
|
|
|
public ProjectileTableData Get_ProjectileTData() { return m_ProjectileData.m_Data; }
|
2026-01-12 03:23:48 +00:00
|
|
|
#endregion
|
2026-01-12 03:05:09 +00:00
|
|
|
|
2026-01-13 06:20:15 +00:00
|
|
|
public void Set(ProjectileData pd)
|
2026-01-11 23:53:38 +00:00
|
|
|
{
|
2026-01-13 06:20:15 +00:00
|
|
|
m_ProjectileData = pd;
|
2026-01-14 21:45:26 +00:00
|
|
|
|
2026-01-14 22:48:21 +00:00
|
|
|
Vector3 pos = pd.tf_Start.position;
|
|
|
|
|
Quaternion rot = pd.tf_Start.rotation;
|
2026-01-14 21:45:26 +00:00
|
|
|
|
2026-01-14 22:48:21 +00:00
|
|
|
/* =============================
|
|
|
|
|
* 직선 화살 위치 분산
|
|
|
|
|
* ============================= */
|
|
|
|
|
if (pd.PreviousArrow > 0)
|
|
|
|
|
{
|
|
|
|
|
int count = pd.PreviousArrow + 1;
|
2026-01-14 21:45:26 +00:00
|
|
|
float spacing = 0.5f;
|
2026-01-14 22:48:21 +00:00
|
|
|
float offset = (pd.LineIndex - (count - 1) * 0.5f) * spacing;
|
|
|
|
|
|
|
|
|
|
pos += pd.tf_Start.right * offset;
|
|
|
|
|
}
|
2026-01-14 21:45:26 +00:00
|
|
|
|
2026-01-14 22:48:21 +00:00
|
|
|
transform.SetPositionAndRotation(pos, rot);
|
2026-01-14 21:45:26 +00:00
|
|
|
|
2026-01-14 22:48:21 +00:00
|
|
|
/* =============================
|
|
|
|
|
* 사선 화살 각도 분산
|
|
|
|
|
* ============================= */
|
|
|
|
|
float angle = 0f;
|
2026-01-14 21:45:26 +00:00
|
|
|
|
2026-01-14 22:48:21 +00:00
|
|
|
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;
|
2026-01-14 21:45:26 +00:00
|
|
|
}
|
2026-01-13 06:20:15 +00:00
|
|
|
|
2026-01-14 22:48:21 +00:00
|
|
|
transform.rotation = Quaternion.AngleAxis(angle, transform.forward) * transform.rotation;
|
|
|
|
|
|
|
|
|
|
if (!pd.IsPC)
|
|
|
|
|
transform.eulerAngles += new Vector3(0f, 0f, 180f);
|
2026-01-13 06:20:15 +00:00
|
|
|
|
2026-01-11 23:53:38 +00:00
|
|
|
dir = transform.up.normalized;
|
2026-01-13 00:46:52 +00:00
|
|
|
|
2026-01-13 06:20:15 +00:00
|
|
|
m_bounceCount = m_ProjectileData.m_Data.n_AttackBounceLimit;
|
2026-01-14 22:48:21 +00:00
|
|
|
if (pd.IsPC)
|
|
|
|
|
m_bounceCount += (int)IngameMgr.Ins.Get_SkillValue(eSkillType.Reflect);
|
|
|
|
|
|
2026-01-13 00:46:52 +00:00
|
|
|
m_bounceCount.RandomizeCryptoKey();
|
2026-01-11 23:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Update()
|
|
|
|
|
{
|
2026-01-12 03:05:09 +00:00
|
|
|
prevPos = transform.position;
|
2026-01-11 23:53:38 +00:00
|
|
|
|
2026-01-12 03:05:09 +00:00
|
|
|
float moveDist = m_Speed * Time.deltaTime;
|
|
|
|
|
transform.position += (Vector3)(dir * moveDist);
|
|
|
|
|
}
|
2026-01-11 23:53:38 +00:00
|
|
|
|
2026-01-12 03:05:09 +00:00
|
|
|
private void OnTriggerEnter2D(Collider2D collision)
|
|
|
|
|
{
|
2026-01-13 06:20:15 +00:00
|
|
|
if (!m_ProjectileData.IsPC && collision.tag == "Mob") return;
|
|
|
|
|
|
2026-01-13 07:33:06 +00:00
|
|
|
Check_Hit(collision);
|
|
|
|
|
|
2026-01-14 22:48:21 +00:00
|
|
|
if (m_piereceCount > 0)
|
|
|
|
|
{
|
|
|
|
|
--m_piereceCount;
|
|
|
|
|
m_piereceCount.RandomizeCryptoKey();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 00:46:52 +00:00
|
|
|
if (m_bounceCount <= 0)
|
2026-01-11 23:53:38 +00:00
|
|
|
{
|
2026-01-12 03:23:48 +00:00
|
|
|
Kill();
|
2026-01-12 03:05:09 +00:00
|
|
|
return;
|
2026-01-11 23:53:38 +00:00
|
|
|
}
|
2026-01-12 03:05:09 +00:00
|
|
|
|
|
|
|
|
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)
|
2026-01-11 23:53:38 +00:00
|
|
|
{
|
2026-01-12 03:05:09 +00:00
|
|
|
if (h.collider == collision)
|
|
|
|
|
{
|
|
|
|
|
// 자기 자신(시작 위치에서 걸리는 경우) 제외 로직이 필요할 수 있으나,
|
|
|
|
|
// distance > 0 체크 등으로 보완. 여기서는 가장 먼저 닿는 지점을 찾음.
|
|
|
|
|
hit = h;
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-01-11 23:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-12 03:05:09 +00:00
|
|
|
Vector2 normal;
|
|
|
|
|
Vector2 targetPos;
|
2026-01-11 23:53:38 +00:00
|
|
|
|
2026-01-12 03:05:09 +00:00
|
|
|
if (found)
|
2026-01-11 23:53:38 +00:00
|
|
|
{
|
2026-01-12 03:05:09 +00:00
|
|
|
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;
|
2026-01-11 23:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-12 03:05:09 +00:00
|
|
|
// 반사
|
2026-01-11 23:53:38 +00:00
|
|
|
dir = Vector2.Reflect(dir, normal).normalized;
|
2026-01-13 00:46:52 +00:00
|
|
|
m_bounceCount--;
|
|
|
|
|
m_bounceCount.RandomizeCryptoKey();
|
2026-01-11 23:53:38 +00:00
|
|
|
|
2026-01-12 03:05:09 +00:00
|
|
|
// 회전
|
2026-01-11 23:53:38 +00:00
|
|
|
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90f;
|
|
|
|
|
transform.rotation = Quaternion.Euler(0, 0, angle);
|
2026-01-12 03:05:09 +00:00
|
|
|
|
|
|
|
|
// 위치 보정
|
|
|
|
|
transform.position = targetPos;
|
2026-01-13 07:33:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Check_Hit(Collider2D collision)
|
|
|
|
|
{
|
|
|
|
|
if (m_ProjectileData.IsPC)
|
|
|
|
|
{
|
2026-01-13 22:18:11 +00:00
|
|
|
switch(collision.tag)
|
2026-01-13 07:33:06 +00:00
|
|
|
{
|
2026-01-14 22:48:21 +00:00
|
|
|
case "Mob":
|
|
|
|
|
collision.GetComponent<MobActor>().Get_Dmg(m_ProjectileData);
|
|
|
|
|
Spawn_LeftRight(collision.transform);
|
|
|
|
|
Spawn_UpDown(collision.transform);
|
|
|
|
|
Spawn_Bounce(collision.GetComponent<MobActor>());
|
|
|
|
|
break;
|
2026-01-13 22:18:11 +00:00
|
|
|
case "MobShield": collision.GetComponent<MobShield>().Get_Dmg(); break;
|
2026-01-13 07:33:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (collision.tag == "Wall_HP")
|
|
|
|
|
{
|
|
|
|
|
IngameMgr.Ins.Get_Dmg(m_ProjectileData.Dmg);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-11 23:53:38 +00:00
|
|
|
}
|
2026-01-14 22:48:21 +00:00
|
|
|
|
|
|
|
|
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<float> GetOffsets(int count, float spacing)
|
|
|
|
|
{
|
|
|
|
|
List<float> offsets = new List<float>();
|
|
|
|
|
|
|
|
|
|
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,
|
2026-01-14 23:00:10 +00:00
|
|
|
Bounce = m_ProjectileData.Bounce
|
2026-01-14 22:48:21 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
p.transform.position = pos;
|
|
|
|
|
p.SetDirection(dir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Spawn_LeftRight(Transform tf)
|
|
|
|
|
{
|
|
|
|
|
if (m_ProjectileData.ArrowLeftRight <= 0) return;
|
|
|
|
|
|
|
|
|
|
Vector3 hitPos = tf.position;
|
2026-01-14 23:00:10 +00:00
|
|
|
float spacing = 0.5f;
|
2026-01-14 22:48:21 +00:00
|
|
|
int count = m_ProjectileData.ArrowLeftRight;
|
|
|
|
|
var offsets = GetOffsets(count, spacing);
|
|
|
|
|
|
|
|
|
|
foreach (float offset in offsets)
|
|
|
|
|
{
|
|
|
|
|
// ▶ 오른쪽
|
2026-01-14 23:00:10 +00:00
|
|
|
SpawnExtra(hitPos + Vector3.up * (offset + 0.5f), Vector2.right);
|
2026-01-14 22:48:21 +00:00
|
|
|
// ◀ 왼쪽
|
2026-01-14 23:00:10 +00:00
|
|
|
SpawnExtra(hitPos + Vector3.up * (offset + 0.5f), Vector2.left);
|
2026-01-14 22:48:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
// ▲ 위
|
2026-01-14 23:00:10 +00:00
|
|
|
SpawnExtra(hitPos + Vector3.right * offset, Vector2.up);
|
2026-01-14 22:48:21 +00:00
|
|
|
|
|
|
|
|
// ▼ 아래
|
2026-01-14 23:00:10 +00:00
|
|
|
SpawnExtra(hitPos + Vector3.right * offset, Vector2.down);
|
2026-01-14 22:48:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void Spawn_Bounce(MobActor mob)
|
|
|
|
|
{
|
|
|
|
|
if (m_ProjectileData.Bounce <= 0 || mob == null) return;
|
|
|
|
|
|
|
|
|
|
MobActor target = IngameMgr.Ins.FindNearestMob(mob.transform.position, mob);
|
2026-01-14 23:00:10 +00:00
|
|
|
if (target == null)
|
|
|
|
|
{
|
|
|
|
|
Kill();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-14 22:48:21 +00:00
|
|
|
|
|
|
|
|
Vector3 dir = (target.transform.position - mob.transform.position).normalized;
|
|
|
|
|
|
2026-01-14 23:00:10 +00:00
|
|
|
SetDirection(dir);
|
2026-01-14 22:48:21 +00:00
|
|
|
--m_ProjectileData.Bounce;
|
|
|
|
|
}
|
2026-01-13 06:20:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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(); } }
|
2026-01-13 07:33:06 +00:00
|
|
|
ObscuredInt _Dmg; public int Dmg { get { return _Dmg; } set { _Dmg = value; _Dmg.RandomizeCryptoKey(); } }
|
2026-01-14 22:48:21 +00:00
|
|
|
|
|
|
|
|
// 발사 계열
|
|
|
|
|
public int PreviousArrow;
|
|
|
|
|
public int DiagonalArrow;
|
|
|
|
|
public int LineIndex;
|
|
|
|
|
public int DiagIndex;
|
|
|
|
|
|
|
|
|
|
// 피격 계열
|
|
|
|
|
public int ArrowLeftRight;
|
|
|
|
|
public int ArrowUpDown;
|
|
|
|
|
public int Bounce;
|
2026-01-11 23:53:38 +00:00
|
|
|
}
|