using System.Collections; using UnityEngine; using RR.Game2D.Core.Managers.Interfaces; namespace RR.Game2D.Entities.Projectiles { [RequireComponent(typeof(Rigidbody2D), typeof(Collider2D))] public abstract class Projectile2D : MonoBehaviour, ILauchable { [Header("Базовые настройки")] [SerializeField] protected float initialSpeed = 15f; [SerializeField] protected float ignoreOwnerTime = 0.1f; [SerializeField] protected float projectileDamage = 1f; public float maxLifetime = 5f; [Header("Столкновения/пробивания")] [SerializeField] protected LayerMask hitLayers = ~0; [SerializeField] protected int maxPierce = 0; //[Header("Гравитация/физика")] //[SerializeField] protected bool useGravity = false; //[SerializeField] protected float gravityScale = 1f; [Header("FX")] [SerializeField] protected GameObject hitVfxPrefab; [SerializeField] protected AudioClip hitSfx; [SerializeField] protected float sfxVolume = 1f; [Header("Другое")] [SerializeField] private bool alignRotationToVelocity = true; //[SerializeField] float damage = 10f; //[SerializeField] float knockback = 0f; protected Rigidbody2D rb; protected Collider2D col; protected int remainingPierce; protected bool launched; protected float launchTime; public GameObject owner; protected virtual void Awake() { rb = GetComponent(); col = GetComponent(); //// Базовые настройки физики //rb.bodyType = RigidbodyType2D.Dynamic; ////rb.gravityScale = useGravity ? gravityScale : 0f; //rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous; //col.isTrigger = false; } protected virtual void OnEnable() { remainingPierce = maxPierce; //if (maxLifetime > 0f) // Invoke(nameof(Despawn), maxLifetime); } protected virtual void FixedUpdate() { if (!launched) return; if (alignRotationToVelocity && rb.linearVelocity.sqrMagnitude > 0.0001f) { float angle = Mathf.Atan2(rb.linearVelocity.y, rb.linearVelocity.x) * Mathf.Rad2Deg; rb.SetRotation(angle); } } protected virtual void OnCollisionEnter2D(Collision2D collision) { if (!IsInHitLayer(collision.collider.gameObject.layer)) return; Vector2 point = collision.GetContact(0).point; OnHit(collision, point, collision.relativeVelocity); } protected virtual void OnTriggerEnter2D(Collider2D other) { if (!col.isTrigger) return; if (!IsInHitLayer(other.gameObject.layer)) return; Vector2 point = other.ClosestPoint(transform.position); OnHit(other, point, rb.linearVelocity); } public virtual void Launch(Vector2 direction, float speed = -1f, GameObject thrower = null) { this.owner = thrower; launched = true; float finalSpeed = (speed > 0f) ? speed : initialSpeed; rb.linearVelocity = direction.normalized * finalSpeed; if (thrower != null && ignoreOwnerTime > 0f) StartCoroutine(IgnoreOwnerTemporarily(thrower, ignoreOwnerTime)); launchTime = Time.time; } /// /// /// /// /// protected virtual IEnumerator IgnoreOwnerTemporarily(GameObject who, float time) { Collider2D[] ownerColliders = who.GetComponentsInChildren(); foreach (var oc in ownerColliders) if (oc && col) Physics2D.IgnoreCollision(col, oc, true); yield return new WaitForSeconds(time); foreach (var oc in ownerColliders) if (oc && col) Physics2D.IgnoreCollision(col, oc, false); } /// /// /// /// /// protected bool IsInHitLayer(int layer) { return ((1 << layer) & hitLayers.value) != 0; } /// /// /// /// /// /// protected virtual void TryDealDamage(Collider2D target, Vector2 hitPoint, Vector2 relativeVelocity) { //var dmg = target.GetComponentInParent(); //if (dmg != null) //{ // Vector2 force = (rb != null && rb.linearVelocity.sqrMagnitude > 0.001f) // ? rb.linearVelocity.normalized * knockback // : Vector2.zero; // dmg.ApplyDamage(damage, hitPoint, force, owner ?? gameObject); //} } /// /// /// /// /// /// protected virtual void OnHit(Collision2D collision, Vector2 point, Vector2 relativeVelocity) { TryDealDamage(collision.collider, point, relativeVelocity); PlayFX(point); if (remainingPierce > 0) { remainingPierce--; return; } OnImpactFinal(collision); } /// /// /// /// /// /// protected virtual void OnHit(Collider2D otherCol, Vector2 point, Vector2 relativeVelocity) { TryDealDamage(otherCol, point, relativeVelocity); PlayFX(point); if (remainingPierce > 0) { remainingPierce--; return; } OnImpactFinal(otherCol); } /// /// Метод воспроизведения эффектов в определённой точке /// Может отрабатывать при любом поведении снаряда (столкновение, истечение времени жизни и т.д.) /// /// Точка воспроизведения FX protected virtual void PlayFX(Vector2 point) { if (hitVfxPrefab != null) { Instantiate(hitVfxPrefab, point, Quaternion.identity); } if (hitSfx != null) { AudioSource.PlayClipAtPoint(hitSfx, point, sfxVolume); } } /// /// /// /// protected virtual void OnImpactFinal(Collision2D collision) => Despawn(); /// /// /// /// protected virtual void OnImpactFinal(Collider2D other) => Despawn(); /// /// /// public virtual void Despawn() { Destroy(gameObject); } } }