158 lines
5.0 KiB
C#

// Projectile2D.cs
using System.Collections;
using UnityEngine;
namespace Projectiles2D
{
public interface IDamageable2D
{
void ApplyDamage(float amount, Vector2 hitPoint, Vector2 force, GameObject source);
}
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
public abstract class Projectile2D : MonoBehaviour
{
public float initialSpeed = 15f;
public float maxLifetime = 5f;
public bool alignRotationToVelocity = true;
protected float damage = 10f;
public float knockback = 0f;
public LayerMask hitLayers = ~0;
public int maxPierce = 0;
public bool useGravity = false;
public float gravityScale = 1f;
protected Rigidbody2D rb;
protected Collider2D col;
protected int remainingPierce;
protected bool launched;
public GameObject owner;
protected virtual void Awake()
{
rb = GetComponent<Rigidbody2D>();
col = GetComponent<Collider2D>();
// Базовые настройки физики
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);
}
public virtual void Launch(Vector2 direction, float speed = -1f, GameObject owner = null, float ignoreOwnerTime = 0.1f)
{
this.owner = owner;
launched = true;
float finalSpeed = (speed > 0f) ? speed : initialSpeed;
rb.linearVelocity = direction.normalized * finalSpeed;
if (owner != null && ignoreOwnerTime > 0f)
StartCoroutine(IgnoreOwnerTemporarily(owner, ignoreOwnerTime));
}
protected virtual IEnumerator IgnoreOwnerTemporarily(GameObject who, float time)
{
var ownerColliders = who.GetComponentsInChildren<Collider2D>();
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 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)
{
var other = collision.collider;
if (!IsInHitLayer(other.gameObject.layer)) return;
Vector2 point = collision.GetContact(0).point;
HandleHit(other, point, collision.relativeVelocity, isTriggerEvent: false, collision);
}
protected virtual void OnTriggerEnter2D(Collider2D other)
{
if (!col.isTrigger) return;
if (!IsInHitLayer(other.gameObject.layer)) return;
Vector2 point = other.ClosestPoint(transform.position);
HandleHit(other, point, rb.linearVelocity, isTriggerEvent: true, collision: null);
}
protected bool IsInHitLayer(int layer)
{
return ((1 << layer) & hitLayers.value) != 0;
}
protected void HandleHit(Collider2D target, Vector2 point, Vector2 relativeVelocity, bool isTriggerEvent, Collision2D collision)
{
TryDealDamage(target, point, relativeVelocity);
OnHit(target, point, relativeVelocity);
if (remainingPierce > 0)
{
remainingPierce--;
return;
}
if (isTriggerEvent) OnImpactFinal(target);
else OnImpactFinal(collision);
}
protected void TryDealDamage(Collider2D target, Vector2 hitPoint, Vector2 relativeVelocity)
{
var dmg = target.GetComponentInParent<IDamageable2D>();
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(Collider2D target, Vector2 point, Vector2 relativeVelocity) { }
protected virtual void OnImpactFinal(Collision2D collision) => Despawn();
protected virtual void OnImpactFinal(Collider2D other) => Despawn();
public virtual void Despawn()
{
Destroy(gameObject);
}
}
}