using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; public delegate GameObject OnCreate(GameObject prefab, Transform parents, Vector3 scale, Vector3 pos, bool ON = true); public class Projectile : MonoBehaviour { public Rigidbody rigi; public LookGravity look; public Vector3 dirAdd = Vector3.zero; [Space(5)] public DistanceHide distanceHide; public float distance = 10;//사거리 public float RANGE = 0;//추가 사거리 [Space(5)] [Range(0, 100f)] public float spdMove = 0.1f;//이동속도 public float spdMovePer = 1;//이동속도 추가 비율 public float CalculateSpeed() { return spdMove * spdMovePer; } [Space(5)] public float DLAATK = 0.6f;//후딜레이 [Space(5)] public float ATK = 6;//공격력 [Space(5)] public List hitCheck; public int cntPenetrate = 1;//관통 횟수 [Space(20)] public Character owner; [Space(10)] public SpriteRenderer spriteRenderer; [System.Serializable] public class InitializedEvent : UnityEvent { } [FormerlySerializedAs("onInitializeded")] [SerializeField] [Space(10)] private InitializedEvent onInitializeded = new InitializedEvent(); /// /// 투사체의 초기화를 진행합니다. /// /// 투사체를 던진 캐릭터 /// 적 레이어 이름 /// 발사 방향 /// 추가 거리 비율 public void OnInitialized(Character owner, int layerEnemy, Vector3 dir, float RANGE_ADD) { this.owner = owner; Rotate(dir); if (look != null) look.OnInitialized(transform.position - dir); RANGE = RANGE_ADD; if (distanceHide != null) { //distanceHide.distance = distance + RANGE; distanceHide.OnInitialized(distance + RANGE); distanceHide.enabled = true; } AddEffect(transform);//스킬에 의해 이팩트가 추가로 붙는 경우가 있는지를 확인 rigi.velocity = Vector3.zero; rigi.AddRelativeForce((dir + dirAdd) * CalculateSpeed(), ForceMode.Impulse);//투사체 발사 방향 설정 dirBefore = dir; STOP = false; for (int i = 0; i < hitCheck.Count; i++) hitCheck[i].Initialized(owner, layerEnemy, cntPenetrate, OnUseMax);//무기의 관통 횟수 초기화 if (radar != null) radar.layer = layerEnemy; onInitializeded.Invoke(); } public void Freeze() { rigi.constraints = RigidbodyConstraints.None; rigi.constraints = RigidbodyConstraints.FreezeRotation | RigidbodyConstraints.FreezePositionY; } public void Realese() { rigi.constraints = RigidbodyConstraints.None; rigi.constraints = RigidbodyConstraints.FreezeRotation; } [Space(10)] public bool USE_SPINE = false; public bool ROTATE = true; public bool ROTATE_SPRITE = true; public Transform rotate; public Transform shadow; /// /// 방향에 따른 이미지의 방향을 재설정합니다. /// /// void Rotate(Vector3 dir) { if (!ROTATE) return; if (USE_SPINE) ; else { if (rotate != null) { rotate.rotation = Quaternion.Euler(0, Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg, 0); if (ROTATE_SPRITE) { Vector3 rot = spriteRenderer.transform.localEulerAngles; rot.x = rotate.localEulerAngles.y == 90 ? -65 : (rotate.localEulerAngles.y == 270 ? 65 : (rotate.localEulerAngles.y == 180 ? 90 : 90)); spriteRenderer.transform.localEulerAngles = rot; } } else transform.rotation = Quaternion.Euler(0, Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg, 0); if (shadow != null) { Vector3 angle = rotate.eulerAngles; angle.y = rotate.eulerAngles.y - 90f; angle.x = 65f; shadow.eulerAngles = angle; } } } [Space(10)] public GameObject radar; Transform target; /// /// 유도, 회수 가 가능한 투사체일때 사용됩니다. /// 타겟을 재설정 합니다. /// /// /// public void TargetSet(Transform target, float durtationRepeat = 0.3f) { this.target = target; dirBefore = Vector3.zero; if (RETURN) InvokeRepeating("Return", durtationRepeat, durtationRepeat);//회수, 부메랑에서 사용 else { timeStep = durtationRepeat / step; dirBefore = (target.position - transform.position).normalized * CalculateSpeed(); rigi.velocity = Vector3.zero; InvokeRepeating("Guided", 0, timeStep);//유도, 유도탄에서 사용 } } public void TargetSet(Transform target, bool ENTER) { this.target = ENTER ? target : (this.target != target ? target : this.target); //Guided(); } Vector3 dirBefore = Vector3.zero; Vector3 dirAfter = Vector3.zero; /// /// 회수되는 투사체에 사용됩니다. /// 현재 부메랑에 적용 중....... /// void Return() { Vector3 pos = transform.position; if(target != null) pos.y = target.position.y; if (RETURN) { if ((target.position - pos).magnitude < disReturnHide) { OnRetunrEnd(); return; } } if (target != null) { Vector3 dir = (target.position - pos).normalized; if (dirBefore != dir) { rigi.velocity = Vector3.zero; rigi.AddForce(dir * CalculateSpeed(), ForceMode.Impulse); Rotate(dir); dirBefore = dir; } } } float timeStep = 0; public int step = 30; int stepCur = 0; /// /// 유도탄 타입의 투사체에 사용됩니다. /// 현재 고스트:다크 의 스킬에 사용 중입니다. /// void Guided() { rigi.velocity = Vector3.zero; rigi.AddForce(Vector3.Lerp(dirBefore, (target.position - transform.position).normalized * CalculateSpeed(), stepCur / step), ForceMode.Impulse); if (step < ++stepCur) stepCur = 0; dirBefore = (target.position - transform.position).normalized * CalculateSpeed(); } private void OnDisable() { target = null; CancelInvoke(); onInitializeded.Invoke(); foreach (GameObject obj in dicEffect.Values) obj.SetActive(false); } /// /// 관통 횟수가 0 이 되었을때 투사체를 숨깁니다. /// void OnUseMax() { if (STOP) return; gameObject.SetActive(false); } [Space(10)] public GameObject effTimeOver; public bool RETURN = false; [Range(0f, 1f)] public float disReturnHide = 0.1f; public HoldingTime timer; /// /// 투사체의 회수가 완료되어 이를 처리합니다. /// void OnRetunrEnd() { OnTimeOver(); } /// /// 투사체의 /// public void OnTimeOver() { if (STOP) return; if (!RETURN || target != null) { if (effTimeOver != null) EventManager.Instance.Invoke("PrefabCreate", new EventManager.ObjectCreate(effTimeOver, null, Vector3.one, spriteRenderer != null ? spriteRenderer.transform.position : transform.position)); //onCreate(effTimeOver, null, Vector3.one, spriteRenderer != null ? spriteRenderer.transform.position : transform.position); gameObject.SetActive(false); } else { if (distanceHide != null) distanceHide.enabled = false; rigi.velocity = Vector3.zero; TargetSet(owner.transform, 0.1f); } } bool STOP = false; /// /// 타이머에 의한 동작을 멈춥니다. /// public void OnTimerStop() { STOP = true; rigi.velocity = Vector3.zero; } /// /// 이동을 멈춥니다. /// public void OnMoveStop() { rigi.velocity = Vector3.zero; } Dictionary dicEffect = new Dictionary(); /// /// 투사체에 붙게되는 추가 이팩트를 적용합니다. /// /// void AddEffect(Transform target) { List skills = owner.skill.SkillList_IDX("AddEffect"); for (int i = 0; i < skills.Count; i++) { string path = $"Prefab/Effect/{skills[i].work}"; if (!dicEffect.ContainsKey(path)) { GameObject eff = ResourcePool.ObjectCreateStatic(Resources.Load(path) as GameObject, target); dicEffect.Add(path, eff); } dicEffect[path].SetActive(true); } } /// /// HitCheck.cs 로 부터 적과 충돌했을때 호출됩니다. /// public void OnHited() { List skills = owner.skill.SkillList_IDX("AddEffect"); for (int i = 0; i < skills.Count; i++) { string path = $"Prefab/Effect/{skills[i].work}_Hited"; GameObject eff = null; if (!dicEffect.ContainsKey(path)) { eff = Resources.Load(path) as GameObject; dicEffect.Add(path, eff); } else eff = dicEffect[path]; //onCreate(eff, null, Vector3.one, transform.position); EventManager.Instance.Invoke("PrefabCreate", new EventManager.ObjectCreate(eff, null, Vector3.one, transform.position)); } } }